mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-07-29 07:18:49 +02:00
feat(tablet): clamp remaining screens - settings, queue lists, wizard
- SettingsGroup centers itself at 720dp via its incoming constraint (not screen width, so groups inside clamped sheets are never over-inset; unbounded-width contexts guarded); priority scaffold, bespoke hero cards, and donate page get the same inset - all 18+ settings pages clamp from shared widgets - queue list-mode, local/downloaded album track lists, favorite artists, home recent-access, and extension detail sections adopt wideListInset like the screens fixed earlier - track metadata content clamped at 720dp, which also tames the width-derived spectrogram height - announcement dialog capped at 480dp wide; setup wizard and tutorial content at 560dp with a 400dp action button
This commit is contained in:
@@ -9,6 +9,7 @@ import 'package:spotiflac_android/services/platform_bridge.dart';
|
||||
import 'package:spotiflac_android/services/batch_track_actions.dart';
|
||||
import 'package:spotiflac_android/models/unified_library_item.dart';
|
||||
import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/utils/adaptive_layout.dart';
|
||||
import 'package:spotiflac_android/utils/confirm_and_delete_tracks.dart';
|
||||
import 'package:spotiflac_android/utils/cover_art_utils.dart';
|
||||
import 'package:spotiflac_android/utils/file_access.dart';
|
||||
@@ -515,23 +516,26 @@ class _DownloadedAlbumScreenState extends ConsumerState<DownloadedAlbumScreen>
|
||||
final discMap = _getDiscGroups(tracks);
|
||||
|
||||
if (discMap.length <= 1) {
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
final track = tracks[index];
|
||||
return KeyedSubtree(
|
||||
key: ValueKey(track.id),
|
||||
child: StaggeredListItem(
|
||||
index: index,
|
||||
child: _buildTrackItem(
|
||||
context,
|
||||
colorScheme,
|
||||
track,
|
||||
tracks,
|
||||
index,
|
||||
return SliverPadding(
|
||||
padding: EdgeInsets.symmetric(horizontal: wideListInset(context)),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
final track = tracks[index];
|
||||
return KeyedSubtree(
|
||||
key: ValueKey(track.id),
|
||||
child: StaggeredListItem(
|
||||
index: index,
|
||||
child: _buildTrackItem(
|
||||
context,
|
||||
colorScheme,
|
||||
track,
|
||||
tracks,
|
||||
index,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}, childCount: tracks.length),
|
||||
);
|
||||
}, childCount: tracks.length),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -565,7 +569,10 @@ class _DownloadedAlbumScreenState extends ConsumerState<DownloadedAlbumScreen>
|
||||
}
|
||||
}
|
||||
|
||||
return SliverList(delegate: SliverChildListDelegate(children));
|
||||
return SliverPadding(
|
||||
padding: EdgeInsets.symmetric(horizontal: wideListInset(context)),
|
||||
sliver: SliverList(delegate: SliverChildListDelegate(children)),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTrackItem(
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/providers/library_collections_provider.dart';
|
||||
import 'package:spotiflac_android/screens/artist_screen.dart';
|
||||
import 'package:spotiflac_android/services/cover_cache_manager.dart';
|
||||
import 'package:spotiflac_android/utils/adaptive_layout.dart';
|
||||
import 'package:spotiflac_android/utils/nav_bar_inset.dart';
|
||||
import 'package:spotiflac_android/widgets/animation_utils.dart';
|
||||
import 'package:spotiflac_android/widgets/settings_sliver_app_bar.dart';
|
||||
@@ -58,67 +59,70 @@ class FavoriteArtistsScreen extends ConsumerWidget {
|
||||
),
|
||||
)
|
||||
else
|
||||
SliverList.separated(
|
||||
itemCount: artists.length,
|
||||
separatorBuilder: (_, _) => const Divider(height: 1),
|
||||
itemBuilder: (context, index) {
|
||||
final artist = artists[index];
|
||||
return ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 4,
|
||||
),
|
||||
leading: _ArtistThumbnail(artist: artist),
|
||||
title: Text(
|
||||
artist.name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
subtitle:
|
||||
artist.providerId == null || artist.providerId!.isEmpty
|
||||
? null
|
||||
: Text(
|
||||
artist.providerId!,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
trailing: IconButton(
|
||||
tooltip: context.l10n.artistOptionRemoveFromFavorites,
|
||||
icon: Icon(Icons.favorite, color: colorScheme.error),
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(libraryCollectionsProvider.notifier)
|
||||
.removeFavoriteArtist(artist.key);
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
context.l10n.collectionRemovedFromFavoriteArtists(
|
||||
artist.name,
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.symmetric(horizontal: wideListInset(context)),
|
||||
sliver: SliverList.separated(
|
||||
itemCount: artists.length,
|
||||
separatorBuilder: (_, _) => const Divider(height: 1),
|
||||
itemBuilder: (context, index) {
|
||||
final artist = artists[index];
|
||||
return ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 4,
|
||||
),
|
||||
leading: _ArtistThumbnail(artist: artist),
|
||||
title: Text(
|
||||
artist.name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
subtitle:
|
||||
artist.providerId == null || artist.providerId!.isEmpty
|
||||
? null
|
||||
: Text(
|
||||
artist.providerId!,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
trailing: IconButton(
|
||||
tooltip: context.l10n.artistOptionRemoveFromFavorites,
|
||||
icon: Icon(Icons.favorite, color: colorScheme.error),
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(libraryCollectionsProvider.notifier)
|
||||
.removeFavoriteArtist(artist.key);
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
context.l10n.collectionRemovedFromFavoriteArtists(
|
||||
artist.name,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
slidePageRoute<void>(
|
||||
page: ArtistScreen(
|
||||
artistId: artist.artistId,
|
||||
artistName: artist.name,
|
||||
coverUrl: artist.imageUrl,
|
||||
extensionId:
|
||||
artist.providerId != null &&
|
||||
artist.providerId!.isNotEmpty
|
||||
? artist.providerId
|
||||
: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
slidePageRoute<void>(
|
||||
page: ArtistScreen(
|
||||
artistId: artist.artistId,
|
||||
artistName: artist.name,
|
||||
coverUrl: artist.imageUrl,
|
||||
extensionId:
|
||||
artist.providerId != null &&
|
||||
artist.providerId!.isNotEmpty
|
||||
? artist.providerId
|
||||
: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(child: SizedBox(height: bottomInset)),
|
||||
],
|
||||
|
||||
@@ -2209,7 +2209,9 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
final hasHiddenDownloads = view.hasHiddenDownloads;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
|
||||
padding:
|
||||
const EdgeInsets.fromLTRB(16, 8, 16, 8) +
|
||||
EdgeInsets.symmetric(horizontal: wideListInset(context)),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:spotiflac_android/models/track.dart';
|
||||
import 'package:spotiflac_android/providers/download_queue_provider.dart';
|
||||
import 'package:spotiflac_android/providers/extension_provider.dart';
|
||||
import 'package:spotiflac_android/providers/settings_provider.dart';
|
||||
import 'package:spotiflac_android/utils/adaptive_layout.dart';
|
||||
import 'package:spotiflac_android/utils/confirm_and_delete_tracks.dart';
|
||||
import 'package:spotiflac_android/utils/file_access.dart';
|
||||
import 'package:spotiflac_android/utils/image_cache_utils.dart';
|
||||
@@ -372,7 +373,10 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen>
|
||||
);
|
||||
}
|
||||
|
||||
return SliverMainAxisGroup(slivers: slivers);
|
||||
return SliverPadding(
|
||||
padding: EdgeInsets.symmetric(horizontal: wideListInset(context)),
|
||||
sliver: SliverMainAxisGroup(slivers: slivers),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTrackItem(
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:share_plus/share_plus.dart';
|
||||
import 'package:spotiflac_android/services/ffmpeg_service.dart';
|
||||
import 'package:spotiflac_android/services/platform_bridge.dart';
|
||||
import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/utils/adaptive_layout.dart';
|
||||
import 'package:spotiflac_android/utils/app_bar_layout.dart';
|
||||
import 'package:spotiflac_android/utils/nav_bar_inset.dart';
|
||||
import 'package:spotiflac_android/widgets/settings_group.dart';
|
||||
|
||||
@@ -469,36 +469,52 @@ extension _QueueTabFilterWidgets on _QueueTabState {
|
||||
),
|
||||
)
|
||||
else
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
if (index < collectionCount) {
|
||||
return _buildAllTabListCollectionItem(
|
||||
context: context,
|
||||
colorScheme: colorScheme,
|
||||
entry: collectionEntries[index],
|
||||
collectionState: collectionState,
|
||||
filteredUnifiedItems: filteredUnifiedItems,
|
||||
);
|
||||
}
|
||||
final afterCollections = index - collectionCount;
|
||||
if (afterCollections < leadCount) {
|
||||
return leadListCell(afterCollections);
|
||||
}
|
||||
final trackIndex = afterCollections - leadCount;
|
||||
if (trackIndex < filteredUnifiedItems.length) {
|
||||
final item = filteredUnifiedItems[trackIndex];
|
||||
return KeyedSubtree(
|
||||
key: ValueKey(item.id),
|
||||
child: LongPressDraggable<UnifiedLibraryItem>(
|
||||
data: item,
|
||||
feedback: _buildDragFeedback(
|
||||
context,
|
||||
item,
|
||||
colorScheme,
|
||||
),
|
||||
childWhenDragging: Opacity(
|
||||
opacity: 0.4,
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.symmetric(horizontal: wideListInset(context)),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
if (index < collectionCount) {
|
||||
return _buildAllTabListCollectionItem(
|
||||
context: context,
|
||||
colorScheme: colorScheme,
|
||||
entry: collectionEntries[index],
|
||||
collectionState: collectionState,
|
||||
filteredUnifiedItems: filteredUnifiedItems,
|
||||
);
|
||||
}
|
||||
final afterCollections = index - collectionCount;
|
||||
if (afterCollections < leadCount) {
|
||||
return leadListCell(afterCollections);
|
||||
}
|
||||
final trackIndex = afterCollections - leadCount;
|
||||
if (trackIndex < filteredUnifiedItems.length) {
|
||||
final item = filteredUnifiedItems[trackIndex];
|
||||
return KeyedSubtree(
|
||||
key: ValueKey(item.id),
|
||||
child: LongPressDraggable<UnifiedLibraryItem>(
|
||||
data: item,
|
||||
feedback: _buildDragFeedback(
|
||||
context,
|
||||
item,
|
||||
colorScheme,
|
||||
),
|
||||
childWhenDragging: Opacity(
|
||||
opacity: 0.4,
|
||||
child: _buildUnifiedLibraryItem(
|
||||
context,
|
||||
item,
|
||||
colorScheme,
|
||||
downloadedNavigationItems:
|
||||
downloadedNavigationItems,
|
||||
downloadedNavigationIndex:
|
||||
downloadedNavigationIndexByUnifiedId[item.id],
|
||||
localNavigationItems: localNavigationItems,
|
||||
localNavigationIndex:
|
||||
localNavigationIndexByUnifiedId[item.id],
|
||||
libraryItems: filteredUnifiedItems,
|
||||
),
|
||||
),
|
||||
child: _buildUnifiedLibraryItem(
|
||||
context,
|
||||
item,
|
||||
@@ -513,25 +529,13 @@ extension _QueueTabFilterWidgets on _QueueTabState {
|
||||
libraryItems: filteredUnifiedItems,
|
||||
),
|
||||
),
|
||||
child: _buildUnifiedLibraryItem(
|
||||
context,
|
||||
item,
|
||||
colorScheme,
|
||||
downloadedNavigationItems: downloadedNavigationItems,
|
||||
downloadedNavigationIndex:
|
||||
downloadedNavigationIndexByUnifiedId[item.id],
|
||||
localNavigationItems: localNavigationItems,
|
||||
localNavigationIndex:
|
||||
localNavigationIndexByUnifiedId[item.id],
|
||||
libraryItems: filteredUnifiedItems,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
childCount:
|
||||
leadCount + collectionCount + filteredUnifiedItems.length,
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
childCount:
|
||||
leadCount + collectionCount + filteredUnifiedItems.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -605,28 +609,33 @@ extension _QueueTabFilterWidgets on _QueueTabState {
|
||||
}, childCount: leadCount + filteredUnifiedItems.length),
|
||||
),
|
||||
)
|
||||
: SliverList(
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
if (index < leadCount) {
|
||||
return leadListCell(index);
|
||||
}
|
||||
final item = filteredUnifiedItems[index - leadCount];
|
||||
return KeyedSubtree(
|
||||
key: ValueKey(item.id),
|
||||
child: _buildUnifiedLibraryItem(
|
||||
context,
|
||||
item,
|
||||
colorScheme,
|
||||
downloadedNavigationItems: downloadedNavigationItems,
|
||||
downloadedNavigationIndex:
|
||||
downloadedNavigationIndexByUnifiedId[item.id],
|
||||
localNavigationItems: localNavigationItems,
|
||||
localNavigationIndex:
|
||||
localNavigationIndexByUnifiedId[item.id],
|
||||
libraryItems: filteredUnifiedItems,
|
||||
),
|
||||
);
|
||||
}, childCount: leadCount + filteredUnifiedItems.length),
|
||||
: SliverPadding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: wideListInset(context),
|
||||
),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
if (index < leadCount) {
|
||||
return leadListCell(index);
|
||||
}
|
||||
final item = filteredUnifiedItems[index - leadCount];
|
||||
return KeyedSubtree(
|
||||
key: ValueKey(item.id),
|
||||
child: _buildUnifiedLibraryItem(
|
||||
context,
|
||||
item,
|
||||
colorScheme,
|
||||
downloadedNavigationItems: downloadedNavigationItems,
|
||||
downloadedNavigationIndex:
|
||||
downloadedNavigationIndexByUnifiedId[item.id],
|
||||
localNavigationItems: localNavigationItems,
|
||||
localNavigationIndex:
|
||||
localNavigationIndexByUnifiedId[item.id],
|
||||
libraryItems: filteredUnifiedItems,
|
||||
),
|
||||
);
|
||||
}, childCount: leadCount + filteredUnifiedItems.length),
|
||||
),
|
||||
),
|
||||
|
||||
if (!hasQueueItems &&
|
||||
@@ -960,5 +969,4 @@ extension _QueueTabFilterWidgets on _QueueTabState {
|
||||
return selectedItems.isNotEmpty &&
|
||||
selectedItems.every((item) => item.localItem != null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:path_provider/path_provider.dart';
|
||||
import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/providers/repo_provider.dart';
|
||||
import 'package:spotiflac_android/providers/extension_provider.dart';
|
||||
import 'package:spotiflac_android/utils/adaptive_layout.dart';
|
||||
import 'package:spotiflac_android/utils/nav_bar_inset.dart';
|
||||
|
||||
class ExtensionDetailsScreen extends ConsumerStatefulWidget {
|
||||
@@ -153,7 +154,9 @@ class _ExtensionDetailsScreenState
|
||||
) {
|
||||
return SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
padding:
|
||||
const EdgeInsets.all(16) +
|
||||
EdgeInsets.symmetric(horizontal: wideListInset(context)),
|
||||
child: Card(
|
||||
elevation: 0,
|
||||
color: colorScheme.surfaceContainerLow,
|
||||
@@ -295,7 +298,9 @@ class _ExtensionDetailsScreenState
|
||||
) {
|
||||
return SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 8, 20, 8),
|
||||
padding:
|
||||
const EdgeInsets.fromLTRB(20, 8, 20, 8) +
|
||||
EdgeInsets.symmetric(horizontal: wideListInset(context)),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon, size: 20, color: colorScheme.primary),
|
||||
@@ -320,7 +325,9 @@ class _ExtensionDetailsScreenState
|
||||
) {
|
||||
return SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 24, vertical: 8) +
|
||||
EdgeInsets.symmetric(horizontal: wideListInset(context)),
|
||||
child: Text(
|
||||
ext.description,
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
@@ -339,7 +346,9 @@ class _ExtensionDetailsScreenState
|
||||
) {
|
||||
return SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 24, vertical: 8) +
|
||||
EdgeInsets.symmetric(horizontal: wideListInset(context)),
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
@@ -369,7 +378,9 @@ class _ExtensionDetailsScreenState
|
||||
ColorScheme colorScheme,
|
||||
) {
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 8) +
|
||||
EdgeInsets.symmetric(horizontal: wideListInset(context)),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Card(
|
||||
elevation: 0,
|
||||
@@ -416,7 +427,9 @@ class _ExtensionDetailsScreenState
|
||||
final isUtility = ext.category == 'utility';
|
||||
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 8) +
|
||||
EdgeInsets.symmetric(horizontal: wideListInset(context)),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Card(
|
||||
elevation: 0,
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:spotiflac_android/services/cover_cache_manager.dart';
|
||||
import 'package:spotiflac_android/constants/app_info.dart';
|
||||
import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/utils/adaptive_layout.dart';
|
||||
import 'package:spotiflac_android/widgets/settings_group.dart';
|
||||
import 'package:spotiflac_android/widgets/settings_sliver_app_bar.dart';
|
||||
|
||||
@@ -23,7 +24,12 @@ class AboutPage extends StatelessWidget {
|
||||
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
16 + wideListInset(context),
|
||||
16,
|
||||
16 + wideListInset(context),
|
||||
8,
|
||||
),
|
||||
child: _AppHeaderCard(),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/l10n/supported_locales.dart';
|
||||
import 'package:spotiflac_android/providers/settings_provider.dart';
|
||||
import 'package:spotiflac_android/providers/theme_provider.dart';
|
||||
import 'package:spotiflac_android/utils/adaptive_layout.dart';
|
||||
import 'package:spotiflac_android/widgets/settings_group.dart';
|
||||
import 'package:spotiflac_android/widgets/settings_sliver_app_bar.dart';
|
||||
|
||||
@@ -24,8 +25,8 @@ class AppearanceSettingsPage extends ConsumerWidget {
|
||||
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 16 + wideListInset(context),
|
||||
vertical: 8,
|
||||
),
|
||||
child: _ThemePreviewCard(),
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:spotiflac_android/services/app_remote_config_service.dart';
|
||||
import 'package:spotiflac_android/utils/adaptive_layout.dart';
|
||||
import 'package:spotiflac_android/widgets/donate_icons.dart';
|
||||
import 'package:spotiflac_android/widgets/settings_sliver_app_bar.dart';
|
||||
|
||||
@@ -63,7 +64,12 @@ class _DonatePageState extends State<DonatePage> {
|
||||
const SettingsSliverAppBar(title: 'Donate'),
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
16 + wideListInset(context),
|
||||
16,
|
||||
16 + wideListInset(context),
|
||||
16,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
_DonateLinksCard(colorScheme: colorScheme, config: _config),
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/providers/extension_provider.dart';
|
||||
import 'package:spotiflac_android/providers/settings_provider.dart';
|
||||
import 'package:spotiflac_android/utils/adaptive_layout.dart';
|
||||
import 'package:spotiflac_android/widgets/discard_changes_dialog.dart';
|
||||
import 'package:spotiflac_android/widgets/settings_group.dart';
|
||||
import 'package:spotiflac_android/widgets/settings_sliver_app_bar.dart';
|
||||
@@ -53,6 +54,7 @@ class _DownloadFallbackExtensionsPageState
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final wideInset = wideListInset(context);
|
||||
|
||||
return PopScope(
|
||||
canPop: !_hasChanges,
|
||||
@@ -92,7 +94,12 @@ class _DownloadFallbackExtensionsPageState
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
16 + wideInset,
|
||||
16,
|
||||
16 + wideInset,
|
||||
16,
|
||||
),
|
||||
child: Text(
|
||||
context.l10n.providerPriorityFallbackExtensionsDescription,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
@@ -104,7 +111,7 @@ class _DownloadFallbackExtensionsPageState
|
||||
if (_extensions.isEmpty)
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: EdgeInsets.symmetric(horizontal: 16 + wideInset),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
@@ -124,7 +131,7 @@ class _DownloadFallbackExtensionsPageState
|
||||
),
|
||||
if (_extensions.isNotEmpty)
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: EdgeInsets.symmetric(horizontal: 16 + wideInset),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: SettingsGroup(
|
||||
margin: EdgeInsets.zero,
|
||||
@@ -157,7 +164,12 @@ class _DownloadFallbackExtensionsPageState
|
||||
if (_extensions.isNotEmpty)
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 0),
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
16 + wideInset,
|
||||
8,
|
||||
16 + wideInset,
|
||||
0,
|
||||
),
|
||||
child: Text(
|
||||
context.l10n.providerPriorityFallbackExtensionsHint,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/providers/settings_provider.dart';
|
||||
import 'package:spotiflac_android/providers/local_library_provider.dart';
|
||||
import 'package:spotiflac_android/services/platform_bridge.dart';
|
||||
import 'package:spotiflac_android/utils/adaptive_layout.dart';
|
||||
import 'package:spotiflac_android/widgets/settings_group.dart';
|
||||
import 'package:spotiflac_android/widgets/settings_sliver_app_bar.dart';
|
||||
|
||||
@@ -715,7 +716,10 @@ class _LibraryHeroCard extends StatelessWidget {
|
||||
: itemCount + excludedDownloadedCount;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
margin: EdgeInsets.symmetric(
|
||||
horizontal: 16 + wideListInset(context),
|
||||
vertical: 8,
|
||||
),
|
||||
constraints: const BoxConstraints(minHeight: 220),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/providers/settings_provider.dart';
|
||||
import 'package:spotiflac_android/utils/adaptive_layout.dart';
|
||||
import 'package:spotiflac_android/widgets/discard_changes_dialog.dart';
|
||||
import 'package:spotiflac_android/widgets/priority_settings_scaffold.dart';
|
||||
import 'package:spotiflac_android/widgets/reorderable_priority_item.dart';
|
||||
@@ -83,7 +84,9 @@ class _LyricsProviderPriorityPageState
|
||||
),
|
||||
if (_enabledProviders.isNotEmpty)
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 16 + wideListInset(context),
|
||||
),
|
||||
sliver: SliverReorderableList(
|
||||
itemCount: _enabledProviders.length,
|
||||
itemBuilder: (context, index) {
|
||||
@@ -127,7 +130,9 @@ class _LyricsProviderPriorityPageState
|
||||
),
|
||||
if (disabled.isNotEmpty)
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 16 + wideListInset(context),
|
||||
),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
final id = disabled[index];
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/providers/extension_provider.dart';
|
||||
import 'package:spotiflac_android/utils/adaptive_layout.dart';
|
||||
import 'package:spotiflac_android/widgets/discard_changes_dialog.dart';
|
||||
import 'package:spotiflac_android/widgets/priority_settings_scaffold.dart';
|
||||
import 'package:spotiflac_android/widgets/reorderable_priority_item.dart';
|
||||
@@ -57,7 +58,9 @@ class _MetadataProviderPriorityPageState
|
||||
onConfirmDiscard: showDiscardChangesDialog,
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 16 + wideListInset(context),
|
||||
),
|
||||
sliver: SliverReorderableList(
|
||||
itemCount: _providers.length,
|
||||
itemBuilder: (context, index) {
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/providers/extension_provider.dart';
|
||||
import 'package:spotiflac_android/utils/adaptive_layout.dart';
|
||||
import 'package:spotiflac_android/widgets/discard_changes_dialog.dart';
|
||||
import 'package:spotiflac_android/widgets/priority_settings_scaffold.dart';
|
||||
import 'package:spotiflac_android/widgets/reorderable_priority_item.dart';
|
||||
@@ -55,7 +56,9 @@ class _ProviderPriorityPageState extends ConsumerState<ProviderPriorityPage> {
|
||||
onConfirmDiscard: showDiscardChangesDialog,
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 16 + wideListInset(context),
|
||||
),
|
||||
sliver: SliverReorderableList(
|
||||
itemCount: _providers.length,
|
||||
itemBuilder: (context, index) {
|
||||
|
||||
@@ -15,6 +15,7 @@ import 'package:spotiflac_android/screens/settings/cache_management_page.dart';
|
||||
import 'package:spotiflac_android/screens/settings/backup_restore_page.dart';
|
||||
import 'package:spotiflac_android/screens/settings/donate_page.dart';
|
||||
import 'package:spotiflac_android/screens/settings/log_screen.dart';
|
||||
import 'package:spotiflac_android/utils/adaptive_layout.dart';
|
||||
import 'package:spotiflac_android/utils/app_bar_layout.dart';
|
||||
import 'package:spotiflac_android/utils/nav_bar_inset.dart';
|
||||
import 'package:spotiflac_android/widgets/settings_group.dart';
|
||||
@@ -28,6 +29,7 @@ class SettingsTab extends ConsumerWidget {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final topPadding = normalizedHeaderTopPadding(context);
|
||||
final bottomInset = context.navBarBottomInset;
|
||||
final wideInset = wideListInset(context);
|
||||
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
@@ -69,7 +71,12 @@ class SettingsTab extends ConsumerWidget {
|
||||
builder: (context) {
|
||||
final l10n = context.l10n;
|
||||
return SettingsGroup(
|
||||
margin: const EdgeInsets.fromLTRB(16, 16, 16, 4),
|
||||
margin: EdgeInsets.fromLTRB(
|
||||
16 + wideInset,
|
||||
16,
|
||||
16 + wideInset,
|
||||
4,
|
||||
),
|
||||
children: [
|
||||
SettingsItem(
|
||||
icon: Icons.palette_outlined,
|
||||
@@ -103,7 +110,12 @@ class SettingsTab extends ConsumerWidget {
|
||||
builder: (context) {
|
||||
final l10n = context.l10n;
|
||||
return SettingsGroup(
|
||||
margin: const EdgeInsets.fromLTRB(16, 4, 16, 4),
|
||||
margin: EdgeInsets.fromLTRB(
|
||||
16 + wideInset,
|
||||
4,
|
||||
16 + wideInset,
|
||||
4,
|
||||
),
|
||||
children: [
|
||||
SettingsItem(
|
||||
icon: Icons.download_outlined,
|
||||
@@ -145,7 +157,12 @@ class SettingsTab extends ConsumerWidget {
|
||||
builder: (context) {
|
||||
final l10n = context.l10n;
|
||||
return SettingsGroup(
|
||||
margin: const EdgeInsets.fromLTRB(16, 4, 16, 4),
|
||||
margin: EdgeInsets.fromLTRB(
|
||||
16 + wideInset,
|
||||
4,
|
||||
16 + wideInset,
|
||||
4,
|
||||
),
|
||||
children: [
|
||||
SettingsItem(
|
||||
icon: Icons.storage_outlined,
|
||||
@@ -158,8 +175,7 @@ class SettingsTab extends ConsumerWidget {
|
||||
icon: Icons.tune_outlined,
|
||||
title: l10n.settingsApp,
|
||||
subtitle: l10n.settingsAppSubtitle,
|
||||
onTap: () =>
|
||||
_navigateTo(context, const AppSettingsPage()),
|
||||
onTap: () => _navigateTo(context, const AppSettingsPage()),
|
||||
),
|
||||
SettingsItem(
|
||||
icon: Icons.settings_backup_restore,
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:spotiflac_android/providers/settings_provider.dart';
|
||||
import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/l10n/supported_locales.dart';
|
||||
import 'package:spotiflac_android/services/platform_bridge.dart';
|
||||
import 'package:spotiflac_android/utils/adaptive_layout.dart';
|
||||
import 'package:spotiflac_android/utils/file_access.dart';
|
||||
import 'package:spotiflac_android/utils/logger.dart';
|
||||
|
||||
@@ -653,41 +654,46 @@ class _SetupScreenState extends ConsumerState<SetupScreen> {
|
||||
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(minHeight: minContentHeight),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/images/logo-transparent.png',
|
||||
width: logoSize,
|
||||
height: logoSize,
|
||||
color: colorScheme.primary,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
SizedBox(height: titleGap),
|
||||
Text(
|
||||
context.l10n.appName,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.displaySmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.onSurface,
|
||||
fontSize:
|
||||
(Theme.of(context).textTheme.displaySmall?.fontSize ??
|
||||
36) *
|
||||
(1 + ((textScale - 1) * 0.18)),
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: minContentHeight,
|
||||
maxWidth: 560,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/images/logo-transparent.png',
|
||||
width: logoSize,
|
||||
height: logoSize,
|
||||
color: colorScheme.primary,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
SizedBox(height: subtitleGap),
|
||||
Text(
|
||||
context.l10n.setupDownloadInFlac,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
height: 1.5,
|
||||
SizedBox(height: titleGap),
|
||||
Text(
|
||||
context.l10n.appName,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.displaySmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.onSurface,
|
||||
fontSize:
|
||||
(Theme.of(context).textTheme.displaySmall?.fontSize ??
|
||||
36) *
|
||||
(1 + ((textScale - 1) * 0.18)),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
SizedBox(height: subtitleGap),
|
||||
Text(
|
||||
context.l10n.setupDownloadInFlac,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -743,7 +749,12 @@ class _SetupScreenState extends ConsumerState<SetupScreen> {
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 24, 24, 0),
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
24 + wideListInset(context, contentMaxWidth: 560),
|
||||
24,
|
||||
24 + wideListInset(context, contentMaxWidth: 560),
|
||||
0,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
@@ -782,7 +793,12 @@ class _SetupScreenState extends ConsumerState<SetupScreen> {
|
||||
),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.fromLTRB(24, 0, 24, 80),
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
24 + wideListInset(context, contentMaxWidth: 560),
|
||||
0,
|
||||
24 + wideListInset(context, contentMaxWidth: 560),
|
||||
80,
|
||||
),
|
||||
itemCount: languages.length,
|
||||
itemBuilder: (context, index) {
|
||||
final lang = languages[index];
|
||||
@@ -984,40 +1000,49 @@ class _StepLayout extends StatelessWidget {
|
||||
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(minHeight: minContentHeight),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(iconPadding),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
shape: BoxShape.circle,
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: minContentHeight,
|
||||
maxWidth: 560,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(iconPadding),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
size: iconSize,
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
),
|
||||
child: Icon(icon, size: iconSize, color: colorScheme.primary),
|
||||
),
|
||||
SizedBox(height: titleGap),
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.onSurface,
|
||||
SizedBox(height: titleGap),
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: descriptionGap),
|
||||
Text(
|
||||
description,
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
height: 1.5,
|
||||
SizedBox(height: descriptionGap),
|
||||
Text(
|
||||
description,
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
height: 1.5,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: actionGap),
|
||||
child,
|
||||
],
|
||||
SizedBox(height: actionGap),
|
||||
child,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1181,7 +1181,12 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen>
|
||||
),
|
||||
|
||||
SliverToBoxAdapter(
|
||||
child: _buildAnimatedTrackContent(context, ref, colorScheme),
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 720),
|
||||
child: _buildAnimatedTrackContent(context, ref, colorScheme),
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(child: SizedBox(height: bottomInset)),
|
||||
],
|
||||
|
||||
@@ -244,21 +244,26 @@ class _TutorialScreenState extends ConsumerState<TutorialScreen> {
|
||||
}),
|
||||
),
|
||||
SizedBox(height: bottomGap),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: actionButtonHeight,
|
||||
child: FilledButton(
|
||||
onPressed: _nextPage,
|
||||
style: FilledButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
isLastPage ? l10n.setupGetStarted : l10n.setupNext,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 400),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
height: actionButtonHeight,
|
||||
child: FilledButton(
|
||||
onPressed: _nextPage,
|
||||
style: FilledButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
isLastPage ? l10n.setupGetStarted : l10n.setupNext,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -712,58 +717,65 @@ class _TutorialPage extends StatelessWidget {
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
physics: const BouncingScrollPhysics(),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(height: topGap),
|
||||
AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
curve: Curves.easeOutBack,
|
||||
transform: Matrix4.translationValues(0, isActive ? 0 : -20, 0),
|
||||
padding: EdgeInsets.all(iconPadding),
|
||||
decoration: BoxDecoration(
|
||||
color: (iconColor ?? colorScheme.primary).withValues(alpha: 0.15),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
size: iconSize,
|
||||
color: iconColor ?? colorScheme.primary,
|
||||
),
|
||||
),
|
||||
SizedBox(height: iconTextGap),
|
||||
AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
opacity: isActive ? 1.0 : 0.0,
|
||||
curve: Curves.easeOut,
|
||||
child: Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.onSurface,
|
||||
letterSpacing: -0.5,
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 560),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(height: topGap),
|
||||
AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
curve: Curves.easeOutBack,
|
||||
transform: Matrix4.translationValues(0, isActive ? 0 : -20, 0),
|
||||
padding: EdgeInsets.all(iconPadding),
|
||||
decoration: BoxDecoration(
|
||||
color: (iconColor ?? colorScheme.primary).withValues(
|
||||
alpha: 0.15,
|
||||
),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
size: iconSize,
|
||||
color: iconColor ?? colorScheme.primary,
|
||||
),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
SizedBox(height: descriptionGap),
|
||||
AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
opacity: isActive ? 1.0 : 0.0,
|
||||
curve: Curves.easeOut,
|
||||
child: Text(
|
||||
description,
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
height: 1.5,
|
||||
fontSize: 16 * (1 + ((textScale - 1) * 0.1)),
|
||||
SizedBox(height: iconTextGap),
|
||||
AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
opacity: isActive ? 1.0 : 0.0,
|
||||
curve: Curves.easeOut,
|
||||
child: Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.onSurface,
|
||||
letterSpacing: -0.5,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: descriptionGap),
|
||||
AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
opacity: isActive ? 1.0 : 0.0,
|
||||
curve: Curves.easeOut,
|
||||
child: Text(
|
||||
description,
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
height: 1.5,
|
||||
fontSize: 16 * (1 + ((textScale - 1) * 0.1)),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
SizedBox(height: contentGap),
|
||||
content, // The content itself now handles its own internal animations
|
||||
SizedBox(height: bottomGap),
|
||||
],
|
||||
),
|
||||
SizedBox(height: contentGap),
|
||||
content, // The content itself now handles its own internal animations
|
||||
SizedBox(height: bottomGap),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
/// Horizontal inset that centers content of [maxWidth] at [contentMaxWidth];
|
||||
/// zero once it fits, so rows stop stretching across a wide surface. Prefer
|
||||
/// this constraint-based form when the widget may live inside an already
|
||||
/// clamped box (e.g. a bottom sheet), where screen width would over-inset.
|
||||
double wideInsetForWidth(double maxWidth, {double contentMaxWidth = 720}) =>
|
||||
maxWidth > contentMaxWidth ? (maxWidth - contentMaxWidth) / 2 : 0;
|
||||
|
||||
/// Horizontal inset that centers full-width list content at [contentMaxWidth]
|
||||
/// on tablets/landscape; zero on phones, so rows stop stretching across the
|
||||
/// whole screen.
|
||||
double wideListInset(BuildContext context, {double contentMaxWidth = 720}) {
|
||||
final width = MediaQuery.sizeOf(context).width;
|
||||
return width > contentMaxWidth ? (width - contentMaxWidth) / 2 : 0;
|
||||
}
|
||||
double wideListInset(BuildContext context, {double contentMaxWidth = 720}) =>
|
||||
wideInsetForWidth(
|
||||
MediaQuery.sizeOf(context).width,
|
||||
contentMaxWidth: contentMaxWidth,
|
||||
);
|
||||
|
||||
@@ -101,7 +101,7 @@ class AppAnnouncementDialog extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
content: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 260),
|
||||
constraints: const BoxConstraints(maxHeight: 260, maxWidth: 480),
|
||||
child: SingleChildScrollView(
|
||||
child: Text(
|
||||
announcement.message,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/utils/adaptive_layout.dart';
|
||||
import 'package:spotiflac_android/widgets/settings_sliver_app_bar.dart';
|
||||
|
||||
class PrioritySettingsScaffold extends StatelessWidget {
|
||||
@@ -40,6 +41,7 @@ class PrioritySettingsScaffold extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final wideInset = wideListInset(context);
|
||||
|
||||
return PopScope(
|
||||
canPop: !hasChanges,
|
||||
@@ -70,7 +72,9 @@ class PrioritySettingsScaffold extends StatelessWidget {
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: descriptionPadding,
|
||||
padding: descriptionPadding.add(
|
||||
EdgeInsets.symmetric(horizontal: wideInset),
|
||||
),
|
||||
child: Text(
|
||||
description,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
@@ -82,7 +86,12 @@ class PrioritySettingsScaffold extends StatelessWidget {
|
||||
...slivers,
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
16 + wideInset,
|
||||
16,
|
||||
16 + wideInset,
|
||||
16,
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:spotiflac_android/utils/adaptive_layout.dart';
|
||||
|
||||
/// Background fill for grouped cards, matching the Settings group look. Blends a
|
||||
/// translucent overlay over the surface so it stays visible on AMOLED (pure
|
||||
@@ -28,19 +29,42 @@ class SettingsGroup extends StatelessWidget {
|
||||
final cardColor = settingsGroupColor(context);
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
return Container(
|
||||
margin: margin ?? const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: cardColor,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: colorScheme.outlineVariant.withValues(alpha: 0.5),
|
||||
),
|
||||
final decoration = BoxDecoration(
|
||||
color: cardColor,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: colorScheme.outlineVariant.withValues(alpha: 0.5),
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Column(mainAxisSize: MainAxisSize.min, children: children),
|
||||
);
|
||||
final child = Material(
|
||||
color: Colors.transparent,
|
||||
child: Column(mainAxisSize: MainAxisSize.min, children: children),
|
||||
);
|
||||
|
||||
// Explicit caller margin wins as-is. Otherwise center on wide surfaces
|
||||
// using the incoming constraint (not screen width) so groups nested in an
|
||||
// already clamped box, e.g. a bottom sheet, are not over-inset.
|
||||
if (margin != null) {
|
||||
return Container(
|
||||
margin: margin,
|
||||
decoration: decoration,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) => Container(
|
||||
margin: EdgeInsets.symmetric(
|
||||
horizontal:
|
||||
16 +
|
||||
(constraints.hasBoundedWidth
|
||||
? wideInsetForWidth(constraints.maxWidth)
|
||||
: 0),
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: decoration,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user