feat: adaptive popup and bottom sheet list widget

This commit is contained in:
Kingkor Roy Tirtho 2023-06-12 10:51:00 +06:00
parent d88d287fc5
commit ddc1c5f373
2 changed files with 179 additions and 14 deletions

View File

@ -0,0 +1,146 @@
import 'package:flutter/material.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/extensions/constrains.dart';
class PopSheetEntry<T> {
final T? value;
final VoidCallback? onTap;
final Widget child;
final bool enabled;
const PopSheetEntry({
required this.child,
this.value,
this.onTap,
this.enabled = true,
});
}
/// An adaptive widget that shows a [PopupMenuButton] when screen size is above
/// or equal to 640px
/// In smaller screen, a [IconButton] with a [showModalBottomSheet] is shown
class AdaptivePopSheetList<T> extends StatelessWidget {
final List<PopSheetEntry<T>> children;
final Widget? icon;
final Widget? child;
final bool useRootNavigator;
final List<Widget>? headings;
final String? tooltip;
final ValueChanged<T>? onSelected;
final BorderRadius borderRadius;
const AdaptivePopSheetList({
super.key,
required this.children,
this.icon,
this.child,
this.useRootNavigator = true,
this.headings,
this.onSelected,
this.borderRadius = const BorderRadius.all(Radius.circular(999)),
this.tooltip,
}) : assert(
icon != null || child != null,
'Either icon or child must be provided',
);
@override
Widget build(BuildContext context) {
final mediaQuery = MediaQuery.of(context);
final theme = Theme.of(context);
if (mediaQuery.mdAndUp) {
return PopupMenuButton(
icon: icon,
tooltip: tooltip,
child: IgnorePointer(child: child),
itemBuilder: (context) => children
.map(
(item) => PopupMenuItem(
padding: EdgeInsets.zero,
child: ListTile(
enabled: item.enabled,
onTap: () {
item.onTap?.call();
Navigator.pop(context);
if (item.value != null) {
onSelected?.call(item.value as T);
}
},
title: item.child,
),
),
)
.toList(),
);
}
void showSheet() {
showModalBottomSheet(
context: context,
useRootNavigator: useRootNavigator,
builder: (context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: DefaultTextStyle(
style: theme.textTheme.titleMedium!,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (headings != null) ...[
...headings!,
Divider(
color: theme.colorScheme.primary,
thickness: 0.3,
endIndent: 16,
indent: 16,
),
],
...children.map(
(item) => ListTile(
onTap: () {
item.onTap?.call();
Navigator.pop(context);
if (item.value != null) {
onSelected?.call(item.value as T);
}
},
enabled: item.enabled,
title: item.child,
),
)
],
),
),
);
},
);
}
if (child != null) {
return Tooltip(
message: tooltip ?? '',
child: InkWell(
onTap: showSheet,
borderRadius: borderRadius,
child: IgnorePointer(child: child),
),
);
}
return IconButton(
icon: icon ?? const Icon(SpotubeIcons.moreVertical),
tooltip: tooltip,
style: theme.iconButtonTheme.style?.copyWith(
shape: MaterialStatePropertyAll(
RoundedRectangleBorder(
borderRadius: borderRadius,
),
),
),
onPressed: showSheet,
);
}
}

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/library/user_local_tracks.dart'; import 'package:spotube/components/library/user_local_tracks.dart';
import 'package:spotube/components/shared/adaptive/adaptive_pop_sheet_list.dart';
import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/context.dart';
class SortTracksDropdown extends StatelessWidget { class SortTracksDropdown extends StatelessWidget {
@ -15,44 +16,62 @@ class SortTracksDropdown extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return PopupMenuButton<SortBy>( return ListTileTheme(
itemBuilder: (context) { shape: RoundedRectangleBorder(
return [ borderRadius: BorderRadius.circular(8),
PopupMenuItem( ),
child: AdaptivePopSheetList<SortBy>(
children: [
PopSheetEntry(
value: SortBy.none, value: SortBy.none,
enabled: value != SortBy.none, enabled: value != SortBy.none,
child: Text(context.l10n.none), child: Text(context.l10n.none),
), ),
PopupMenuItem( PopSheetEntry(
value: SortBy.ascending, value: SortBy.ascending,
enabled: value != SortBy.ascending, enabled: value != SortBy.ascending,
child: Text(context.l10n.sort_a_z), child: Text(context.l10n.sort_a_z),
), ),
PopupMenuItem( PopSheetEntry(
value: SortBy.descending, value: SortBy.descending,
enabled: value != SortBy.descending, enabled: value != SortBy.descending,
child: Text(context.l10n.sort_z_a), child: Text(context.l10n.sort_z_a),
), ),
PopupMenuItem( PopSheetEntry(
value: SortBy.dateAdded, value: SortBy.dateAdded,
enabled: value != SortBy.dateAdded, enabled: value != SortBy.dateAdded,
child: Text(context.l10n.sort_date), child: Text(context.l10n.sort_date),
), ),
PopupMenuItem( PopSheetEntry(
value: SortBy.artist, value: SortBy.artist,
enabled: value != SortBy.artist, enabled: value != SortBy.artist,
child: Text(context.l10n.sort_artist), child: Text(context.l10n.sort_artist),
), ),
PopupMenuItem( PopSheetEntry(
value: SortBy.album, value: SortBy.album,
enabled: value != SortBy.album, enabled: value != SortBy.album,
child: Text(context.l10n.sort_album), child: Text(context.l10n.sort_album),
), ),
]; ],
}, headings: [
Text(context.l10n.sort_tracks),
],
onSelected: onChanged, onSelected: onChanged,
tooltip: context.l10n.sort_tracks, tooltip: context.l10n.sort_tracks,
icon: const Icon(SpotubeIcons.sort), child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
child: DefaultTextStyle(
style: Theme.of(context).textTheme.titleSmall!,
child: Row(
children: [
const Icon(SpotubeIcons.sort),
const SizedBox(width: 8),
Text(context.l10n.sort_tracks),
],
),
),
),
),
); );
} }
} }