Files
App/lib/widgets/navigation/fab_menu.dart
2025-10-26 03:31:46 +08:00

193 lines
6.1 KiB
Dart

import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:island/pods/network.dart';
import 'package:island/screens/notification.dart';
import 'package:island/services/event_bus.dart';
import 'package:island/widgets/account/account_picker.dart';
import 'package:island/widgets/alert.dart';
import 'package:island/widgets/post/compose_dialog.dart';
import 'package:material_symbols_icons/symbols.dart';
enum FabMenuType { main, chat, realm }
/// Global state provider for FAB menu type
final fabMenuTypeProvider = StateProvider<FabMenuType>(
(ref) => FabMenuType.main,
);
class FabMenu extends HookConsumerWidget {
final double? elevation;
const FabMenu({super.key, this.elevation});
Future<void> _createDirectMessage(BuildContext context, WidgetRef ref) async {
final result = await showModalBottomSheet(
context: context,
useRootNavigator: true,
isScrollControlled: true,
builder: (context) => const AccountPickerSheet(),
);
if (result == null) return;
final client = ref.read(apiClientProvider);
try {
await client.post(
'/sphere/chat/direct',
data: {'related_user_id': result.id},
);
eventBus.fire(const ChatRoomsRefreshEvent());
} catch (err) {
showErrorAlert(err);
}
}
@override
Widget build(BuildContext context, WidgetRef ref) {
final fabType = ref.watch(fabMenuTypeProvider);
late final IconData icon;
late final bool useRootNavigator;
late final Widget menuContent;
final commonEntires = <Widget>[
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Symbols.bubble_chart),
title: Text('aiThoughtTitle').tr(),
onTap: () async {
Navigator.of(context).pop();
context.pushNamed('thought');
},
),
Consumer(
builder: (context, ref, _) {
final notificationCount = ref.watch(
notificationUnreadCountNotifierProvider,
);
return ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Symbols.notifications),
trailing: Badge(
label: Text(notificationCount.toString()),
isLabelVisible: notificationCount.value! > 0,
),
title: Text('notifications').tr(),
onTap: () async {
Navigator.of(context).pop();
showModalBottomSheet(
context: context,
isScrollControlled: true,
useRootNavigator: true,
builder: (context) => const NotificationSheet(),
);
},
);
},
),
];
switch (fabType) {
case FabMenuType.chat:
icon = Symbols.chat_add_on;
useRootNavigator = true;
menuContent = Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Gap(24),
ListTile(
title: const Text('createChatRoom').tr(),
leading: const Icon(Symbols.add),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
onTap: () {
Navigator.pop(context);
context.pushNamed('chatNew').then((value) {
if (value != null) {
eventBus.fire(const ChatRoomsRefreshEvent());
}
});
},
),
ListTile(
title: const Text('createDirectMessage').tr(),
leading: const Icon(Symbols.person),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
onTap: () {
Navigator.pop(context);
_createDirectMessage(context, ref);
},
),
const Divider(),
...commonEntires,
Gap(MediaQuery.of(context).padding.bottom + 16),
],
);
break;
case FabMenuType.realm:
icon = Symbols.group_add;
useRootNavigator = false;
menuContent = Column(
mainAxisSize: MainAxisSize.min,
children: [
const Gap(24),
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Symbols.group_add),
title: Text('createRealm').tr(),
onTap: () {
Navigator.of(context).pop();
context.pushNamed('realmNew').then((value) {
if (value != null) {
// Fire realm refresh event if needed
// eventBus.fire(const RealmsRefreshEvent());
}
});
},
),
const Divider(),
...commonEntires,
Gap(MediaQuery.of(context).padding.bottom + 16),
],
);
break;
case FabMenuType.main:
icon = Symbols.menu;
useRootNavigator = false;
menuContent = Column(
mainAxisSize: MainAxisSize.min,
children: [
const Gap(24),
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Symbols.post_add_rounded),
title: Text('postCompose').tr(),
onTap: () async {
Navigator.of(context).pop();
await PostComposeDialog.show(context);
},
),
const Divider(),
...commonEntires,
Gap(MediaQuery.of(context).padding.bottom + 16),
],
);
break;
}
return FloatingActionButton(
elevation: elevation,
child: Icon(icon),
onPressed: () {
showModalBottomSheet(
context: context,
useRootNavigator: useRootNavigator,
builder: (BuildContext context) => menuContent,
);
},
);
}
}