fix(android): back button and safe area issues

This commit is contained in:
Kingkor Roy Tirtho 2025-01-31 23:07:37 +06:00
parent 6ddf6b9cce
commit d4504722d8
21 changed files with 1559 additions and 1516 deletions

View File

@ -1,3 +1,4 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
@ -73,6 +74,10 @@ class TitleBar extends HookConsumerWidget implements PreferredSizeWidget {
final hasFullscreen =
MediaQuery.sizeOf(context).width == constraints.maxWidth;
final canPop = leading.isEmpty &&
automaticallyImplyLeading &&
(Navigator.canPop(context) || context.watchRouter.canPop());
return GestureDetector(
onHorizontalDragStart: (_) => onDrag(ref),
onVerticalDragStart: (_) => onDrag(ref),
@ -94,13 +99,7 @@ class TitleBar extends HookConsumerWidget implements PreferredSizeWidget {
}
},
child: AppBar(
leading: leading.isEmpty &&
automaticallyImplyLeading &&
Navigator.canPop(context)
? [
const BackButton(),
]
: leading,
leading: canPop ? [const BackButton()] : leading,
trailing: [
...trailing,
Align(

View File

@ -23,12 +23,11 @@ class ConnectPage extends HookConsumerWidget {
final connectClientsNotifier = ref.read(connectClientsProvider.notifier);
final discoveredDevices = connectClients.asData?.value.services;
return Scaffold(
return SafeArea(
bottom: false,
child: Scaffold(
headers: [
TitleBar(
automaticallyImplyLeading: true,
title: Text(context.l10n.devices),
)
TitleBar(title: Text(context.l10n.devices)),
],
child: Padding(
padding: const EdgeInsets.all(10.0),
@ -84,6 +83,7 @@ class ConnectPage extends HookConsumerWidget {
],
),
),
),
);
}
}

View File

@ -75,7 +75,6 @@ class ConnectControlPage extends HookConsumerWidget {
headers: [
TitleBar(
title: Text(resolvedService!.name),
automaticallyImplyLeading: true,
)
],
child: LayoutBuilder(builder: (context, constrains) {

View File

@ -28,13 +28,14 @@ class HomeFeedSectionPage extends HookConsumerWidget {
final controller = useScrollController();
final isArtist = section.items.every((item) => item.artist != null);
return Skeletonizer(
return SafeArea(
bottom: false,
child: Skeletonizer(
enabled: homeFeedSection.isLoading,
child: Scaffold(
headers: [
TitleBar(
title: Text(section.title ?? ""),
automaticallyImplyLeading: true,
)
],
child: Padding(
@ -44,7 +45,8 @@ class HomeFeedSectionPage extends HookConsumerWidget {
slivers: [
if (isArtist)
SliverGrid.builder(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
gridDelegate:
const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200,
mainAxisExtent: 250,
crossAxisSpacing: 8,
@ -93,6 +95,7 @@ class HomeFeedSectionPage extends HookConsumerWidget {
),
),
),
),
);
}
}

View File

@ -45,7 +45,8 @@ class GenrePlaylistsPage extends HookConsumerWidget {
automaticSystemUiAdjustment: false,
);
return Scaffold(
return SafeArea(
child: Scaffold(
headers: [
if (kIsDesktop)
const TitleBar(
@ -74,7 +75,9 @@ class GenrePlaylistsPage extends HookConsumerWidget {
child: CustomScrollView(
controller: scrollController,
slivers: [
SliverAppBar(
SliverSafeArea(
bottom: false,
sliver: SliverAppBar(
automaticallyImplyLeading: false,
leading: kIsMobile ? const BackButton() : null,
expandedHeight: mediaQuery.mdAndDown ? 200 : 150,
@ -110,6 +113,7 @@ class GenrePlaylistsPage extends HookConsumerWidget {
collapseMode: CollapseMode.parallax,
),
),
),
const SliverGap(20),
SliverSafeArea(
top: false,
@ -123,8 +127,8 @@ class GenrePlaylistsPage extends HookConsumerWidget {
isLoading: playlists.isLoading,
hasMore: playlists.asData?.value.hasMore == true,
onRequestMore: playlistsNotifier.fetchMore,
listItemBuilder: (context, index) =>
PlaylistCard.tile(playlists.asData!.value.items[index]),
listItemBuilder: (context, index) => PlaylistCard.tile(
playlists.asData!.value.items[index]),
gridItemBuilder: (context, index) =>
PlaylistCard(playlists.asData!.value.items[index]),
),
@ -135,6 +139,7 @@ class GenrePlaylistsPage extends HookConsumerWidget {
),
),
),
),
);
}
}

View File

@ -32,7 +32,6 @@ class GenrePage extends HookConsumerWidget {
headers: [
TitleBar(
title: Text(context.l10n.explore_genres),
automaticallyImplyLeading: true,
)
],
child: GridView.builder(

View File

@ -31,6 +31,7 @@ class LastFMLoginPage extends HookConsumerWidget {
return Scaffold(
headers: const [
SafeArea(
bottom: false,
child: TitleBar(
leading: [BackButton()],
),
@ -39,7 +40,8 @@ class LastFMLoginPage extends HookConsumerWidget {
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
Flexible(
child: Container(
constraints: const BoxConstraints(maxWidth: 400),
alignment: Alignment.center,
padding: const EdgeInsets.all(16),
@ -139,6 +141,7 @@ class LastFMLoginPage extends HookConsumerWidget {
),
),
),
),
],
),
);

View File

@ -256,7 +256,9 @@ class PlaylistGeneratorPage extends HookConsumerWidget {
final controller = useScrollController();
return Scaffold(
return SafeArea(
bottom: false,
child: Scaffold(
headers: [
TitleBar(
leading: const [BackButton()],
@ -308,8 +310,8 @@ class PlaylistGeneratorPage extends HookConsumerWidget {
),
Expanded(
child: Slider(
value:
SliderValue.single(value.toDouble()),
value: SliderValue.single(
value.toDouble()),
min: 10,
max: 100,
divisions: 9,
@ -655,8 +657,9 @@ class PlaylistGeneratorPage extends HookConsumerWidget {
seedArtists: artists.value
.map((a) => a.id!)
.toList(),
seedTracks:
tracks.value.map((t) => t.id!).toList(),
seedTracks: tracks.value
.map((t) => t.id!)
.toList(),
seedGenres: genres.value,
limit: limit.value,
max: max.value,
@ -680,6 +683,7 @@ class PlaylistGeneratorPage extends HookConsumerWidget {
),
),
),
),
);
}
}

View File

@ -48,7 +48,9 @@ class PlaylistGenerateResultPage extends HookConsumerWidget {
final isAllTrackSelected = selectedTracks.value.length ==
(generatedPlaylist.asData?.value.length ?? 0);
return Scaffold(
return SafeArea(
bottom: false,
child: Scaffold(
headers: const [
TitleBar(leading: [BackButton()])
],
@ -101,7 +103,8 @@ class PlaylistGenerateResultPage extends HookConsumerWidget {
: () async {
await playlistNotifier.addTracks(
generatedPlaylist.asData!.value.where(
(e) => selectedTracks.value.contains(e.id!),
(e) =>
selectedTracks.value.contains(e.id!),
),
);
if (context.mounted) {
@ -152,11 +155,13 @@ class PlaylistGenerateResultPage extends HookConsumerWidget {
: () async {
final hasAdded = await showDialog<bool>(
context: context,
builder: (context) => PlaylistAddTrackDialog(
builder: (context) =>
PlaylistAddTrackDialog(
openFromPlaylist: null,
tracks: selectedTracks.value
.map(
(e) => generatedPlaylist.asData!.value
(e) => generatedPlaylist
.asData!.value
.firstWhere(
(element) => element.id == e,
),
@ -244,7 +249,8 @@ class PlaylistGenerateResultPage extends HookConsumerWidget {
child: GestureDetector(
onTap: () {
selectedTracks.value.contains(track.id)
? selectedTracks.value.remove(track.id)
? selectedTracks.value
.remove(track.id)
: selectedTracks.value.add(track.id!);
selectedTracks.value =
selectedTracks.value.toList();
@ -260,6 +266,7 @@ class PlaylistGenerateResultPage extends HookConsumerWidget {
],
),
),
),
);
}
}

View File

@ -27,7 +27,9 @@ class WebViewLoginPage extends HookConsumerWidget {
);
}
return Scaffold(
return SafeArea(
bottom: false,
child: Scaffold(
headers: const [
TitleBar(
leading: [BackButton(color: Colors.white)],
@ -72,6 +74,7 @@ class WebViewLoginPage extends HookConsumerWidget {
}
},
),
),
);
}
}

View File

@ -44,7 +44,6 @@ class ProfilePage extends HookConsumerWidget {
headers: [
TitleBar(
title: Text(context.l10n.profile),
automaticallyImplyLeading: true,
)
],
child: Skeletonizer(

View File

@ -31,7 +31,9 @@ class AboutSpotubePage extends HookConsumerWidget {
const colon = TableCell(child: Text(":"));
return Scaffold(
return SafeArea(
bottom: false,
child: Scaffold(
headers: [
TitleBar(
leading: const [BackButton()],
@ -91,8 +93,8 @@ class AboutSpotubePage extends HookConsumerWidget {
TableCell(child: Text(context.l10n.build_number)),
colon,
TableCell(
child: Text(
packageInfo.buildNumber.replaceAll(".", " ")),
child: Text(packageInfo.buildNumber
.replaceAll(".", " ")),
)
],
),
@ -191,6 +193,7 @@ class AboutSpotubePage extends HookConsumerWidget {
),
),
),
),
);
}
}

View File

@ -47,7 +47,9 @@ class BlackListPage extends HookConsumerWidget {
[blacklist, searchText.value],
);
return Scaffold(
return SafeArea(
bottom: false,
child: Scaffold(
headers: [
TitleBar(
title: Text(context.l10n.blacklist),
@ -81,9 +83,8 @@ class BlackListPage extends HookConsumerWidget {
trailing: IconButton.ghost(
icon: Icon(SpotubeIcons.trash, color: Colors.red[400]),
onPressed: () {
ref
.read(blacklistProvider.notifier)
.remove(filteredBlacklist.elementAt(index).elementId);
ref.read(blacklistProvider.notifier).remove(
filteredBlacklist.elementAt(index).elementId);
},
),
);
@ -92,6 +93,7 @@ class BlackListPage extends HookConsumerWidget {
),
],
),
),
);
}
}

View File

@ -34,7 +34,6 @@ class SettingsPage extends HookConsumerWidget {
headers: [
TitleBar(
title: Text(context.l10n.settings),
automaticallyImplyLeading: true,
)
],
child: Scrollbar(

View File

@ -26,10 +26,11 @@ class StatsAlbumsPage extends HookConsumerWidget {
final albumsData = topAlbums.asData?.value.items ?? [];
return Scaffold(
return SafeArea(
bottom: false,
child: Scaffold(
headers: [
TitleBar(
automaticallyImplyLeading: true,
title: Text(context.l10n.albums),
)
],
@ -53,6 +54,7 @@ class StatsAlbumsPage extends HookConsumerWidget {
},
),
),
),
);
}
}

View File

@ -29,10 +29,11 @@ class StatsArtistsPage extends HookConsumerWidget {
final artistsData = useMemoized(
() => topTracks.asData?.value.artists ?? [], [topTracks.asData?.value]);
return Scaffold(
return SafeArea(
bottom: false,
child: Scaffold(
headers: [
TitleBar(
automaticallyImplyLeading: true,
title: Text(context.l10n.artists),
)
],
@ -56,6 +57,7 @@ class StatsArtistsPage extends HookConsumerWidget {
},
),
),
),
);
}
}

View File

@ -50,10 +50,11 @@ class StatsStreamFeesPage extends HookConsumerWidget {
HistoryDuration.allTime: context.l10n.all_time,
};
return Scaffold(
return SafeArea(
bottom: false,
child: Scaffold(
headers: [
TitleBar(
automaticallyImplyLeading: true,
title: Text(context.l10n.streaming_fees_hypothetical),
)
],
@ -86,7 +87,8 @@ class StatsStreamFeesPage extends HookConsumerWidget {
if (value == null) return;
duration.value = value;
},
itemBuilder: (context, value) => Text(translations[value]!),
itemBuilder: (context, value) =>
Text(translations[value]!),
constraints: const BoxConstraints(maxWidth: 150),
popupWidthConstraint: PopoverConstraint.anchorMaxSize,
children: [
@ -109,7 +111,8 @@ class StatsStreamFeesPage extends HookConsumerWidget {
await topTracksNotifier.fetchMore();
},
hasError: topTracks.hasError,
isLoading: topTracks.isLoading && !topTracks.isLoadingNextPage,
isLoading:
topTracks.isLoading && !topTracks.isLoadingNextPage,
hasReachedMax: topTracks.asData?.value.hasMore ?? true,
itemCount: artistsData.length,
itemBuilder: (context, index) {
@ -124,6 +127,7 @@ class StatsStreamFeesPage extends HookConsumerWidget {
),
],
),
),
);
}
}

View File

@ -28,11 +28,12 @@ class StatsMinutesPage extends HookConsumerWidget {
final tracksData = topTracks.asData?.value.items ?? [];
return Scaffold(
return SafeArea(
bottom: false,
child: Scaffold(
headers: [
TitleBar(
title: Text(context.l10n.minutes_listened),
automaticallyImplyLeading: true,
)
],
child: Skeletonizer(
@ -58,6 +59,7 @@ class StatsMinutesPage extends HookConsumerWidget {
},
),
),
),
);
}
}

View File

@ -27,10 +27,11 @@ class StatsPlaylistsPage extends HookConsumerWidget {
final playlistsData = topPlaylists.asData?.value.items ?? [];
return Scaffold(
return SafeArea(
bottom: false,
child: Scaffold(
headers: [
TitleBar(
automaticallyImplyLeading: true,
title: Text(context.l10n.playlists),
)
],
@ -41,7 +42,8 @@ class StatsPlaylistsPage extends HookConsumerWidget {
await topPlaylistsNotifier.fetchMore();
},
hasError: topPlaylists.hasError,
isLoading: topPlaylists.isLoading && !topPlaylists.isLoadingNextPage,
isLoading:
topPlaylists.isLoading && !topPlaylists.isLoadingNextPage,
hasReachedMax: topPlaylists.asData?.value.hasMore ?? true,
itemCount: playlistsData.length,
itemBuilder: (context, index) {
@ -49,13 +51,14 @@ class StatsPlaylistsPage extends HookConsumerWidget {
return StatsPlaylistItem(
playlist: playlist.playlist,
info: Text(
context.l10n
.count_plays(compactNumberFormatter.format(playlist.count)),
context.l10n.count_plays(
compactNumberFormatter.format(playlist.count)),
),
);
},
),
),
),
);
}
}

View File

@ -28,11 +28,12 @@ class StatsStreamsPage extends HookConsumerWidget {
final tracksData = topTracks.asData?.value.items ?? [];
return Scaffold(
return SafeArea(
bottom: false,
child: Scaffold(
headers: [
TitleBar(
title: Text(context.l10n.streamed_songs),
automaticallyImplyLeading: true,
)
],
child: Skeletonizer(
@ -58,6 +59,7 @@ class StatsStreamsPage extends HookConsumerWidget {
},
),
),
),
);
}
}

View File

@ -54,10 +54,11 @@ class TrackPage extends HookConsumerWidget {
}
}
return Scaffold(
return SafeArea(
bottom: false,
child: Scaffold(
headers: const [
TitleBar(
automaticallyImplyLeading: true,
backgroundColor: Colors.transparent,
surfaceBlur: 0,
)
@ -121,7 +122,8 @@ class TrackPage extends HookConsumerWidget {
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
padding:
const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
crossAxisAlignment: mediaQuery.smAndDown
? CrossAxisAlignment.center
@ -246,6 +248,7 @@ class TrackPage extends HookConsumerWidget {
),
],
),
),
);
}
}