From 1bae2e6b1691f8182eee3930913c2ef8fc005fc6 Mon Sep 17 00:00:00 2001 From: zarzet <42882290+zarzet@users.noreply.github.com> Date: Thu, 15 Oct 2026 02:52:15 +0700 Subject: [PATCH] refactor(ui): unify expressive actions and modal transitions --- lib/screens/artist_screen.dart | 1 + lib/screens/home_tab_explore.dart | 1 + lib/screens/home_tab_search_results.dart | 14 +++--- lib/screens/queue_tab_navigation.dart | 10 ++--- lib/screens/setup_screen.dart | 1 + lib/screens/track_metadata_actions.dart | 1 + lib/screens/track_metadata_convert.dart | 2 + lib/screens/track_metadata_edit_sheet.dart | 3 ++ lib/screens/track_metadata_screen.dart | 1 + lib/screens/track_metadata_screen_menu.dart | 1 + lib/services/batch_track_actions.dart | 2 + lib/utils/extension_auth_launcher.dart | 6 +-- lib/widgets/artist_concerts_button.dart | 45 +++++++++++++------ lib/widgets/download_service_picker.dart | 4 ++ lib/widgets/player_details_sheet.dart | 2 + lib/widgets/playlist_picker_sheet.dart | 2 + .../track_collection_quick_actions.dart | 2 + 17 files changed, 70 insertions(+), 28 deletions(-) diff --git a/lib/screens/artist_screen.dart b/lib/screens/artist_screen.dart index 6fae7956..d8cfcf8e 100644 --- a/lib/screens/artist_screen.dart +++ b/lib/screens/artist_screen.dart @@ -1001,6 +1001,7 @@ class _ArtistScreenState extends ConsumerState showModalBottomSheet( context: context, + sheetAnimationStyle: AppMotion.surfaceEntrance(context), useRootNavigator: true, isScrollControlled: true, backgroundColor: context.isMornye diff --git a/lib/screens/home_tab_explore.dart b/lib/screens/home_tab_explore.dart index 0b387af4..bacddb9c 100644 --- a/lib/screens/home_tab_explore.dart +++ b/lib/screens/home_tab_explore.dart @@ -430,6 +430,7 @@ extension _HomeTabExploreUI on _HomeTabState { showModalBottomSheet( context: context, + sheetAnimationStyle: AppMotion.surfaceEntrance(context), useRootNavigator: true, backgroundColor: colorScheme.surface, builder: (context) => SafeArea( diff --git a/lib/screens/home_tab_search_results.dart b/lib/screens/home_tab_search_results.dart index cf92037a..b35a563d 100644 --- a/lib/screens/home_tab_search_results.dart +++ b/lib/screens/home_tab_search_results.dart @@ -177,6 +177,7 @@ extension _HomeTabSearchResultsUI on _HomeTabState { final mornye = context.isMornye; showModalBottomSheet( context: context, + sheetAnimationStyle: AppMotion.surfaceEntrance(context), useRootNavigator: true, isScrollControlled: true, backgroundColor: mornye @@ -494,7 +495,8 @@ extension _HomeTabSearchResultsUI on _HomeTabState { if (showSortButton) SizedBox( height: 32, - child: TextButton.icon( + child: ExpressiveButton( + text: true, onPressed: () => _showSortOptions(colorScheme), icon: mornye ? null @@ -507,7 +509,11 @@ extension _HomeTabSearchResultsUI on _HomeTabState { ? colorScheme.primary : colorScheme.onSurfaceVariant, ), - label: Text( + style: TextButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 8), + visualDensity: VisualDensity.compact, + ), + child: Text( _searchSortOption != HomeSearchSortOption.defaultOrder ? _sortOptionLabel(_searchSortOption) : context.l10n.libraryFilterSort, @@ -519,10 +525,6 @@ extension _HomeTabSearchResultsUI on _HomeTabState { : colorScheme.onSurfaceVariant, ), ), - style: TextButton.styleFrom( - padding: const EdgeInsets.symmetric(horizontal: 8), - visualDensity: VisualDensity.compact, - ), ), ), ], diff --git a/lib/screens/queue_tab_navigation.dart b/lib/screens/queue_tab_navigation.dart index 0786ec3e..a8d8e9d8 100644 --- a/lib/screens/queue_tab_navigation.dart +++ b/lib/screens/queue_tab_navigation.dart @@ -309,10 +309,10 @@ extension _QueueTabNavigation on _QueueTabState { final controller = TextEditingController(); final formKey = GlobalKey(); - final playlistName = await showDialog( + final playlistName = await showAppDialog( context: context, builder: (dialogContext) { - return AlertDialog( + return AppAlertDialog( title: Text(dialogContext.l10n.collectionCreatePlaylist), content: Form( key: formKey, @@ -336,12 +336,12 @@ extension _QueueTabNavigation on _QueueTabState { ), ), actions: [ - ExpressiveButton( - text: true, + AppDialogAction( onPressed: () => Navigator.of(dialogContext).pop(), child: Text(dialogContext.l10n.dialogCancel), ), - ExpressiveButton( + AppDialogAction( + filled: true, onPressed: () { if (formKey.currentState?.validate() != true) return; Navigator.of(dialogContext).pop(controller.text.trim()); diff --git a/lib/screens/setup_screen.dart b/lib/screens/setup_screen.dart index 03b481a1..baad9885 100644 --- a/lib/screens/setup_screen.dart +++ b/lib/screens/setup_screen.dart @@ -438,6 +438,7 @@ class _SetupScreenState extends ConsumerState final colorScheme = Theme.of(context).colorScheme; await showModalBottomSheet( context: context, + sheetAnimationStyle: AppMotion.surfaceEntrance(context), useRootNavigator: true, backgroundColor: colorScheme.surfaceContainerHigh, builder: (ctx) => SafeArea( diff --git a/lib/screens/track_metadata_actions.dart b/lib/screens/track_metadata_actions.dart index 1536b2a6..541cf60e 100644 --- a/lib/screens/track_metadata_actions.dart +++ b/lib/screens/track_metadata_actions.dart @@ -56,6 +56,7 @@ extension _TrackMetadataFileActions on _TrackMetadataScreenState { final saved = await showModalBottomSheet( context: context, + sheetAnimationStyle: AppMotion.surfaceEntrance(context), useRootNavigator: true, isScrollControlled: true, backgroundColor: context.isMornye diff --git a/lib/screens/track_metadata_convert.dart b/lib/screens/track_metadata_convert.dart index c16b03f5..d8f87804 100644 --- a/lib/screens/track_metadata_convert.dart +++ b/lib/screens/track_metadata_convert.dart @@ -281,6 +281,7 @@ extension _TrackMetadataConvertAndCueSplit on _TrackMetadataScreenState { showModalBottomSheet( context: context, + sheetAnimationStyle: AppMotion.surfaceEntrance(context), useRootNavigator: true, isScrollControlled: true, backgroundColor: context.isMornye ? Colors.transparent : null, @@ -382,6 +383,7 @@ extension _TrackMetadataConvertAndCueSplit on _TrackMetadataScreenState { showModalBottomSheet( context: this.context, + sheetAnimationStyle: AppMotion.surfaceEntrance(this.context), useRootNavigator: true, isScrollControlled: true, builder: (sheetContext) { diff --git a/lib/screens/track_metadata_edit_sheet.dart b/lib/screens/track_metadata_edit_sheet.dart index 37a070a9..7018c645 100644 --- a/lib/screens/track_metadata_edit_sheet.dart +++ b/lib/screens/track_metadata_edit_sheet.dart @@ -9,6 +9,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:path/path.dart' as p; import 'package:spotiflac_android/controllers/metadata_editor_controller.dart'; import 'package:spotiflac_android/l10n/l10n.dart'; +import 'package:spotiflac_android/theme/app_motion.dart'; import 'package:spotiflac_android/providers/extension_provider.dart'; import 'package:spotiflac_android/providers/settings_provider.dart'; import 'package:spotiflac_android/services/metadata_cover_resources.dart'; @@ -120,6 +121,7 @@ class _EditMetadataSheetState extends State { FocusScope.of(context).unfocus(); final selectedId = await showModalBottomSheet( context: context, + sheetAnimationStyle: AppMotion.surfaceEntrance(context), useRootNavigator: true, isScrollControlled: true, showDragHandle: true, @@ -202,6 +204,7 @@ class _EditMetadataSheetState extends State { final currentValue = _coverMaxDimension ?? 0; final selectedValue = await showModalBottomSheet( context: context, + sheetAnimationStyle: AppMotion.surfaceEntrance(context), useRootNavigator: true, isScrollControlled: true, showDragHandle: true, diff --git a/lib/screens/track_metadata_screen.dart b/lib/screens/track_metadata_screen.dart index d253734a..47afbb3a 100644 --- a/lib/screens/track_metadata_screen.dart +++ b/lib/screens/track_metadata_screen.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:io'; import 'dart:ui' show ImageFilter; import 'package:flutter/material.dart'; +import 'package:spotiflac_android/theme/app_motion.dart'; import 'package:spotiflac_android/widgets/app_alert_dialog.dart'; import 'package:flutter/cupertino.dart' show CupertinoButton, CupertinoIcons; import 'package:spotiflac_android/widgets/app_bottom_sheet.dart'; diff --git a/lib/screens/track_metadata_screen_menu.dart b/lib/screens/track_metadata_screen_menu.dart index 7bd25750..c0a210c3 100644 --- a/lib/screens/track_metadata_screen_menu.dart +++ b/lib/screens/track_metadata_screen_menu.dart @@ -268,6 +268,7 @@ extension _TrackMetadataMenu on _TrackMetadataScreenState { } showModalBottomSheet( context: screenContext, + sheetAnimationStyle: AppMotion.surfaceEntrance(screenContext), useRootNavigator: true, backgroundColor: colorScheme.surfaceContainerHigh, isScrollControlled: true, diff --git a/lib/services/batch_track_actions.dart b/lib/services/batch_track_actions.dart index beec4af6..ee47b346 100644 --- a/lib/services/batch_track_actions.dart +++ b/lib/services/batch_track_actions.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:path_provider/path_provider.dart'; import 'package:spotiflac_android/l10n/l10n.dart'; +import 'package:spotiflac_android/theme/app_motion.dart'; import 'package:spotiflac_android/models/unified_library_item.dart'; import 'package:spotiflac_android/providers/download_history_provider.dart'; import 'package:spotiflac_android/providers/local_library_provider.dart'; @@ -97,6 +98,7 @@ Future showBatchConvertSheet( await showModalBottomSheet( context: modalContext, + sheetAnimationStyle: AppMotion.surfaceEntrance(modalContext), useRootNavigator: true, isScrollControlled: true, backgroundColor: modalContext.isMornye ? Colors.transparent : null, diff --git a/lib/utils/extension_auth_launcher.dart b/lib/utils/extension_auth_launcher.dart index cc4a61f6..ac2ae6c5 100644 --- a/lib/utils/extension_auth_launcher.dart +++ b/lib/utils/extension_auth_launcher.dart @@ -8,6 +8,7 @@ import 'package:spotiflac_android/services/app_navigation_service.dart'; import 'package:spotiflac_android/services/platform_bridge.dart'; import 'package:spotiflac_android/utils/extension_session_callback.dart'; import 'package:spotiflac_android/utils/logger.dart'; +import 'package:spotiflac_android/widgets/app_alert_dialog.dart'; import 'package:spotiflac_android/widgets/app_loading_indicator.dart'; import 'package:spotiflac_android/widgets/expressive_button.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -298,15 +299,14 @@ Future showExtensionVerificationHelpDialog( }); try { - await showDialog( + await showAppDialog( context: context, - useRootNavigator: true, barrierDismissible: false, builder: (dialogContext) { activeDialogContext = dialogContext; final dialogL10n = dialogContext.l10n; return StatefulBuilder( - builder: (dialogContext, setDialogState) => AlertDialog( + builder: (dialogContext, setDialogState) => AppAlertDialog( title: Text(title), content: Column( mainAxisSize: MainAxisSize.min, diff --git a/lib/widgets/artist_concerts_button.dart b/lib/widgets/artist_concerts_button.dart index 430c8c20..ebb072c7 100644 --- a/lib/widgets/artist_concerts_button.dart +++ b/lib/widgets/artist_concerts_button.dart @@ -2,7 +2,9 @@ import 'package:flutter/material.dart'; import 'package:spotiflac_android/l10n/l10n.dart'; import 'package:spotiflac_android/models/artist_concert.dart'; import 'package:spotiflac_android/screens/artist_concerts_screen.dart'; +import 'package:spotiflac_android/theme/mornye_theme.dart'; import 'package:spotiflac_android/widgets/animation_utils.dart'; +import 'package:spotiflac_android/widgets/expressive_button.dart'; class ArtistConcertsButton extends StatelessWidget { const ArtistConcertsButton({ @@ -21,17 +23,36 @@ class ArtistConcertsButton extends StatelessWidget { @override Widget build(BuildContext context) { if (concerts.isEmpty) return const SizedBox.shrink(); - return TextButton.icon( - onPressed: () => Navigator.of(context).push( - slidePageRoute( - page: ArtistConcertsScreen( - artistName: artistName, - concerts: concerts, - coverUrl: coverUrl, - providerId: providerId, - ), + void open() => Navigator.of(context).push( + slidePageRoute( + page: ArtistConcertsScreen( + artistName: artistName, + concerts: concerts, + coverUrl: coverUrl, + providerId: providerId, ), ), + ); + final label = Text( + context.l10n.artistUpcomingConcerts, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ); + if (!context.isMornye) { + // A compact tonal pill with the expressive press morph. + return ExpressiveButton( + tonal: true, + onPressed: open, + icon: const Icon(Icons.confirmation_number_rounded, size: 16), + style: FilledButton.styleFrom( + minimumSize: const Size(0, 36), + padding: const EdgeInsets.symmetric(horizontal: 14), + ), + child: label, + ); + } + return TextButton.icon( + onPressed: open, style: TextButton.styleFrom( foregroundColor: Colors.white, backgroundColor: Colors.black.withValues(alpha: 0.35), @@ -45,11 +66,7 @@ class ArtistConcertsButton extends StatelessWidget { textStyle: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600), ), icon: const Icon(Icons.confirmation_number_rounded, size: 16), - label: Text( - context.l10n.artistUpcomingConcerts, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), + label: label, ); } } diff --git a/lib/widgets/download_service_picker.dart b/lib/widgets/download_service_picker.dart index 7ffc0ac9..9530521e 100644 --- a/lib/widgets/download_service_picker.dart +++ b/lib/widgets/download_service_picker.dart @@ -4,6 +4,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:spotiflac_android/providers/extension_provider.dart'; +import 'package:spotiflac_android/theme/app_motion.dart'; import 'package:spotiflac_android/providers/settings_provider.dart'; import 'package:spotiflac_android/l10n/l10n.dart'; import 'package:spotiflac_android/theme/mornye_icons.dart'; @@ -44,6 +45,7 @@ class DownloadServicePicker extends ConsumerStatefulWidget { showModalBottomSheet( context: context, + sheetAnimationStyle: AppMotion.surfaceEntrance(context), useRootNavigator: true, backgroundColor: mornye ? Colors.transparent @@ -57,7 +59,9 @@ class DownloadServicePicker extends ConsumerStatefulWidget { : null, isScrollControlled: true, showDragHandle: !mornye, + // Explicit constraints replace the theme's, so repeat its tablet width. constraints: BoxConstraints( + maxWidth: 640, maxHeight: MediaQuery.sizeOf(context).height * 0.88, ), builder: (context) { diff --git a/lib/widgets/player_details_sheet.dart b/lib/widgets/player_details_sheet.dart index 7d2c1ddf..2b5e6bf5 100644 --- a/lib/widgets/player_details_sheet.dart +++ b/lib/widgets/player_details_sheet.dart @@ -2,6 +2,7 @@ import 'package:audio_service/audio_service.dart'; import 'package:flutter/material.dart'; import 'package:spotiflac_android/controllers/player_metadata_controller.dart'; import 'package:spotiflac_android/l10n/l10n.dart'; +import 'package:spotiflac_android/theme/app_motion.dart'; import 'package:spotiflac_android/theme/mornye_theme.dart'; import 'package:spotiflac_android/utils/isrc_utils.dart'; import 'package:spotiflac_android/widgets/app_icon.dart'; @@ -19,6 +20,7 @@ Future showPlayerDetailsSheet( final metadataFuture = metadata.readDetails(mediaItem); return showModalBottomSheet( context: context, + sheetAnimationStyle: AppMotion.surfaceEntrance(context), isScrollControlled: true, useRootNavigator: mornye, showDragHandle: !mornye, diff --git a/lib/widgets/playlist_picker_sheet.dart b/lib/widgets/playlist_picker_sheet.dart index 972b6e61..e45384e5 100644 --- a/lib/widgets/playlist_picker_sheet.dart +++ b/lib/widgets/playlist_picker_sheet.dart @@ -4,6 +4,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:spotiflac_android/l10n/l10n.dart'; +import 'package:spotiflac_android/theme/app_motion.dart'; import 'package:spotiflac_android/models/track.dart'; import 'package:spotiflac_android/providers/library_collections_provider.dart'; import 'package:spotiflac_android/theme/mornye_theme.dart'; @@ -39,6 +40,7 @@ Future showAddTracksToPlaylistSheet( await showModalBottomSheet( context: context, + sheetAnimationStyle: AppMotion.surfaceEntrance(context), useRootNavigator: true, showDragHandle: !context.isMornye, backgroundColor: context.isMornye ? Colors.transparent : null, diff --git a/lib/widgets/track_collection_quick_actions.dart b/lib/widgets/track_collection_quick_actions.dart index 3020b864..a3abc213 100644 --- a/lib/widgets/track_collection_quick_actions.dart +++ b/lib/widgets/track_collection_quick_actions.dart @@ -1,6 +1,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; +import 'package:spotiflac_android/theme/app_motion.dart'; import 'package:spotiflac_android/theme/mornye_theme.dart'; import 'package:flutter/services.dart'; import 'package:spotiflac_android/widgets/app_bottom_sheet.dart'; @@ -49,6 +50,7 @@ class TrackCollectionQuickActions extends ConsumerWidget { } showModalBottomSheet( context: context, + sheetAnimationStyle: AppMotion.surfaceEntrance(context), useRootNavigator: true, isScrollControlled: true, backgroundColor: colorScheme.surfaceContainerHigh,