♻️ Remove fab menu

This commit is contained in:
2025-12-27 22:01:54 +08:00
parent 804dd029b1
commit 836449e3f4
11 changed files with 191 additions and 506 deletions

View File

@@ -9,15 +9,16 @@ import 'package:island/models/chat.dart';
import 'package:island/pods/chat/chat_summary.dart';
import 'package:island/pods/network.dart';
import 'package:island/pods/userinfo.dart';
import 'package:island/screens/chat/chat_form.dart';
import 'package:island/screens/realm/realms.dart';
import 'package:island/services/event_bus.dart';
import 'package:island/services/responsive.dart';
import 'package:island/widgets/account/account_picker.dart';
import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/chat_room_widgets.dart';
import 'package:island/widgets/content/cloud_files.dart';
import 'package:island/widgets/content/sheet.dart';
import 'package:island/widgets/navigation/fab_menu.dart';
import 'package:island/widgets/response.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:styled_widget/styled_widget.dart';
@@ -372,23 +373,6 @@ class ChatListScreen extends HookConsumerWidget {
};
}, [tabController]);
useEffect(() {
// Set FAB type to chat
final fabMenuNotifier = ref.read(fabMenuTypeProvider.notifier);
Future(() {
fabMenuNotifier.setMenuType(FabMenuType.chat);
});
return () {
// Clean up: reset FAB type to main
final fabMenu = ref.read(fabMenuTypeProvider);
WidgetsBinding.instance.addPostFrameCallback((_) {
if (fabMenu == FabMenuType.chat) {
fabMenuNotifier.setMenuType(FabMenuType.main);
}
});
};
}, []);
if (isAside) {
return Card(
margin: EdgeInsets.zero,
@@ -461,8 +445,74 @@ class ChatListScreen extends HookConsumerWidget {
final appbarFeColor = Theme.of(context).appBarTheme.foregroundColor;
final userInfo = ref.watch(userInfoProvider);
return AppScaffold(
extendBody: false, // Prevent conflicts with tabs navigation
floatingActionButton: userInfo.value != null
? FloatingActionButton(
child: const Icon(Symbols.add),
onPressed: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
useRootNavigator: true,
builder: (context) => Column(
mainAxisSize: MainAxisSize.min,
children: [
const Gap(40),
ListTile(
title: const Text('createChatRoom').tr(),
leading: const Icon(Symbols.add),
contentPadding: const EdgeInsets.symmetric(
horizontal: 24,
),
onTap: () {
showModalBottomSheet(
context: context,
useRootNavigator: true,
isScrollControlled: true,
builder: (context) => const EditChatScreen(),
).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: () 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);
}
},
),
const Gap(16),
],
),
);
},
).padding(bottom: isWideScreen(context) ? null : 56)
: null,
appBar: AppBar(
flexibleSpace: Container(
height: 48,

View File

@@ -19,8 +19,8 @@ import 'package:island/services/responsive.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:island/models/post.dart';
import 'package:island/widgets/extended_refresh_indicator.dart';
import 'package:island/widgets/navigation/fab_menu.dart';
import 'package:island/widgets/paging/pagination_list.dart';
import 'package:island/widgets/post/compose_sheet.dart';
import 'package:island/widgets/post/post_item.dart';
import 'package:island/widgets/post/post_item_skeleton.dart';
import 'package:island/widgets/post/post_list.dart';
@@ -46,24 +46,6 @@ class ExploreScreen extends HookConsumerWidget {
final selectedTagIds = useState<List<String>>([]);
final notifier = ref.watch(activityListProvider.notifier);
useEffect(() {
// Set FAB type to chat
final fabMenuNotifier = ref.read(fabMenuTypeProvider.notifier);
Future(() {
fabMenuNotifier.setMenuType(FabMenuType.compose);
});
return () {
// Clean up: reset FAB type to main
final fabMenu = ref.read(fabMenuTypeProvider);
WidgetsBinding.instance.addPostFrameCallback((_) {
if (fabMenu == FabMenuType.compose) {
fabMenuNotifier.setMenuType(FabMenuType.main);
}
});
};
}, []);
void handleFilterChange(String? filter) {
currentFilter.value = filter;
notifier.applyFilter(filter);
@@ -200,6 +182,8 @@ class ExploreScreen extends HookConsumerWidget {
).padding(horizontal: 8, vertical: 4),
);
final userInfo = ref.watch(userInfoProvider);
final appBar = isWide
? null
: _buildAppBar(
@@ -230,6 +214,49 @@ class ExploreScreen extends HookConsumerWidget {
AppScaffold(
isNoBackground: false,
appBar: appBar,
floatingActionButton: userInfo.value != null
? FloatingActionButton(
child: const Icon(Symbols.create),
onPressed: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
useRootNavigator: true,
builder: (context) => Column(
mainAxisSize: MainAxisSize.min,
children: [
const Gap(40),
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 PostComposeSheet.show(context);
},
),
ListTile(
contentPadding: const EdgeInsets.symmetric(
horizontal: 24,
),
leading: const Icon(Symbols.article),
title: Text('articleCompose').tr(),
onTap: () async {
Navigator.of(context).pop();
GoRouter.of(
context,
).pushNamed('articleCompose');
},
),
const Gap(16),
],
),
);
},
).padding(bottom: isWideScreen(context) ? null : 56)
: null,
body: isWide
? _buildWideBody(
context,

View File

@@ -1,16 +1,16 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/realm.dart';
import 'package:island/pods/network.dart';
import 'package:island/pods/userinfo.dart';
import 'package:island/services/responsive.dart';
import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/content/cloud_files.dart';
import 'package:island/widgets/content/sheet.dart';
import 'package:island/widgets/navigation/fab_menu.dart';
import 'package:island/widgets/response.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -43,22 +43,7 @@ class RealmListScreen extends HookConsumerWidget {
final realms = ref.watch(realmsJoinedProvider);
final realmInvites = ref.watch(realmInvitesProvider);
useEffect(() {
// Set FAB type to realm
final fabMenuNotifier = ref.read(fabMenuTypeProvider.notifier);
Future(() {
fabMenuNotifier.setMenuType(FabMenuType.realm);
});
return () {
// Clean up: reset FAB type to main
final fabMenu = ref.read(fabMenuTypeProvider);
WidgetsBinding.instance.addPostFrameCallback((_) {
if (fabMenu == FabMenuType.realm) {
fabMenuNotifier.setMenuType(FabMenuType.main);
}
});
};
}, []);
final userInfo = ref.watch(userInfoProvider);
return AppScaffold(
isNoBackground: false,
@@ -98,6 +83,41 @@ class RealmListScreen extends HookConsumerWidget {
const Gap(8),
],
),
floatingActionButton: userInfo.value != null
? FloatingActionButton(
child: const Icon(Symbols.add),
onPressed: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
useRootNavigator: true,
builder: (context) => Column(
mainAxisSize: MainAxisSize.min,
children: [
const Gap(40),
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 Gap(16),
],
),
);
},
).padding(bottom: isWideScreen(context) ? null : 56)
: null,
body: ExtendedRefreshIndicator(
child: realms.when(
data: (value) => Column(

View File

@@ -407,46 +407,6 @@ class SettingsScreen extends HookConsumerWidget {
),
),
// FAB position settings
ListTile(
minLeadingWidth: 48,
title: Text('fabLocation').tr(),
contentPadding: const EdgeInsets.only(left: 24, right: 17),
leading: const Icon(Symbols.adjust),
trailing: DropdownButtonHideUnderline(
child: DropdownButton2<String>(
isExpanded: true,
items: [
DropdownMenuItem<String>(
value: 'left',
child: Text('Left').fontSize(14),
),
DropdownMenuItem<String>(
value: 'center',
child: Text('Center').fontSize(14),
),
DropdownMenuItem<String>(
value: 'right',
child: Text('Right').fontSize(14),
),
],
value: settings.fabPosition,
onChanged: (String? value) {
if (value != null) {
ref.read(appSettingsProvider.notifier).setFabPosition(value);
showSnackBar('settingsApplied'.tr());
}
},
buttonStyleData: const ButtonStyleData(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 5),
height: 40,
width: 120,
),
menuItemStyleData: const MenuItemStyleData(height: 40),
),
),
),
// Card background opacity settings
ListTile(
isThreeLine: true,

View File

@@ -10,10 +10,7 @@ import 'package:island/screens/notification.dart';
import 'package:island/services/responsive.dart';
import 'package:island/widgets/content/cloud_files.dart';
import 'package:island/widgets/navigation/conditional_bottom_nav.dart';
import 'package:island/widgets/navigation/fab_menu.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:island/pods/config.dart';
import 'package:island/pods/chat/chat_summary.dart';
final currentRouteProvider = NotifierProvider<CurrentRouteNotifier, String?>(
@@ -31,13 +28,13 @@ class CurrentRouteNotifier extends Notifier<String?> {
}
}
const kWideScreenRouteStart = 4;
const kWideScreenRouteStart = 5;
const kTabRoutes = [
'/',
'/explore',
'/chat',
'/account',
'/realms',
'/account',
'/files',
'/thought',
'/creators',
@@ -84,7 +81,10 @@ class TabsScreen extends HookConsumerWidget {
child: const Icon(Symbols.forum_rounded),
),
),
NavigationDestination(
label: 'realms'.tr(),
icon: const Icon(Symbols.group_rounded),
),
NavigationDestination(
label: 'account'.tr(),
icon: Badge.count(
@@ -107,10 +107,6 @@ class TabsScreen extends HookConsumerWidget {
),
if (wideScreen)
...([
NavigationDestination(
label: 'realms'.tr(),
icon: const Icon(Symbols.group_rounded),
),
NavigationDestination(
label: 'files'.tr(),
icon: const Icon(Symbols.folder_rounded),
@@ -146,13 +142,6 @@ class TabsScreen extends HookConsumerWidget {
final currentIndex = getCurrentIndex();
final routes = kTabRoutes.sublist(
0,
isWideScreen(context) ? null : kWideScreenRouteStart,
);
final shouldShowFab = routes.contains(currentLocation) && !wideScreen;
final settings = ref.watch(appSettingsProvider);
if (isWideScreen(context)) {
return Container(
color: Theme.of(context).colorScheme.surfaceContainer,
@@ -170,10 +159,6 @@ class TabsScreen extends HookConsumerWidget {
.toList(),
selectedIndex: currentIndex,
onDestinationSelected: onDestinationSelected,
trailingAtBottom: true,
trailing: const FabMenu(
elevation: 0,
).padding(bottom: MediaQuery.of(context).padding.bottom + 16),
),
Expanded(
child: ClipRRect(
@@ -199,10 +184,6 @@ class TabsScreen extends HookConsumerWidget {
),
child: child ?? const SizedBox.shrink(),
),
floatingActionButton: shouldShowFab ? const FabMenu() : null,
floatingActionButtonLocation: shouldShowFab
? _DockedFabLocation(context, settings.fabPosition)
: null,
bottomNavigationBar: ConditionalBottomNav(
child: ClipRRect(
borderRadius: BorderRadius.only(
@@ -225,47 +206,18 @@ class TabsScreen extends HookConsumerWidget {
),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
child: BottomAppBar(
child: NavigationBar(
height: 56,
padding: EdgeInsets.symmetric(horizontal: 24),
shape: AutomaticNotchedShape(
RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
),
),
color: Theme.of(context).colorScheme.surface.withOpacity(0.8),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: () {
final navItems = destinations.asMap().entries.map<Widget>(
(entry) {
int index = entry.key;
NavigationDestination dest = entry.value;
return IconButton(
icon: dest.icon,
onPressed: () => onDestinationSelected(index),
color: index == currentIndex
? Theme.of(context).colorScheme.primary
: null,
);
},
).toList();
// Add mock item to leave space for FAB based on position
final gapIndex = switch (settings.fabPosition) {
'left' => 0,
'right' => navItems.length,
_ => navItems.length ~/ 2, // center
};
navItems.insert(
gapIndex,
SizedBox(
width: settings.fabPosition == 'center' ? 72 : 48,
),
);
return navItems;
}(),
),
destinations: destinations,
selectedIndex: currentIndex,
onDestinationSelected: onDestinationSelected,
labelBehavior: NavigationDestinationLabelBehavior.alwaysHide,
backgroundColor: Theme.of(
context,
).colorScheme.surface.withOpacity(0.8),
indicatorColor: Theme.of(
context,
).colorScheme.primary.withOpacity(0.2),
),
),
),
@@ -275,40 +227,3 @@ class TabsScreen extends HookConsumerWidget {
);
}
}
class _DockedFabLocation extends FloatingActionButtonLocation {
final BuildContext context;
final String fabPosition;
const _DockedFabLocation(this.context, this.fabPosition);
@override
Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) {
final mediaQuery = MediaQuery.of(context);
final safeAreaPadding = mediaQuery.padding;
// Position horizontally based on setting
final double fabX = switch (fabPosition) {
'left' => scaffoldGeometry.minInsets.left + 24,
'right' =>
scaffoldGeometry.scaffoldSize.width -
scaffoldGeometry.floatingActionButtonSize.width -
scaffoldGeometry.minInsets.right -
24,
_ =>
(scaffoldGeometry.scaffoldSize.width -
scaffoldGeometry.floatingActionButtonSize.width) /
2, // center
};
// Position closer to bottom with reduced padding
final double fabY =
scaffoldGeometry.scaffoldSize.height -
scaffoldGeometry.floatingActionButtonSize.height -
scaffoldGeometry.bottomSheetSize.height -
safeAreaPadding.bottom -
16;
return Offset(fabX, fabY);
}
}