-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
46107f2
commit c182c46
Showing
4 changed files
with
325 additions
and
92 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_hooks/flutter_hooks.dart'; | ||
import 'package:hiddify/utils/platform_utils.dart'; | ||
import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||
import 'package:wolt_modal_sheet/wolt_modal_sheet.dart'; | ||
|
||
typedef AdaptiveMenuBuilder = Widget Function( | ||
BuildContext context, | ||
void Function() toggleVisibility, | ||
Widget? child, | ||
); | ||
|
||
class AdaptiveMenuItem<T> { | ||
AdaptiveMenuItem({ | ||
required this.title, | ||
this.icon, | ||
this.onTap, | ||
this.isSelected, | ||
this.subItems, | ||
}); | ||
|
||
final String title; | ||
final IconData? icon; | ||
final T Function()? onTap; | ||
final bool? isSelected; | ||
final List<AdaptiveMenuItem>? subItems; | ||
|
||
(String, IconData?, T Function()?, bool?, List<AdaptiveMenuItem>?) | ||
_equality() => (title, icon, onTap, isSelected, subItems); | ||
|
||
@override | ||
bool operator ==(covariant AdaptiveMenuItem other) { | ||
if (identical(this, other)) return true; | ||
return other._equality() == _equality(); | ||
} | ||
|
||
@override | ||
int get hashCode => _equality().hashCode; | ||
} | ||
|
||
class AdaptiveMenu extends HookConsumerWidget { | ||
const AdaptiveMenu({ | ||
super.key, | ||
required this.items, | ||
required this.builder, | ||
required this.child, | ||
}); | ||
|
||
final Iterable<AdaptiveMenuItem> items; | ||
final AdaptiveMenuBuilder builder; | ||
final Widget? child; | ||
|
||
@override | ||
Widget build(BuildContext context, WidgetRef ref) { | ||
if (PlatformUtils.isDesktop) { | ||
List<Widget> buildMenuItems(Iterable<AdaptiveMenuItem> scopeItems) { | ||
final menuItems = <Widget>[]; | ||
for (final item in scopeItems) { | ||
if (item.subItems != null) { | ||
final subItems = buildMenuItems(item.subItems!); | ||
menuItems.add( | ||
SubmenuButton( | ||
menuChildren: subItems, | ||
leadingIcon: item.icon != null ? Icon(item.icon) : null, | ||
child: Text(item.title), | ||
), | ||
); | ||
} else { | ||
menuItems.add( | ||
MenuItemButton( | ||
leadingIcon: item.icon != null ? Icon(item.icon) : null, | ||
onPressed: item.onTap, | ||
child: Text(item.title), | ||
), | ||
); | ||
} | ||
} | ||
return menuItems; | ||
} | ||
|
||
return MenuAnchor( | ||
builder: (context, controller, child) => builder( | ||
context, | ||
() { | ||
if (controller.isOpen) { | ||
controller.close(); | ||
} else { | ||
controller.open(); | ||
} | ||
}, | ||
child, | ||
), | ||
menuChildren: buildMenuItems(items), | ||
child: child, | ||
); | ||
} | ||
|
||
final pageIndexNotifier = useValueNotifier(0); | ||
final nestedSheets = <SliverWoltModalSheetPage>[]; | ||
int pageIndex = 0; | ||
|
||
void popSheets() { | ||
if (context.mounted) { | ||
Navigator.pop(context); | ||
} | ||
Future.delayed(const Duration(milliseconds: 200)) | ||
.then((_) => pageIndexNotifier.value = 0); | ||
} | ||
|
||
List<Widget> buildSheetItems( | ||
Iterable<AdaptiveMenuItem> menuItems, | ||
int index, | ||
) { | ||
final sheetItems = <Widget>[]; | ||
for (final item in menuItems) { | ||
if (item.subItems != null) { | ||
final subItems = buildSheetItems(item.subItems!, index + 1); | ||
final subSheetIndex = ++pageIndex; | ||
sheetItems.add( | ||
ListTile( | ||
title: Text(item.title), | ||
leading: item.icon != null ? Icon(item.icon) : null, | ||
trailing: const Icon(Icons.chevron_right), | ||
onTap: () { | ||
pageIndexNotifier.value = subSheetIndex; | ||
}, | ||
), | ||
); | ||
nestedSheets.add( | ||
SliverWoltModalSheetPage( | ||
hasTopBarLayer: false, | ||
isTopBarLayerAlwaysVisible: true, | ||
topBarTitle: Text(item.title), | ||
mainContentSlivers: [ | ||
SliverList.list(children: subItems), | ||
], | ||
), | ||
); | ||
} else { | ||
sheetItems.add( | ||
ListTile( | ||
title: Text(item.title), | ||
leading: item.icon != null ? Icon(item.icon) : null, | ||
onTap: () async { | ||
popSheets(); | ||
await item.onTap!(); | ||
}, | ||
), | ||
); | ||
} | ||
} | ||
return sheetItems; | ||
} | ||
|
||
return builder( | ||
context, | ||
() async { | ||
await WoltModalSheet.show( | ||
context: context, | ||
pageIndexNotifier: pageIndexNotifier, | ||
onModalDismissedWithDrag: popSheets, | ||
onModalDismissedWithBarrierTap: popSheets, | ||
useSafeArea: true, | ||
showDragHandle: false, | ||
pageListBuilder: (context) => [ | ||
SliverWoltModalSheetPage( | ||
hasTopBarLayer: false, | ||
mainContentSlivers: [ | ||
SliverList.list(children: buildSheetItems(items, 0)), | ||
], | ||
), | ||
...nestedSheets, | ||
], | ||
); | ||
}, | ||
child, | ||
); | ||
} | ||
} |
Oops, something went wrong.