From 9367a515f44cfe3c95e7c09265bdbb6d709dc630 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Mon, 23 Jun 2025 02:23:04 +0800 Subject: [PATCH] :lipstick: Optimized desktop experience --- lib/screens/account/me/settings.dart | 2 +- .../account/me/settings_connections.dart | 186 +++++++------ lib/screens/auth/oidc.native.dart | 1 + lib/screens/creators/hub.dart | 1 + lib/screens/explore.dart | 3 + lib/screens/posts/compose.dart | 263 +++++++++--------- lib/screens/settings.dart | 2 +- lib/screens/tabs.dart | 4 +- lib/widgets/post/post_item_creator.dart | 4 +- macos/Podfile.lock | 7 + 10 files changed, 243 insertions(+), 230 deletions(-) diff --git a/lib/screens/account/me/settings.dart b/lib/screens/account/me/settings.dart index 6095469..ed00ddf 100644 --- a/lib/screens/account/me/settings.dart +++ b/lib/screens/account/me/settings.dart @@ -470,7 +470,7 @@ class AccountSettingsScreen extends HookConsumerWidget { ), ), ], - ).padding(horizontal: 16); + ); } else { // Single column layout for narrow screens return Column( diff --git a/lib/screens/account/me/settings_connections.dart b/lib/screens/account/me/settings_connections.dart index c85b5a2..5c7b4bc 100644 --- a/lib/screens/account/me/settings_connections.dart +++ b/lib/screens/account/me/settings_connections.dart @@ -85,52 +85,54 @@ class AccountConnectionSheet extends HookConsumerWidget { return SheetScaffold( titleText: 'accountConnections'.tr(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - getProviderIcon( - connection.provider, - size: 32, - color: Theme.of(context).colorScheme.onSurface, - ), - const Gap(8), - Text(getLocalizedProviderName(connection.provider)).tr(), - const Gap(4), - if (connection.meta.isNotEmpty) - Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisSize: MainAxisSize.min, - children: [ - for (final meta in connection.meta.entries) - Text( - '${meta.key.replaceAll('_', ' ').capitalizeEachWord()}: ${meta.value}', - style: const TextStyle(fontSize: 12), - ), - ], + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + getProviderIcon( + connection.provider, + size: 32, + color: Theme.of(context).colorScheme.onSurface, ), - Text( - connection.providedIdentifier, - style: Theme.of(context).textTheme.bodySmall, - ), - const Gap(8), - Text( - connection.lastUsedAt.formatSystem(), - style: Theme.of(context).textTheme.bodySmall, - ).opacity(0.85), - ], - ).padding(all: 20), - const Divider(height: 1), - ListTile( - leading: const Icon(Symbols.delete), - title: Text('accountConnectionDelete').tr(), - onTap: deleteConnection, - contentPadding: const EdgeInsets.symmetric(horizontal: 20), - ), - ], + const Gap(8), + Text(getLocalizedProviderName(connection.provider)).tr(), + const Gap(4), + if (connection.meta.isNotEmpty) + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + for (final meta in connection.meta.entries) + Text( + '${meta.key.replaceAll('_', ' ').capitalizeEachWord()}: ${meta.value}', + style: const TextStyle(fontSize: 12), + ), + ], + ), + Text( + connection.providedIdentifier, + style: Theme.of(context).textTheme.bodySmall, + ), + const Gap(8), + Text( + connection.lastUsedAt.formatSystem(), + style: Theme.of(context).textTheme.bodySmall, + ).opacity(0.85), + ], + ).padding(all: 20), + const Divider(height: 1), + ListTile( + leading: const Icon(Symbols.delete), + title: Text('accountConnectionDelete').tr(), + onTap: deleteConnection, + contentPadding: const EdgeInsets.symmetric(horizontal: 20), + ), + ], + ), ), ); } @@ -213,52 +215,56 @@ class AccountConnectionNewSheet extends HookConsumerWidget { return SheetScaffold( titleText: 'accountConnectionAdd'.tr(), - child: Column( - spacing: 16, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - DropdownButtonFormField( - value: selectedProvider.value, - decoration: InputDecoration( - prefixIcon: getProviderIcon( - selectedProvider.value, - size: 16, - color: Theme.of(context).colorScheme.onSurface, - ).padding(all: 16), - labelText: 'accountConnectionProvider'.tr(), - border: const OutlineInputBorder(), - ), - items: - providers.map((String provider) { - return DropdownMenuItem( - value: provider, - child: Row( - children: [Text(getLocalizedProviderName(provider)).tr()], - ), - ); - }).toList(), - onChanged: (String? newValue) { - if (newValue != null) { - selectedProvider.value = newValue; - } - }, - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Text('accountConnectionDescription'.tr()), - ), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton.icon( - onPressed: addConnection, - icon: const Icon(Symbols.add), - label: Text('next').tr(), + child: SingleChildScrollView( + child: Column( + spacing: 16, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + DropdownButtonFormField( + value: selectedProvider.value, + decoration: InputDecoration( + prefixIcon: getProviderIcon( + selectedProvider.value, + size: 16, + color: Theme.of(context).colorScheme.onSurface, + ).padding(all: 16), + labelText: 'accountConnectionProvider'.tr(), + border: const OutlineInputBorder(), ), - ], - ), - ], - ).padding(horizontal: 20, vertical: 24), + items: + providers.map((String provider) { + return DropdownMenuItem( + value: provider, + child: Row( + children: [ + Text(getLocalizedProviderName(provider)).tr(), + ], + ), + ); + }).toList(), + onChanged: (String? newValue) { + if (newValue != null) { + selectedProvider.value = newValue; + } + }, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Text('accountConnectionDescription'.tr()), + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton.icon( + onPressed: addConnection, + icon: const Icon(Symbols.add), + label: Text('next').tr(), + ), + ], + ), + ], + ).padding(horizontal: 20, vertical: 24), + ), ); } } diff --git a/lib/screens/auth/oidc.native.dart b/lib/screens/auth/oidc.native.dart index ad45f4d..ab1689f 100644 --- a/lib/screens/auth/oidc.native.dart +++ b/lib/screens/auth/oidc.native.dart @@ -213,6 +213,7 @@ class _OidcScreenState extends ConsumerState { } }, ), + const Gap(8), ], ), ), diff --git a/lib/screens/creators/hub.dart b/lib/screens/creators/hub.dart index 84b47ba..8be92f3 100644 --- a/lib/screens/creators/hub.dart +++ b/lib/screens/creators/hub.dart @@ -123,6 +123,7 @@ class CreatorHubScreen extends HookConsumerWidget { ); return AppScaffold( + noBackground: false, appBar: AppBar( leading: !isWide ? const PageBackButton() : null, title: Text('creatorHub').tr(), diff --git a/lib/screens/explore.dart b/lib/screens/explore.dart index 6e2ac37..e251681 100644 --- a/lib/screens/explore.dart +++ b/lib/screens/explore.dart @@ -96,6 +96,7 @@ class ExploreScreen extends HookConsumerWidget { Tab( child: Text( 'explore'.tr(), + textAlign: TextAlign.center, style: TextStyle( color: Theme.of(context).appBarTheme.foregroundColor!, ), @@ -104,6 +105,7 @@ class ExploreScreen extends HookConsumerWidget { Tab( child: Text( 'exploreFilterSubscriptions'.tr(), + textAlign: TextAlign.center, style: TextStyle( color: Theme.of(context).appBarTheme.foregroundColor!, ), @@ -112,6 +114,7 @@ class ExploreScreen extends HookConsumerWidget { Tab( child: Text( 'exploreFilterFriends'.tr(), + textAlign: TextAlign.center, style: TextStyle( color: Theme.of(context).appBarTheme.foregroundColor!, ), diff --git a/lib/screens/posts/compose.dart b/lib/screens/posts/compose.dart index bbc3bbc..1147fe2 100644 --- a/lib/screens/posts/compose.dart +++ b/lib/screens/posts/compose.dart @@ -191,7 +191,8 @@ class PostComposeScreen extends HookConsumerWidget { progress: progressMap[idx], onRequestUpload: () => ComposeLogic.uploadAttachment(ref, state, idx), - onDelete: () => ComposeLogic.deleteAttachment(ref, state, idx), + onDelete: + () => ComposeLogic.deleteAttachment(ref, state, idx), onMove: (delta) { state.attachments.value = ComposeLogic.moveAttachment( state.attachments.value, @@ -209,12 +210,9 @@ class PostComposeScreen extends HookConsumerWidget { // Build UI return AppScaffold( + noBackground: false, appBar: AppBar( leading: const PageBackButton(), - title: - isWideScreen(context) - ? Text(originalPost != null ? 'editPost'.tr() : 'newPost'.tr()) - : null, actions: [ IconButton( icon: const Icon(Symbols.settings), @@ -272,93 +270,98 @@ class PostComposeScreen extends HookConsumerWidget { // Main content area Expanded( - child: Row( - spacing: 12, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Publisher profile picture - GestureDetector( - child: ProfilePictureWidget( - fileId: state.currentPublisher.value?.picture?.id, - radius: 20, - fallbackIcon: - state.currentPublisher.value == null - ? Symbols.question_mark - : null, - ), - onTap: () { - showModalBottomSheet( - isScrollControlled: true, - context: context, - builder: (context) => const PublisherModal(), - ).then((value) { - if (value != null) { - state.currentPublisher.value = value; - } - }); - }, - ).padding(top: 16), + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 480), + child: Row( + spacing: 12, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Publisher profile picture + GestureDetector( + child: ProfilePictureWidget( + fileId: state.currentPublisher.value?.picture?.id, + radius: 20, + fallbackIcon: + state.currentPublisher.value == null + ? Symbols.question_mark + : null, + ), + onTap: () { + showModalBottomSheet( + isScrollControlled: true, + context: context, + builder: (context) => const PublisherModal(), + ).then((value) { + if (value != null) { + state.currentPublisher.value = value; + } + }); + }, + ).padding(top: 16), - // Post content form - Expanded( - child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(vertical: 12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Content field with borderless design - RawKeyboardListener( - focusNode: FocusNode(), - onKey: - (event) => ComposeLogic.handleKeyPress( - event, - state, - ref, - context, - originalPost: originalPost, - repliedPost: repliedPost, - forwardedPost: forwardedPost, - postType: 0, // Regular post type + // Post content form + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(vertical: 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Content field with borderless design + RawKeyboardListener( + focusNode: FocusNode(), + onKey: + (event) => ComposeLogic.handleKeyPress( + event, + state, + ref, + context, + originalPost: originalPost, + repliedPost: repliedPost, + forwardedPost: forwardedPost, + postType: 0, // Regular post type + ), + child: TextField( + controller: state.contentController, + style: theme.textTheme.bodyMedium, + decoration: InputDecoration( + border: InputBorder.none, + hintText: 'postContent'.tr(), + contentPadding: const EdgeInsets.all(8), ), - child: TextField( - controller: state.contentController, - style: theme.textTheme.bodyMedium, - decoration: InputDecoration( - border: InputBorder.none, - hintText: 'postContent'.tr(), - contentPadding: const EdgeInsets.all(8), + maxLines: null, + onTapOutside: + (_) => + FocusManager.instance.primaryFocus + ?.unfocus(), ), - maxLines: null, - onTapOutside: - (_) => - FocusManager.instance.primaryFocus - ?.unfocus(), ), - ), - const Gap(8), + const Gap(8), - // Attachments preview - ValueListenableBuilder>( - valueListenable: state.attachments, - builder: (context, attachments, _) { - if (attachments.isEmpty) return const SizedBox.shrink(); - return LayoutBuilder( - builder: (context, constraints) { - final isWide = isWideScreen(context); - return isWide - ? buildWideAttachmentGrid() - : buildNarrowAttachmentList(); - }, - ); - }, - ), - ], + // Attachments preview + ValueListenableBuilder>( + valueListenable: state.attachments, + builder: (context, attachments, _) { + if (attachments.isEmpty) { + return const SizedBox.shrink(); + } + return LayoutBuilder( + builder: (context, constraints) { + final isWide = isWideScreen(context); + return isWide + ? buildWideAttachmentGrid() + : buildNarrowAttachmentList(); + }, + ); + }, + ), + ], + ), ), ), - ), - ], - ).padding(horizontal: 16), + ], + ).padding(horizontal: 16), + ).alignment(Alignment.topCenter), ), // Bottom toolbar @@ -409,11 +412,11 @@ class PostComposeScreen extends HookConsumerWidget { ), const Gap(4), Text( - 'edit'.tr(), - style: Theme.of(context).textTheme.labelMedium?.copyWith( - color: Theme.of(context).colorScheme.onPrimaryContainer, - ), - ), + 'edit'.tr(), + style: Theme.of(context).textTheme.labelMedium?.copyWith( + color: Theme.of(context).colorScheme.onPrimaryContainer, + ), + ), ], ).padding(all: 16), ), @@ -427,10 +430,7 @@ class PostComposeScreen extends HookConsumerWidget { children: [ Row( children: [ - Icon( - Symbols.reply, - size: 16, - ), + Icon(Symbols.reply, size: 16), const Gap(4), Text( 'postReplyingTo'.tr(), @@ -452,10 +452,7 @@ class PostComposeScreen extends HookConsumerWidget { children: [ Row( children: [ - Icon( - Symbols.forward, - size: 16, - ), + Icon(Symbols.forward, size: 16), const Gap(4), Text( 'postForwardingTo'.tr(), @@ -482,10 +479,7 @@ class PostComposeScreen extends HookConsumerWidget { children: [ Row( children: [ - Icon( - Symbols.reply, - size: 16, - ), + Icon(Symbols.reply, size: 16), const Gap(4), Text( 'postReplyingTo'.tr(), @@ -510,10 +504,7 @@ class PostComposeScreen extends HookConsumerWidget { children: [ Row( children: [ - Icon( - Symbols.forward, - size: 16, - ), + Icon(Symbols.forward, size: 16), const Gap(4), Text( 'postForwardingTo'.tr(), @@ -537,37 +528,41 @@ class PostComposeScreen extends HookConsumerWidget { showModalBottomSheet( context: context, isScrollControlled: true, - builder: (context) => DraggableScrollableSheet( - initialChildSize: 0.7, - maxChildSize: 0.9, - minChildSize: 0.5, - builder: (context, scrollController) => Container( - decoration: BoxDecoration( - color: Theme.of(context).scaffoldBackgroundColor, - borderRadius: const BorderRadius.vertical(top: Radius.circular(16)), - ), - child: Column( - children: [ - Container( - width: 40, - height: 4, - margin: const EdgeInsets.symmetric(vertical: 8), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.outline, - borderRadius: BorderRadius.circular(2), + builder: + (context) => DraggableScrollableSheet( + initialChildSize: 0.7, + maxChildSize: 0.9, + minChildSize: 0.5, + builder: + (context, scrollController) => Container( + decoration: BoxDecoration( + color: Theme.of(context).scaffoldBackgroundColor, + borderRadius: const BorderRadius.vertical( + top: Radius.circular(16), + ), + ), + child: Column( + children: [ + Container( + width: 40, + height: 4, + margin: const EdgeInsets.symmetric(vertical: 8), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.outline, + borderRadius: BorderRadius.circular(2), + ), + ), + Expanded( + child: SingleChildScrollView( + controller: scrollController, + padding: const EdgeInsets.all(16), + child: PostItem(item: post, isOpenable: false), + ), + ), + ], + ), ), - ), - Expanded( - child: SingleChildScrollView( - controller: scrollController, - padding: const EdgeInsets.all(16), - child: PostItem(item: post, isOpenable: false), - ), - ), - ], ), - ), - ), ); }, child: Container( diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart index 0fb6ff1..56fa276 100644 --- a/lib/screens/settings.dart +++ b/lib/screens/settings.dart @@ -525,7 +525,7 @@ class SettingsScreen extends HookConsumerWidget { ), ), ], - ).padding(horizontal: 16); + ); } else { // Single column layout for narrow screens return Column( diff --git a/lib/screens/tabs.dart b/lib/screens/tabs.dart index db66aa9..89ae258 100644 --- a/lib/screens/tabs.dart +++ b/lib/screens/tabs.dart @@ -135,7 +135,7 @@ class TabbedFabLocation extends FloatingActionButtonLocation { final double fabX = scaffoldGeometry.scaffoldSize.width - scaffoldGeometry.floatingActionButtonSize.width - - 16.0 - + 16 - safeAreaPadding.right; // Use safe area bottom padding + navigation bar height (typically 80px) @@ -144,7 +144,7 @@ class TabbedFabLocation extends FloatingActionButtonLocation { scaffoldGeometry.floatingActionButtonSize.height - scaffoldGeometry.bottomSheetSize.height - safeAreaPadding.bottom - - 80.0 + + (isWideScreen(context) ? 24 : 80) + 16; return Offset(fabX, fabY); diff --git a/lib/widgets/post/post_item_creator.dart b/lib/widgets/post/post_item_creator.dart index 01429e2..d27cf48 100644 --- a/lib/widgets/post/post_item_creator.dart +++ b/lib/widgets/post/post_item_creator.dart @@ -88,14 +88,14 @@ class PostItemCreator extends HookConsumerWidget { ); }, child: Material( - color: backgroundColor, + color: Colors.transparent, borderRadius: BorderRadius.circular(12), elevation: 1, child: InkWell( borderRadius: BorderRadius.circular(12), onTap: () { if (isOpenable) { - context.router.push(PostDetailRoute(id: item.id)); + context.router.pushPath('/posts/${item.id}'); } }, child: Padding( diff --git a/macos/Podfile.lock b/macos/Podfile.lock index a1d7e9f..7f068ba 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -94,6 +94,9 @@ PODS: - flutter_webrtc - FlutterMacOS - WebRTC-SDK (= 125.6422.07) + - local_auth_darwin (0.0.1): + - Flutter + - FlutterMacOS - media_kit_libs_macos_video (1.0.4): - FlutterMacOS - media_kit_video (0.0.1): @@ -173,6 +176,7 @@ DEPENDENCIES: - gal (from `Flutter/ephemeral/.symlinks/plugins/gal/darwin`) - irondash_engine_context (from `Flutter/ephemeral/.symlinks/plugins/irondash_engine_context/macos`) - livekit_client (from `Flutter/ephemeral/.symlinks/plugins/livekit_client/macos`) + - local_auth_darwin (from `Flutter/ephemeral/.symlinks/plugins/local_auth_darwin/darwin`) - media_kit_libs_macos_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos`) - media_kit_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos`) - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) @@ -239,6 +243,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/irondash_engine_context/macos livekit_client: :path: Flutter/ephemeral/.symlinks/plugins/livekit_client/macos + local_auth_darwin: + :path: Flutter/ephemeral/.symlinks/plugins/local_auth_darwin/darwin media_kit_libs_macos_video: :path: Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos media_kit_video: @@ -293,6 +299,7 @@ SPEC CHECKSUMS: GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 irondash_engine_context: 893c7d96d20ce361d7e996f39d360c4c2f9869ba livekit_client: 6a35243df3da61750c98e266e02dedcf5d25c888 + local_auth_darwin: 553ce4f9b16d3fdfeafce9cf042e7c9f77c1c391 media_kit_libs_macos_video: 85a23e549b5f480e72cae3e5634b5514bc692f65 media_kit_video: fa6564e3799a0a28bff39442334817088b7ca758 nanopb: fad817b59e0457d11a5dfbde799381cd727c1275