1
0
mirror of https://github.com/immich-app/immich.git synced 2024-12-25 10:43:13 +02:00

feat(mobile): improved logging page experience (#2158)

* feat(mobile): improve logging page

* Use new API for share file

* removed unused code

* Better safe area on the home screen

* Added preparing share dialog to home screen
This commit is contained in:
Alex 2023-04-03 16:43:46 -05:00 committed by GitHub
parent 2dcccb37a0
commit d6f2ca6aaa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 313 additions and 35 deletions

View File

@ -27,6 +27,7 @@ import 'package:immich_mobile/shared/providers/websocket.provider.dart';
import 'package:immich_mobile/shared/services/share.service.dart'; import 'package:immich_mobile/shared/services/share.service.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:immich_mobile/shared/ui/immich_toast.dart'; import 'package:immich_mobile/shared/ui/immich_toast.dart';
import 'package:immich_mobile/shared/ui/share_dialog.dart';
class HomePage extends HookConsumerWidget { class HomePage extends HookConsumerWidget {
const HomePage({Key? key}) : super(key: key); const HomePage({Key? key}) : super(key: key);
@ -81,7 +82,19 @@ class HomePage extends HookConsumerWidget {
} }
void onShareAssets() { void onShareAssets() {
ref.watch(shareServiceProvider).shareAssets(selection.value.toList()); showDialog(
context: context,
builder: (BuildContext buildContext) {
ref
.watch(shareServiceProvider)
.shareAssets(selection.value.toList())
.then((_) => Navigator.of(buildContext).pop());
return const ShareDialog();
},
barrierDismissible: false,
);
// ref.watch(shareServiceProvider).shareAssets(selection.value.toList());
selectionEnabledHook.value = false; selectionEnabledHook.value = false;
} }
@ -244,6 +257,7 @@ class HomePage extends HookConsumerWidget {
return SafeArea( return SafeArea(
top: true, top: true,
bottom: false,
child: Stack( child: Stack(
children: [ children: [
ref.watch(assetProvider).renderList == null || ref.watch(assetProvider).renderList == null ||
@ -261,17 +275,14 @@ class HomePage extends HookConsumerWidget {
onRefresh: refreshAssets, onRefresh: refreshAssets,
), ),
if (selectionEnabledHook.value) if (selectionEnabledHook.value)
SafeArea( ControlBottomAppBar(
bottom: true, onShare: onShareAssets,
child: ControlBottomAppBar( onFavorite: onFavoriteAssets,
onShare: onShareAssets, onDelete: onDelete,
onFavorite: onFavoriteAssets, onAddToAlbum: onAddToAlbum,
onDelete: onDelete, albums: albums,
onAddToAlbum: onAddToAlbum, sharedAlbums: sharedAlbums,
albums: albums, onCreateNewAlbum: onCreateNewAlbum,
sharedAlbums: sharedAlbums,
onCreateNewAlbum: onCreateNewAlbum,
),
), ),
], ],
), ),
@ -279,9 +290,11 @@ class HomePage extends HookConsumerWidget {
} }
return Scaffold( return Scaffold(
appBar: HomePageAppBar( appBar: !selectionEnabledHook.value
onPopBack: reloadAllAsset, ? HomePageAppBar(
), onPopBack: reloadAllAsset,
)
: null,
drawer: const ProfileDrawer(), drawer: const ProfileDrawer(),
body: buildBody(), body: buildBody(),
); );

View File

@ -34,8 +34,10 @@ import 'package:immich_mobile/routing/duplicate_guard.dart';
import 'package:immich_mobile/routing/gallery_permission_guard.dart'; import 'package:immich_mobile/routing/gallery_permission_guard.dart';
import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/models/album.dart'; import 'package:immich_mobile/shared/models/album.dart';
import 'package:immich_mobile/shared/models/logger_message.model.dart';
import 'package:immich_mobile/shared/providers/api.provider.dart'; import 'package:immich_mobile/shared/providers/api.provider.dart';
import 'package:immich_mobile/shared/services/api.service.dart'; import 'package:immich_mobile/shared/services/api.service.dart';
import 'package:immich_mobile/shared/views/app_log_detail_page.dart';
import 'package:immich_mobile/shared/views/app_log_page.dart'; import 'package:immich_mobile/shared/views/app_log_page.dart';
import 'package:immich_mobile/shared/views/splash_screen.dart'; import 'package:immich_mobile/shared/views/splash_screen.dart';
import 'package:immich_mobile/shared/views/tab_controller_page.dart'; import 'package:immich_mobile/shared/views/tab_controller_page.dart';
@ -47,8 +49,12 @@ part 'router.gr.dart';
replaceInRouteName: 'Page,Route', replaceInRouteName: 'Page,Route',
routes: <AutoRoute>[ routes: <AutoRoute>[
AutoRoute(page: SplashScreenPage, initial: true), AutoRoute(page: SplashScreenPage, initial: true),
AutoRoute(page: PermissionOnboardingPage, guards: [AuthGuard, DuplicateGuard]), AutoRoute(
AutoRoute(page: LoginPage, page: PermissionOnboardingPage,
guards: [AuthGuard, DuplicateGuard],
),
AutoRoute(
page: LoginPage,
guards: [ guards: [
DuplicateGuard, DuplicateGuard,
], ],
@ -65,7 +71,10 @@ part 'router.gr.dart';
], ],
transitionsBuilder: TransitionsBuilders.fadeIn, transitionsBuilder: TransitionsBuilders.fadeIn,
), ),
AutoRoute(page: GalleryViewerPage, guards: [AuthGuard, DuplicateGuard, GalleryPermissionGuard]), AutoRoute(
page: GalleryViewerPage,
guards: [AuthGuard, DuplicateGuard, GalleryPermissionGuard],
),
AutoRoute(page: VideoViewerPage, guards: [AuthGuard, DuplicateGuard]), AutoRoute(page: VideoViewerPage, guards: [AuthGuard, DuplicateGuard]),
AutoRoute(page: BackupControllerPage, guards: [AuthGuard, DuplicateGuard]), AutoRoute(page: BackupControllerPage, guards: [AuthGuard, DuplicateGuard]),
AutoRoute(page: SearchResultPage, guards: [AuthGuard, DuplicateGuard]), AutoRoute(page: SearchResultPage, guards: [AuthGuard, DuplicateGuard]),
@ -75,7 +84,10 @@ part 'router.gr.dart';
AutoRoute(page: FavoritesPage, guards: [AuthGuard, DuplicateGuard]), AutoRoute(page: FavoritesPage, guards: [AuthGuard, DuplicateGuard]),
AutoRoute(page: AllVideosPage, guards: [AuthGuard, DuplicateGuard]), AutoRoute(page: AllVideosPage, guards: [AuthGuard, DuplicateGuard]),
AutoRoute(page: AllMotionPhotosPage, guards: [AuthGuard, DuplicateGuard]), AutoRoute(page: AllMotionPhotosPage, guards: [AuthGuard, DuplicateGuard]),
AutoRoute(page: RecentlyAddedPage, guards: [AuthGuard, DuplicateGuard],), AutoRoute(
page: RecentlyAddedPage,
guards: [AuthGuard, DuplicateGuard],
),
CustomRoute<AssetSelectionPageResult?>( CustomRoute<AssetSelectionPageResult?>(
page: AssetSelectionPage, page: AssetSelectionPage,
guards: [AuthGuard, DuplicateGuard], guards: [AuthGuard, DuplicateGuard],
@ -92,14 +104,18 @@ part 'router.gr.dart';
guards: [AuthGuard, DuplicateGuard], guards: [AuthGuard, DuplicateGuard],
transitionsBuilder: TransitionsBuilders.slideBottom, transitionsBuilder: TransitionsBuilders.slideBottom,
), ),
AutoRoute(page: BackupAlbumSelectionPage, guards: [AuthGuard, DuplicateGuard]), AutoRoute(
page: BackupAlbumSelectionPage,
guards: [AuthGuard, DuplicateGuard],
),
AutoRoute(page: AlbumPreviewPage, guards: [AuthGuard, DuplicateGuard]), AutoRoute(page: AlbumPreviewPage, guards: [AuthGuard, DuplicateGuard]),
CustomRoute( CustomRoute(
page: FailedBackupStatusPage, page: FailedBackupStatusPage,
guards: [AuthGuard, DuplicateGuard], guards: [AuthGuard, DuplicateGuard],
transitionsBuilder: TransitionsBuilders.slideBottom, transitionsBuilder: TransitionsBuilders.slideBottom,
), ),
AutoRoute(page: SettingsPage, AutoRoute(
page: SettingsPage,
guards: [ guards: [
AuthGuard, AuthGuard,
DuplicateGuard, DuplicateGuard,
@ -109,6 +125,9 @@ part 'router.gr.dart';
page: AppLogPage, page: AppLogPage,
transitionsBuilder: TransitionsBuilders.slideBottom, transitionsBuilder: TransitionsBuilders.slideBottom,
), ),
AutoRoute(
page: AppLogDetailPage,
),
], ],
) )
class AppRouter extends _$AppRouter { class AppRouter extends _$AppRouter {
@ -116,14 +135,19 @@ class AppRouter extends _$AppRouter {
final ApiService _apiService; final ApiService _apiService;
AppRouter( AppRouter(
this._apiService, this._apiService,
GalleryPermissionNotifier galleryPermissionNotifier, GalleryPermissionNotifier galleryPermissionNotifier,
) : super( ) : super(
authGuard: AuthGuard(_apiService), authGuard: AuthGuard(_apiService),
duplicateGuard: DuplicateGuard(), duplicateGuard: DuplicateGuard(),
galleryPermissionGuard: GalleryPermissionGuard(galleryPermissionNotifier), galleryPermissionGuard:
GalleryPermissionGuard(galleryPermissionNotifier),
); );
} }
final appRouterProvider = final appRouterProvider = Provider(
Provider((ref) => AppRouter(ref.watch(apiServiceProvider), ref.watch(galleryPermissionNotifier.notifier))); (ref) => AppRouter(
ref.watch(apiServiceProvider),
ref.watch(galleryPermissionNotifier.notifier),
),
);

View File

@ -230,6 +230,16 @@ class _$AppRouter extends RootStackRouter {
barrierDismissible: false, barrierDismissible: false,
); );
}, },
AppLogDetailRoute.name: (routeData) {
final args = routeData.argsAs<AppLogDetailRouteArgs>();
return MaterialPageX<dynamic>(
routeData: routeData,
child: AppLogDetailPage(
key: args.key,
logMessage: args.logMessage,
),
);
},
HomeRoute.name: (routeData) { HomeRoute.name: (routeData) {
return MaterialPageX<dynamic>( return MaterialPageX<dynamic>(
routeData: routeData, routeData: routeData,
@ -485,6 +495,10 @@ class _$AppRouter extends RootStackRouter {
AppLogRoute.name, AppLogRoute.name,
path: '/app-log-page', path: '/app-log-page',
), ),
RouteConfig(
AppLogDetailRoute.name,
path: '/app-log-detail-page',
),
]; ];
} }
@ -974,6 +988,40 @@ class AppLogRoute extends PageRouteInfo<void> {
static const String name = 'AppLogRoute'; static const String name = 'AppLogRoute';
} }
/// generated route for
/// [AppLogDetailPage]
class AppLogDetailRoute extends PageRouteInfo<AppLogDetailRouteArgs> {
AppLogDetailRoute({
Key? key,
required LoggerMessage logMessage,
}) : super(
AppLogDetailRoute.name,
path: '/app-log-detail-page',
args: AppLogDetailRouteArgs(
key: key,
logMessage: logMessage,
),
);
static const String name = 'AppLogDetailRoute';
}
class AppLogDetailRouteArgs {
const AppLogDetailRouteArgs({
this.key,
required this.logMessage,
});
final Key? key;
final LoggerMessage logMessage;
@override
String toString() {
return 'AppLogDetailRouteArgs{key: $key, logMessage: $logMessage}';
}
}
/// generated route for /// generated route for
/// [HomePage] /// [HomePage]
class HomeRoute extends PageRouteInfo<void> { class HomeRoute extends PageRouteInfo<void> {

View File

@ -102,15 +102,13 @@ class ImmichLogger {
} }
// Share file // Share file
// ignore: deprecated_member_use await Share.shareXFiles(
await Share.shareFiles( [XFile(filePath)],
[filePath],
subject: "Immich logs $dateTime", subject: "Immich logs $dateTime",
sharePositionOrigin: Rect.zero, sharePositionOrigin: Rect.zero,
).then(
(value) => logFile.delete(),
); );
// Clean up temp file
await logFile.delete();
} }
/// Flush pending log messages to persistent storage /// Flush pending log messages to persistent storage

View File

@ -36,7 +36,6 @@ class ShareService {
} }
}); });
// ignore: deprecated_member_use
Share.shareXFiles( Share.shareXFiles(
await Future.wait(downloadedXFiles), await Future.wait(downloadedXFiles),
sharePositionOrigin: Rect.zero, sharePositionOrigin: Rect.zero,

View File

@ -369,7 +369,6 @@ class SyncService {
List<AssetPathEntity> onDevice, [ List<AssetPathEntity> onDevice, [
Set<String>? excludedAssets, Set<String>? excludedAssets,
]) async { ]) async {
_log.info("Syncing ${onDevice.length} albums from device: $onDevice");
onDevice.sort((a, b) => a.id.compareTo(b.id)); onDevice.sort((a, b) => a.id.compareTo(b.id));
final List<Album> inDb = final List<Album> inDb =
await _db.albums.where().localIdIsNotNull().sortByLocalId().findAll(); await _db.albums.where().localIdIsNotNull().sortByLocalId().findAll();

View File

@ -0,0 +1,190 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/models/logger_message.model.dart';
import 'package:flutter/services.dart';
class AppLogDetailPage extends HookConsumerWidget {
const AppLogDetailPage({super.key, required this.logMessage});
final LoggerMessage logMessage;
@override
Widget build(BuildContext context, WidgetRef ref) {
var isDarkMode = Theme.of(context).brightness == Brightness.dark;
buildStackMessage(String stackTrace) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
"STACK TRACES",
style: TextStyle(
fontSize: 12.0,
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.bold,
),
),
),
IconButton(
onPressed: () {
Clipboard.setData(ClipboardData(text: stackTrace))
.then((_) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Copied to clipboard")),
);
});
},
icon: Icon(
Icons.copy,
size: 16.0,
color: Theme.of(context).primaryColor,
),
)
],
),
Container(
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900] : Colors.grey[200],
borderRadius: BorderRadius.circular(15.0),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: SelectableText(
stackTrace,
style: const TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
fontFamily: "Inconsolata",
),
),
),
),
],
),
);
}
buildLogMessage(String message) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
"MESSAGE",
style: TextStyle(
fontSize: 12.0,
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.bold,
),
),
),
IconButton(
onPressed: () {
Clipboard.setData(ClipboardData(text: message)).then((_) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Copied to clipboard")),
);
});
},
icon: Icon(
Icons.copy,
size: 16.0,
color: Theme.of(context).primaryColor,
),
)
],
),
Container(
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900] : Colors.grey[200],
borderRadius: BorderRadius.circular(15.0),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: SelectableText(
message,
style: const TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
fontFamily: "Inconsolata",
),
),
),
),
],
),
);
}
buildLogContext1(String context1) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
"FROM",
style: TextStyle(
fontSize: 12.0,
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.bold,
),
),
),
Container(
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900] : Colors.grey[200],
borderRadius: BorderRadius.circular(15.0),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: SelectableText(
context1.toString(),
style: const TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
fontFamily: "Inconsolata",
),
),
),
),
],
),
);
}
return Scaffold(
appBar: AppBar(
title: const Text("Log Detail"),
),
body: SafeArea(
child: ListView(
children: [
buildLogMessage(logMessage.message),
if (logMessage.context1 != null)
buildLogContext1(logMessage.context1.toString()),
if (logMessage.context2 != null)
buildStackMessage(logMessage.context2.toString())
],
),
),
);
}
}

View File

@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/models/logger_message.model.dart'; import 'package:immich_mobile/shared/models/logger_message.model.dart';
import 'package:immich_mobile/shared/services/immich_logger.service.dart'; import 'package:immich_mobile/shared/services/immich_logger.service.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
@ -123,6 +124,12 @@ class AppLogPage extends HookConsumerWidget {
itemBuilder: (context, index) { itemBuilder: (context, index) {
var logMessage = logMessages.value[index]; var logMessage = logMessages.value[index];
return ListTile( return ListTile(
onTap: () => AutoRouter.of(context).push(
AppLogDetailRoute(
logMessage: logMessage,
),
),
trailing: const Icon(Icons.arrow_forward_ios_rounded),
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
dense: true, dense: true,
tileColor: getTileColor(logMessage.level), tileColor: getTileColor(logMessage.level),