From 5385d43c8cfa7cbe2ae6700592cf9755a7df0350 Mon Sep 17 00:00:00 2001 From: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> Date: Thu, 24 Oct 2024 01:46:10 +0530 Subject: [PATCH] app bar and log details page --- mobile-v2/assets/i18n/strings.i18n.json | 12 +- .../appbar/immich_app_bar.widget.dart | 27 +++- ...art => immich_navigation_rail.widget.dart} | 0 .../grid/immich_asset_grid.widget.dart | 5 + .../modules/logs/pages/log_details.page.dart | 128 ++++++++++++++++++ .../modules/logs/pages/logs.page.dart | 2 +- .../router/pages/tab_controller.page.dart | 2 +- mobile-v2/lib/presentation/router/router.dart | 5 +- .../lib/presentation/theme/app_colors.dart | 2 + .../lib/presentation/theme/app_theme.dart | 1 + .../lib/utils/constants/size_constants.dart | 2 + mobile-v2/lib/utils/snackbar_manager.dart | 5 + 12 files changed, 183 insertions(+), 8 deletions(-) rename mobile-v2/lib/presentation/components/common/{immich_navigation_rail.dart => immich_navigation_rail.widget.dart} (100%) create mode 100644 mobile-v2/lib/presentation/modules/logs/pages/log_details.page.dart diff --git a/mobile-v2/assets/i18n/strings.i18n.json b/mobile-v2/assets/i18n/strings.i18n.json index 09358cfc61..052403d8c5 100644 --- a/mobile-v2/assets/i18n/strings.i18n.json +++ b/mobile-v2/assets/i18n/strings.i18n.json @@ -38,9 +38,17 @@ }, "logs": { "title": "Logs", - "no_logs": "No logs available" + "no_logs": "No logs available", + "detail": { + "title": "Log Detail", + "message_heading": "MESSAGE", + "context_heading": "CONTEXT", + "error_heading": "ERROR", + "stack_heading": "STACK TRACE" + } }, "common": { - "loading": "Loading" + "loading": "Loading", + "copied_long": "Copied to clipboard" } } \ No newline at end of file diff --git a/mobile-v2/lib/presentation/components/appbar/immich_app_bar.widget.dart b/mobile-v2/lib/presentation/components/appbar/immich_app_bar.widget.dart index 38bf8d706e..cf01946776 100644 --- a/mobile-v2/lib/presentation/components/appbar/immich_app_bar.widget.dart +++ b/mobile-v2/lib/presentation/components/appbar/immich_app_bar.widget.dart @@ -1,5 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:immich_mobile/i18n/strings.g.dart'; +import 'package:immich_mobile/presentation/components/common/gap.widget.dart'; +import 'package:immich_mobile/presentation/components/common/user_avatar.widget.dart'; +import 'package:immich_mobile/presentation/components/image/immich_logo.widget.dart'; +import 'package:immich_mobile/presentation/states/current_user.state.dart'; +import 'package:immich_mobile/service_locator.dart'; +import 'package:immich_mobile/utils/constants/size_constants.dart'; import 'package:immich_mobile/utils/extensions/build_context.extension.dart'; class ImAppBar extends StatelessWidget implements PreferredSizeWidget { @@ -11,10 +16,26 @@ class ImAppBar extends StatelessWidget implements PreferredSizeWidget { @override Widget build(BuildContext context) { return AppBar( - backgroundColor: context.theme.appBarTheme.backgroundColor, automaticallyImplyLeading: false, + title: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ImLogo(dimension: SizeConstants.xm), + SizedGap.sw(), + ImLogoText(fontSize: 20), + ], + ), + actions: [ + Padding( + padding: const EdgeInsets.only(right: SizeConstants.m), + child: ImUserAvatar( + user: di().value, + radius: SizeConstants.m, + ), + ), + ], + backgroundColor: context.theme.appBarTheme.backgroundColor, centerTitle: false, - title: Text(context.t.immich), ); } } diff --git a/mobile-v2/lib/presentation/components/common/immich_navigation_rail.dart b/mobile-v2/lib/presentation/components/common/immich_navigation_rail.widget.dart similarity index 100% rename from mobile-v2/lib/presentation/components/common/immich_navigation_rail.dart rename to mobile-v2/lib/presentation/components/common/immich_navigation_rail.widget.dart diff --git a/mobile-v2/lib/presentation/components/grid/immich_asset_grid.widget.dart b/mobile-v2/lib/presentation/components/grid/immich_asset_grid.widget.dart index 232b9862a1..61a75f14b5 100644 --- a/mobile-v2/lib/presentation/components/grid/immich_asset_grid.widget.dart +++ b/mobile-v2/lib/presentation/components/grid/immich_asset_grid.widget.dart @@ -65,6 +65,8 @@ class _ImAssetGridState extends State { BlocBuilder( builder: (_, state) { final elements = state.renderList.elements; + + // Append padding if required if (widget.topPadding != null && elements.firstOrNull is! RenderListPaddingElement) { elements.insert( @@ -74,6 +76,9 @@ class _ImAssetGridState extends State { before: elements.firstOrNull, ), ); + } else if (widget.topPadding == null && + elements.firstOrNull is RenderListPaddingElement) { + elements.removeAt(0); } final grid = FlutterListView( diff --git a/mobile-v2/lib/presentation/modules/logs/pages/log_details.page.dart b/mobile-v2/lib/presentation/modules/logs/pages/log_details.page.dart new file mode 100644 index 0000000000..35d880b2fa --- /dev/null +++ b/mobile-v2/lib/presentation/modules/logs/pages/log_details.page.dart @@ -0,0 +1,128 @@ +import 'dart:async'; + +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:immich_mobile/domain/models/log.model.dart'; +import 'package:immich_mobile/i18n/strings.g.dart'; +import 'package:immich_mobile/presentation/components/scaffold/adaptive_route_appbar.widget.dart'; +import 'package:immich_mobile/presentation/theme/app_typography.dart'; +import 'package:immich_mobile/utils/extensions/build_context.extension.dart'; +import 'package:immich_mobile/utils/snackbar_manager.dart'; +import 'package:material_symbols_icons/symbols.dart'; + +@RoutePage() +class LogDetailsPage extends StatelessWidget { + final LogMessage log; + + const LogDetailsPage({super.key, required this.log}); + + String _getClipboardText() { + return """ +Message: ${log.content} +Logged at: ${log.createdAt} +Context: ${log.logger ?? ""} +Error: ${log.error ?? ""} +Stack: +------ +${log.stack ?? ""} +"""; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: ImAdaptiveRouteAppBar( + title: context.t.logs.detail.title, + isPrimary: false, + actions: [ + IconButton( + onPressed: () => unawaited( + Clipboard.setData(ClipboardData(text: _getClipboardText())) + .then((_) { + if (context.mounted) { + SnackbarManager.showText( + content: context.t.common.copied_long, + ); + } + }), + ), + icon: Icon( + Symbols.copy_all_rounded, + color: context.colorScheme.primary, + ), + ), + ], + ), + body: SafeArea( + child: ListView( + children: [ + _TextWithCopy( + heading: context.t.logs.detail.message_heading, + text: log.content, + ), + Divider(), + if (log.logger != null) ...[ + _TextWithCopy( + heading: context.t.logs.detail.context_heading, + text: log.logger!.toString(), + ), + Divider(), + ], + if (log.error != null) ...[ + _TextWithCopy( + heading: context.t.logs.detail.error_heading, + text: log.error!.toString(), + ), + Divider(), + ], + if (log.stack != null) ...[ + _TextWithCopy( + heading: context.t.logs.detail.stack_heading, + text: log.stack!.toString(), + ), + Divider(), + ], + ], + ), + ), + ); + } +} + +class _TextWithCopy extends StatelessWidget { + final String heading; + final String text; + + const _TextWithCopy({required this.heading, required this.text}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 16.0, top: 16.0, bottom: 10.0), + child: Text( + heading, + style: AppTypography.bodyMedium.copyWith( + color: context.colorScheme.primary, + fontWeight: FontWeight.bold, + ), + ), + ), + Padding( + padding: const EdgeInsets.only(left: 16.0, bottom: 10.0), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: SelectableText( + text, + style: AppTypography.bodySmall, + textAlign: TextAlign.justify, + ), + ), + ), + ], + ); + } +} diff --git a/mobile-v2/lib/presentation/modules/logs/pages/logs.page.dart b/mobile-v2/lib/presentation/modules/logs/pages/logs.page.dart index a3a255229f..d2fbc3835e 100644 --- a/mobile-v2/lib/presentation/modules/logs/pages/logs.page.dart +++ b/mobile-v2/lib/presentation/modules/logs/pages/logs.page.dart @@ -30,7 +30,6 @@ class LogsWrapperPage extends StatelessWidget { return ImAdaptiveRouteWrapper( primaryRoute: LogsRoute.name, primaryBody: (_) => const LogsPage(), - bodyRatio: RatioConstants.oneThird, ); } } @@ -151,6 +150,7 @@ class _LogList extends StatelessWidget { trailing: const Icon(Symbols.arrow_forward_ios_rounded, size: 18), dense: true, visualDensity: VisualDensity.compact, + onTap: () => unawaited(context.navigateTo(LogDetailsRoute(log: log))), tileColor: _getTileColor(context, log.level), minLeadingWidth: 10, ); diff --git a/mobile-v2/lib/presentation/router/pages/tab_controller.page.dart b/mobile-v2/lib/presentation/router/pages/tab_controller.page.dart index 445b58c4c2..c3ccab9826 100644 --- a/mobile-v2/lib/presentation/router/pages/tab_controller.page.dart +++ b/mobile-v2/lib/presentation/router/pages/tab_controller.page.dart @@ -2,7 +2,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart'; import 'package:immich_mobile/i18n/strings.g.dart'; -import 'package:immich_mobile/presentation/components/common/immich_navigation_rail.dart'; +import 'package:immich_mobile/presentation/components/common/immich_navigation_rail.widget.dart'; import 'package:immich_mobile/presentation/components/common/user_avatar.widget.dart'; import 'package:immich_mobile/presentation/components/image/immich_logo.widget.dart'; import 'package:immich_mobile/presentation/router/router.dart'; diff --git a/mobile-v2/lib/presentation/router/router.dart b/mobile-v2/lib/presentation/router/router.dart index 9931726c2a..27d1885868 100644 --- a/mobile-v2/lib/presentation/router/router.dart +++ b/mobile-v2/lib/presentation/router/router.dart @@ -1,7 +1,10 @@ import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:immich_mobile/domain/models/log.model.dart'; import 'package:immich_mobile/presentation/modules/home/pages/home.page.dart'; import 'package:immich_mobile/presentation/modules/library/pages/library.page.dart'; import 'package:immich_mobile/presentation/modules/login/pages/login.page.dart'; +import 'package:immich_mobile/presentation/modules/logs/pages/log_details.page.dart'; import 'package:immich_mobile/presentation/modules/logs/pages/logs.page.dart'; import 'package:immich_mobile/presentation/modules/search/pages/search.page.dart'; import 'package:immich_mobile/presentation/modules/settings/pages/about_settings.page.dart'; @@ -37,8 +40,8 @@ class AppRouter extends RootStackRouter { ), AutoRoute(page: LogsWrapperRoute.page, children: [ AutoRoute(page: LogsRoute.page), + AutoRoute(page: LogDetailsRoute.page), ]), - AutoRoute(page: LogsRoute.page), AutoRoute(page: TabControllerRoute.page, children: [ AutoRoute(page: HomeRoute.page), AutoRoute(page: SearchRoute.page), diff --git a/mobile-v2/lib/presentation/theme/app_colors.dart b/mobile-v2/lib/presentation/theme/app_colors.dart index 71d3d605d0..7e10fa4fa6 100644 --- a/mobile-v2/lib/presentation/theme/app_colors.dart +++ b/mobile-v2/lib/presentation/theme/app_colors.dart @@ -26,6 +26,7 @@ abstract final class AppColors { surface: Color(0xFFF0EFF4), onSurface: Color(0xff1a1b21), surfaceContainer: Color(0xfffefbff), + surfaceContainerHigh: Color(0xFFE0E1EA), surfaceContainerHighest: Color(0xffe0e2ef), onSurfaceVariant: Color(0xff444651), outline: Color(0xff747782), @@ -59,6 +60,7 @@ abstract final class AppColors { surface: Color(0xFF15181C), onSurface: Color(0xffe2e2e9), surfaceContainer: Color(0xff1a1e22), + surfaceContainerHigh: Color(0xFF2C3138), surfaceContainerHighest: Color(0xff424852), onSurfaceVariant: Color(0xffc2c6d2), outline: Color(0xff8c919c), diff --git a/mobile-v2/lib/presentation/theme/app_theme.dart b/mobile-v2/lib/presentation/theme/app_theme.dart index 0afb12eb25..42eb1db131 100644 --- a/mobile-v2/lib/presentation/theme/app_theme.dart +++ b/mobile-v2/lib/presentation/theme/app_theme.dart @@ -64,6 +64,7 @@ enum AppTheme { closeButtonIconBuilder: (_) => Icon(Symbols.close_rounded), ), appBarTheme: AppBarTheme( + backgroundColor: color.surfaceContainerLowest, iconTheme: IconThemeData(size: 22, color: color.onSurface), titleTextStyle: AppTypography.titleLarge.copyWith(color: color.onSurface), diff --git a/mobile-v2/lib/utils/constants/size_constants.dart b/mobile-v2/lib/utils/constants/size_constants.dart index 36534e09d6..e95730cfe0 100644 --- a/mobile-v2/lib/utils/constants/size_constants.dart +++ b/mobile-v2/lib/utils/constants/size_constants.dart @@ -14,6 +14,8 @@ abstract final class SizeConstants { abstract final class RatioConstants { const RatioConstants._(); + // 0.5 + static const oneHalf = 1 / 2; // 0.3 static const oneThird = 1 / 3; // 0.25 diff --git a/mobile-v2/lib/utils/snackbar_manager.dart b/mobile-v2/lib/utils/snackbar_manager.dart index 093f060751..1f15053dbf 100644 --- a/mobile-v2/lib/utils/snackbar_manager.dart +++ b/mobile-v2/lib/utils/snackbar_manager.dart @@ -10,4 +10,9 @@ abstract final class SnackbarManager { _s?.clearSnackBars(); _s?.showSnackBar(SnackBar(content: Text(errorMsg))); } + + static void showText({required String content, TextStyle? style}) { + _s?.clearSnackBars(); + _s?.showSnackBar(SnackBar(content: Text(content, style: style))); + } }