refactor: platform alert dialog for all dialogs

This commit is contained in:
Kingkor Roy Tirtho 2022-11-03 09:12:43 +06:00
parent e086b520e7
commit c201624f99
15 changed files with 194 additions and 182 deletions

View File

@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:platform_ui/platform_ui.dart';
import 'package:spotube/components/Home/Sidebar.dart'; import 'package:spotube/components/Home/Sidebar.dart';
import 'package:spotube/components/Home/SpotubeNavigationBar.dart'; import 'package:spotube/components/Home/SpotubeNavigationBar.dart';
import 'package:spotube/components/Player/Player.dart'; import 'package:spotube/components/Player/Player.dart';
@ -35,8 +36,8 @@ class Shell extends HookConsumerWidget {
useEffect(() { useEffect(() {
downloader.onFileExists = (track) async { downloader.onFileExists = (track) async {
if (!isMounted()) return false; if (!isMounted()) return false;
return await showDialog<bool>( return await showPlatformAlertDialog<bool>(
context: context, context,
builder: (context) => ReplaceDownloadedFileDialog( builder: (context) => ReplaceDownloadedFileDialog(
track: track, track: track,
), ),

View File

@ -31,14 +31,14 @@ class UserDownloads extends HookConsumerWidget {
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
PlatformFilledButton( PlatformFilledButton(
style: ElevatedButton.styleFrom( style: ButtonStyle(
backgroundColor: Colors.red[50], backgroundColor: MaterialStatePropertyAll(Colors.red[50]),
foregroundColor: Colors.red[400], foregroundColor: MaterialStatePropertyAll(Colors.red[400]),
), ),
onPressed: downloader.currentlyRunning > 0 onPressed: downloader.currentlyRunning > 0
? downloader.cancelAll ? downloader.cancelAll
: null, : null,
macOSIsSecondary: true, isSecondary: true,
child: const PlatformText("Cancel All"), child: const PlatformText("Cancel All"),
), ),
], ],
@ -50,7 +50,7 @@ class UserDownloads extends HookConsumerWidget {
itemBuilder: (context, index) { itemBuilder: (context, index) {
final track = downloader.inQueue.elementAt(index); final track = downloader.inQueue.elementAt(index);
return PlatformListTile( return PlatformListTile(
title: Text(track.name!), title: Text(track.name ?? ''),
leading: Padding( leading: Padding(
padding: const EdgeInsets.symmetric(horizontal: 5), padding: const EdgeInsets.symmetric(horizontal: 5),
child: ClipRRect( child: ClipRRect(

View File

@ -16,15 +16,18 @@ class LyricDelayAdjustDialog extends HookConsumerWidget {
double getValue() => double getValue() =>
double.tryParse(controller.text.replaceAll("ms", "")) ?? 0; double.tryParse(controller.text.replaceAll("ms", "")) ?? 0;
return AlertDialog( return PlatformAlertDialog(
title: const Center(child: Text("Adjust Lyrics Delay")), title: const Center(child: Text("Adjust Lyrics Delay")),
actions: [ secondaryActions: [
PlatformFilledButton( PlatformFilledButton(
child: const Text("Cancel"), isSecondary: true,
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: const Text("Cancel"),
), ),
],
primaryActions: [
PlatformFilledButton( PlatformFilledButton(
child: const Text("Done"), child: const Text("Done"),
onPressed: () { onPressed: () {

View File

@ -90,8 +90,8 @@ class SyncedLyrics extends HookConsumerWidget {
tooltip: "Lyrics Delay", tooltip: "Lyrics Delay",
icon: const Icon(Icons.av_timer_rounded), icon: const Icon(Icons.av_timer_rounded),
onPressed: () async { onPressed: () async {
final delay = await showDialog( final delay = await showPlatformAlertDialog(
context: context, context,
builder: (context) => const LyricDelayAdjustDialog(), builder: (context) => const LyricDelayAdjustDialog(),
); );
if (delay != null) { if (delay != null) {

View File

@ -15,8 +15,8 @@ class PlaylistCreateDialog extends HookConsumerWidget {
return PlatformTextButton( return PlatformTextButton(
onPressed: () { onPressed: () {
showDialog( showPlatformAlertDialog(
context: context, context,
builder: (context) { builder: (context) {
return HookBuilder(builder: (context) { return HookBuilder(builder: (context) {
final playlistName = useTextEditingController(); final playlistName = useTextEditingController();
@ -24,13 +24,9 @@ class PlaylistCreateDialog extends HookConsumerWidget {
final public = useState(false); final public = useState(false);
final collaborative = useState(false); final collaborative = useState(false);
return AlertDialog( return PlatformAlertDialog(
title: const Text("Create a Playlist"), title: const Text("Create a Playlist"),
actions: [ primaryActions: [
PlatformTextButton(
child: const Text("Cancel"),
onPressed: () => Navigator.of(context).pop(),
),
PlatformFilledButton( PlatformFilledButton(
child: const Text("Create"), child: const Text("Create"),
onPressed: () async { onPressed: () async {
@ -53,6 +49,13 @@ class PlaylistCreateDialog extends HookConsumerWidget {
}, },
) )
], ],
secondaryActions: [
PlatformFilledButton(
isSecondary: true,
onPressed: () => Navigator.of(context).pop(),
child: const Text("Cancel"),
),
],
content: Container( content: Container(
width: MediaQuery.of(context).size.width, width: MediaQuery.of(context).size.width,
constraints: const BoxConstraints(maxWidth: 500), constraints: const BoxConstraints(maxWidth: 500),

View File

@ -66,15 +66,9 @@ class ColorSchemePickerDialog extends HookConsumerWidget {
}, },
).key); ).key);
return AlertDialog( return PlatformAlertDialog(
title: Text("Pick ${schemeType.name} color scheme"), title: Text("Pick ${schemeType.name} color scheme"),
actions: [ primaryActions: [
PlatformTextButton(
child: const Text("Cancel"),
onPressed: () {
Navigator.pop(context);
},
),
PlatformFilledButton( PlatformFilledButton(
child: const Text("Save"), child: const Text("Save"),
onPressed: () { onPressed: () {
@ -91,6 +85,15 @@ class ColorSchemePickerDialog extends HookConsumerWidget {
}, },
) )
], ],
secondaryActions: [
PlatformFilledButton(
isSecondary: true,
child: const Text("Cancel"),
onPressed: () {
Navigator.pop(context);
},
),
],
content: SizedBox( content: SizedBox(
height: 200, height: 200,
width: 400, width: 400,

View File

@ -28,9 +28,7 @@ class Settings extends HookConsumerWidget {
final Auth auth = ref.watch(authProvider); final Auth auth = ref.watch(authProvider);
final pickColorScheme = useCallback((ColorSchemeType schemeType) { final pickColorScheme = useCallback((ColorSchemeType schemeType) {
return () => showDialog( return () => showPlatformAlertDialog(context, builder: (context) {
context: context,
builder: (context) {
return ColorSchemePickerDialog( return ColorSchemePickerDialog(
schemeType: schemeType, schemeType: schemeType,
); );

View File

@ -34,11 +34,11 @@ class AdaptiveListTile extends HookWidget {
onTap: breakpoint.isLessThan(breakOn) onTap: breakpoint.isLessThan(breakOn)
? () { ? () {
onTap?.call(); onTap?.call();
showDialog( showPlatformAlertDialog(
context: context, context,
builder: (context) { builder: (context) {
return StatefulBuilder(builder: (context, update) { return StatefulBuilder(builder: (context, update) {
return AlertDialog( return PlatformAlertDialog(
title: title != null title: title != null
? Row( ? Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
@ -50,7 +50,7 @@ class AdaptiveListTile extends HookWidget {
Flexible(child: title!), Flexible(child: title!),
], ],
) )
: null, : Container(),
content: trailing?.call(context, update), content: trailing?.call(context, update),
); );
}); });

View File

@ -7,22 +7,24 @@ class DownloadConfirmationDialog extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AlertDialog( return PlatformAlertDialog(
contentPadding: const EdgeInsets.all(15), title: Padding(
title: Row( padding: const EdgeInsets.all(15),
children: const [ child: Row(
Text("Are you sure?"), children: const [
SizedBox(width: 10), Text("Are you sure?"),
UniversalImage( SizedBox(width: 10),
path: UniversalImage(
"https://c.tenor.com/kHcmsxlKHEAAAAAM/rock-one-eyebrow-raised-rock-staring.gif", path:
height: 40, "https://c.tenor.com/kHcmsxlKHEAAAAAM/rock-one-eyebrow-raised-rock-staring.gif",
width: 40, height: 40,
) width: 40,
], )
],
),
), ),
content: ConstrainedBox( content: Padding(
constraints: const BoxConstraints(maxWidth: 400), padding: const EdgeInsets.all(15),
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -57,19 +59,22 @@ class DownloadConfirmationDialog extends StatelessWidget {
), ),
), ),
), ),
actions: [ primaryActions: [
PlatformFilledButton( PlatformFilledButton(
style: const ButtonStyle(
foregroundColor: MaterialStatePropertyAll(Colors.white),
backgroundColor: MaterialStatePropertyAll(Colors.red),
),
onPressed: () => Navigator.of(context).pop(true),
child: const Text("Accept"),
)
],
secondaryActions: [
PlatformFilledButton(
isSecondary: true,
child: const Text("Decline"), child: const Text("Decline"),
onPressed: () => Navigator.of(context).pop(false), onPressed: () => Navigator.of(context).pop(false),
), ),
PlatformFilledButton(
onPressed: () => Navigator.of(context).pop(true),
style: ElevatedButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: Colors.red,
),
child: const Text("Accept"),
)
], ],
); );
} }

View File

@ -14,7 +14,7 @@ class ReplaceDownloadedFileDialog extends ConsumerWidget {
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final groupValue = ref.watch(replaceDownloadedFileState); final groupValue = ref.watch(replaceDownloadedFileState);
return AlertDialog( return PlatformAlertDialog(
title: Text("Track ${track.name} Already Exists"), title: Text("Track ${track.name} Already Exists"),
content: Column( content: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -48,20 +48,23 @@ class ReplaceDownloadedFileDialog extends ConsumerWidget {
), ),
], ],
), ),
actions: [ primaryActions: [
PlatformTextButton( PlatformFilledButton(
child: const Text("No"),
onPressed: () {
Navigator.pop(context, false);
},
),
PlatformTextButton(
child: const Text("Yes"), child: const Text("Yes"),
onPressed: () { onPressed: () {
Navigator.pop(context, true); Navigator.pop(context, true);
}, },
) )
], ],
secondaryActions: [
PlatformFilledButton(
isSecondary: true,
child: const Text("No"),
onPressed: () {
Navigator.pop(context, false);
},
),
],
); );
} }
} }

View File

@ -84,81 +84,79 @@ class TrackTile extends HookConsumerWidget {
} }
Future<void> actionAddToPlaylist() async { Future<void> actionAddToPlaylist() async {
showDialog( showPlatformAlertDialog(context, builder: (context) {
context: context, return FutureBuilder<Iterable<PlaylistSimple>>(
builder: (context) { future: spotify.playlists.me.all().then((playlists) async {
return FutureBuilder<Iterable<PlaylistSimple>>( final me = await spotify.me.get();
future: spotify.playlists.me.all().then((playlists) async { return playlists.where((playlist) =>
final me = await spotify.me.get(); playlist.owner?.id != null && playlist.owner!.id == me.id);
return playlists.where((playlist) => }),
playlist.owner?.id != null && builder: (context, snapshot) {
playlist.owner!.id == me.id); return HookBuilder(builder: (context) {
}), final playlistsCheck = useState(<String, bool>{});
builder: (context, snapshot) { return PlatformAlertDialog(
return HookBuilder(builder: (context) { title: PlatformText(
final playlistsCheck = useState(<String, bool>{}); "Add `${track.value.name}` to following Playlists",
return AlertDialog( style: const TextStyle(
title: PlatformText( fontSize: 18,
"Add `${track.value.name}` to following Playlists"), fontWeight: FontWeight.bold,
titleTextStyle: ),
Theme.of(context).textTheme.bodyText1?.copyWith( ),
fontSize: 18, secondaryActions: [
fontWeight: FontWeight.bold, PlatformFilledButton(
), isSecondary: true,
actions: [ child: const PlatformText("Cancel"),
PlatformTextButton( onPressed: () => Navigator.pop(context),
child: const PlatformText("Cancel"), ),
onPressed: () => Navigator.pop(context), ],
), primaryActions: [
PlatformFilledButton( PlatformFilledButton(
child: const PlatformText("Add"), child: const PlatformText("Add"),
onPressed: () async { onPressed: () async {
final selectedPlaylists = playlistsCheck final selectedPlaylists = playlistsCheck.value.entries
.value.entries .where((entry) => entry.value)
.where((entry) => entry.value) .map((entry) => entry.key);
.map((entry) => entry.key);
await Future.wait( await Future.wait(
selectedPlaylists.map( selectedPlaylists.map(
(playlistId) => spotify.playlists (playlistId) => spotify.playlists
.addTrack(track.value.uri!, playlistId), .addTrack(track.value.uri!, playlistId),
), ),
).then((_) => Navigator.pop(context)); ).then((_) => Navigator.pop(context));
}, },
) )
], ],
content: SizedBox( content: SizedBox(
height: 300, height: 300,
width: 300, width: 300,
child: !snapshot.hasData child: !snapshot.hasData
? const Center( ? const Center(
child: PlatformCircularProgressIndicator()) child: PlatformCircularProgressIndicator())
: ListView.builder( : ListView.builder(
shrinkWrap: true, shrinkWrap: true,
itemCount: snapshot.data!.length, itemCount: snapshot.data!.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final playlist = final playlist = snapshot.data!.elementAt(index);
snapshot.data!.elementAt(index); return CheckboxListTile(
return CheckboxListTile( title: PlatformText(playlist.name!),
title: PlatformText(playlist.name!), controlAffinity:
controlAffinity: ListTileControlAffinity.leading,
ListTileControlAffinity.leading, value:
value: playlistsCheck.value[playlist.id] ?? playlistsCheck.value[playlist.id] ?? false,
false, onChanged: (val) {
onChanged: (val) { playlistsCheck.value = {
playlistsCheck.value = { ...playlistsCheck.value,
...playlistsCheck.value, playlist.id!: val == true
playlist.id!: val == true };
};
},
);
}, },
), );
), },
); ),
}); ),
}); );
}); });
});
});
} }
final String thumbnailUrl = TypeConversionUtils.image_X_UrlString( final String thumbnailUrl = TypeConversionUtils.image_X_UrlString(

View File

@ -146,11 +146,10 @@ class TracksTableView extends HookConsumerWidget {
switch (action) { switch (action) {
case "download": case "download":
{ {
final isConfirmed = await showDialog( final isConfirmed = await showPlatformAlertDialog(
context: context, context, builder: (context) {
builder: (context) { return const DownloadConfirmationDialog();
return const DownloadConfirmationDialog(); });
});
if (isConfirmed != true) return; if (isConfirmed != true) return;
for (final selectedTrack in selectedTracks) { for (final selectedTrack in selectedTracks) {
downloader.addToQueue(selectedTrack); downloader.addToQueue(selectedTrack);

View File

@ -52,41 +52,39 @@ void useUpdateChecker(WidgetRef ref) {
final latestVersion = value.last; final latestVersion = value.last;
if (currentVersion == null || latestVersion == null) return; if (currentVersion == null || latestVersion == null) return;
if (latestVersion <= currentVersion) return; if (latestVersion <= currentVersion) return;
showDialog( showPlatformAlertDialog(context, builder: (context) {
context: context, const url =
builder: (context) { "https://spotube.netlify.app/other-downloads/stable-downloads";
const url = return PlatformAlertDialog(
"https://spotube.netlify.app/other-downloads/stable-downloads"; title: const PlatformText("Spotube has an update"),
return AlertDialog( primaryActions: [
title: const Text("Spotube has an update"), PlatformFilledButton(
actions: [ child: const Text("Download Now"),
PlatformFilledButton( onPressed: () => download(url),
child: const Text("Download Now"), ),
onPressed: () => download(url), ],
), content: Column(
], crossAxisAlignment: CrossAxisAlignment.start,
content: Column( mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, children: [
mainAxisSize: MainAxisSize.min, Text("Spotube v${value.last} has been released"),
Row(
children: [ children: [
Text("Spotube v${value.last} has been released"), const PlatformText("Read the latest "),
Row( AnchorButton(
children: [ "release notes",
const Text("Read the latest "), style: const TextStyle(color: Colors.blue),
AnchorButton( onTap: () => launchUrlString(
"release notes", url,
style: const TextStyle(color: Colors.blue), mode: LaunchMode.externalApplication,
onTap: () => launchUrlString( ),
url,
mode: LaunchMode.externalApplication,
),
),
],
), ),
], ],
), ),
); ],
}); ),
);
});
}); });
return null; return null;
}, [packageInfo, isCheckUpdateEnabled]); }, [packageInfo, isCheckUpdateEnabled]);

View File

@ -109,8 +109,8 @@ void main() async {
logger.v( logger.v(
"[onFileExists] download confirmation for ${track.name}", "[onFileExists] download confirmation for ${track.name}",
); );
return showDialog<bool>( return showPlatformAlertDialog<bool>(
context: context, context,
builder: (_) => builder: (_) =>
ReplaceDownloadedFileDialog(track: track), ReplaceDownloadedFileDialog(track: track),
).then((s) => s ?? false); ).then((s) => s ?? false);
@ -201,7 +201,7 @@ class SpotubeState extends ConsumerState<Spotube> with WidgetsBindingObserver {
}; };
}, []); }, []);
platform = TargetPlatform.macOS; platform = TargetPlatform.windows;
return PlatformApp.router( return PlatformApp.router(
routeInformationParser: router.routeInformationParser, routeInformationParser: router.routeInformationParser,
@ -217,7 +217,7 @@ class SpotubeState extends ConsumerState<Spotube> with WidgetsBindingObserver {
accentMaterialColor: accentMaterialColor, accentMaterialColor: accentMaterialColor,
backgroundMaterialColor: backgroundMaterialColor, backgroundMaterialColor: backgroundMaterialColor,
), ),
iosTheme: iosTheme, iosTheme: themeMode == ThemeMode.dark ? iosDarkTheme : iosTheme,
windowsTheme: windowsTheme, windowsTheme: windowsTheme,
windowsDarkTheme: windowsDarkTheme, windowsDarkTheme: windowsDarkTheme,
macosTheme: macosTheme, macosTheme: macosTheme,

View File

@ -147,4 +147,5 @@ final macosDarkTheme = MacosThemeData.dark().copyWith(
iconTheme: const MacosIconThemeData(size: 14), iconTheme: const MacosIconThemeData(size: 14),
typography: MacosTypography(color: MacosColors.textColor), typography: MacosTypography(color: MacosColors.textColor),
); );
final iosTheme = CupertinoThemeData(); const iosTheme = CupertinoThemeData(brightness: Brightness.light);
const iosDarkTheme = CupertinoThemeData(brightness: Brightness.dark);