💄 Optimized bottom nav

This commit is contained in:
LittleSheep 2025-06-26 01:54:43 +08:00
parent 891a0b999c
commit 2b237eaad9
7 changed files with 117 additions and 35 deletions

View File

@ -9,6 +9,6 @@ import receive_sharing_intent
class ShareViewController: RSIShareViewController { class ShareViewController: RSIShareViewController {
override func shouldAutoRedirect() -> Bool { override func shouldAutoRedirect() -> Bool {
return false return true
} }
} }

View File

@ -18,6 +18,7 @@ import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:island/pods/userinfo.dart'; import 'package:island/pods/userinfo.dart';
import 'package:island/pods/websocket.dart'; import 'package:island/pods/websocket.dart';
import 'package:island/route.dart'; import 'package:island/route.dart';
import 'package:island/screens/tabs.dart';
import 'package:island/services/notify.dart'; import 'package:island/services/notify.dart';
import 'package:island/services/timezone.dart'; import 'package:island/services/timezone.dart';
import 'package:island/widgets/alert.dart'; import 'package:island/widgets/alert.dart';
@ -186,7 +187,16 @@ class IslandApp extends HookConsumerWidget {
theme: theme?.light, theme: theme?.light,
darkTheme: theme?.dark, darkTheme: theme?.dark,
themeMode: ThemeMode.system, themeMode: ThemeMode.system,
routerConfig: appRouter.config(), routerConfig: appRouter.config(
navigatorObservers:
() => [
TabNavigationObserver(
onChange: (route) {
ref.read(currentRouteProvider.notifier).state = route;
},
),
],
),
supportedLocales: context.supportedLocales, supportedLocales: context.supportedLocales,
localizationsDelegates: [ localizationsDelegates: [
...context.localizationDelegates, ...context.localizationDelegates,

View File

@ -12,10 +12,13 @@ class AppRouter extends RootStackRouter {
]; ];
List<AutoRoute> get _appRoutes => [ List<AutoRoute> get _appRoutes => [
// Standalone routes without bottom navigation
AutoRoute(page: PostComposeRoute.page, path: 'posts/compose'), AutoRoute(page: PostComposeRoute.page, path: 'posts/compose'),
AutoRoute(page: PostEditRoute.page, path: 'posts/:id/edit'), AutoRoute(page: PostEditRoute.page, path: 'posts/:id/edit'),
AutoRoute(page: CallRoute.page, path: 'chat/:id/call'), AutoRoute(page: CallRoute.page, path: 'chat/:id/call'),
AutoRoute(page: EventCalanderRoute.page, path: 'account/:name/calendar'), AutoRoute(page: EventCalanderRoute.page, path: 'account/:name/calendar'),
// Main tabs with bottom navigation and shell routes for desktop layout
AutoRoute( AutoRoute(
page: TabsRoute.page, page: TabsRoute.page,
path: '', path: '',

View File

@ -1,3 +1,4 @@
import 'dart:developer';
import 'dart:ui'; import 'dart:ui';
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
@ -6,8 +7,40 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/route.gr.dart'; import 'package:island/route.gr.dart';
import 'package:island/screens/notification.dart'; import 'package:island/screens/notification.dart';
import 'package:island/services/responsive.dart'; import 'package:island/services/responsive.dart';
import 'package:island/widgets/navigation/conditional_bottom_nav.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
final currentRouteProvider = StateProvider<String?>((ref) => null);
class TabNavigationObserver extends AutoRouterObserver {
Function(String?) onChange;
TabNavigationObserver({required this.onChange});
@override
void didPush(Route route, Route? previousRoute) {
log('pushed ${previousRoute?.settings.name} -> ${route.settings.name}');
if (route is DialogRoute) return;
final name = route.settings.name;
if (name == null) return;
if (name.contains('Shell')) return;
Future(() {
onChange(name);
});
}
@override
void didPop(Route route, Route? previousRoute) {
log('popped ${route.settings.name} -> ${previousRoute?.settings.name}');
if (previousRoute is DialogRoute) return;
final name = previousRoute?.settings.name;
if (name == null) return;
if (name.contains('Shell')) return;
Future(() {
onChange(name);
});
}
}
@RoutePage() @RoutePage()
class TabsScreen extends HookConsumerWidget { class TabsScreen extends HookConsumerWidget {
const TabsScreen({super.key}); const TabsScreen({super.key});
@ -41,10 +74,10 @@ class TabsScreen extends HookConsumerWidget {
]; ];
final routes = <PageRouteInfo>[ final routes = <PageRouteInfo>[
ExploreRoute(), ExploreShellRoute(),
ChatListRoute(), ChatShellRoute(),
RealmListRoute(), RealmListRoute(),
AccountRoute(), AccountShellRoute(),
]; ];
return AutoTabsRouter.tabBar( return AutoTabsRouter.tabBar(
@ -83,31 +116,33 @@ class TabsScreen extends HookConsumerWidget {
left: 0, left: 0,
right: 0, right: 0,
bottom: 0, bottom: 0,
child: ClipRRect( child: ConditionalBottomNav(
child: BackdropFilter( child: ClipRRect(
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), child: BackdropFilter(
child: Container( filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
decoration: BoxDecoration( child: Container(
color: Theme.of( decoration: BoxDecoration(
context, color: Theme.of(
).colorScheme.surface.withOpacity(0.8), context,
), ).colorScheme.surface.withOpacity(0.8),
child: MediaQuery.removePadding( ),
context: context, child: MediaQuery.removePadding(
removeTop: true, context: context,
child: NavigationBar( removeTop: true,
backgroundColor: Colors.transparent, child: NavigationBar(
shadowColor: Colors.transparent, backgroundColor: Colors.transparent,
overlayColor: WidgetStatePropertyAll( shadowColor: Colors.transparent,
Colors.transparent, overlayColor: const WidgetStatePropertyAll(
Colors.transparent,
),
surfaceTintColor: Colors.transparent,
height: 56,
labelBehavior:
NavigationDestinationLabelBehavior.alwaysHide,
selectedIndex: tabsRouter.activeIndex,
onDestinationSelected: tabsRouter.setActiveIndex,
destinations: destinations,
), ),
surfaceTintColor: Colors.transparent,
height: 56,
labelBehavior:
NavigationDestinationLabelBehavior.alwaysHide,
selectedIndex: tabsRouter.activeIndex,
onDestinationSelected: tabsRouter.setActiveIndex,
destinations: destinations,
), ),
), ),
), ),

View File

@ -1,4 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:receive_sharing_intent/receive_sharing_intent.dart'; import 'package:receive_sharing_intent/receive_sharing_intent.dart';
import 'package:island/widgets/share/share_sheet.dart'; import 'package:island/widgets/share/share_sheet.dart';
@ -15,6 +17,7 @@ class SharingIntentService {
/// Initialize the sharing intent service /// Initialize the sharing intent service
void initialize(BuildContext context) { void initialize(BuildContext context) {
if (kIsWeb || !(Platform.isIOS || Platform.isAndroid)) return;
debugPrint("SharingIntentService: Initializing with context"); debugPrint("SharingIntentService: Initializing with context");
_context = context; _context = context;
_setupSharingListeners(); _setupSharingListeners();

View File

@ -0,0 +1,27 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/route.gr.dart';
import 'package:island/screens/tabs.dart';
class ConditionalBottomNav extends HookConsumerWidget {
final Widget child;
const ConditionalBottomNav({super.key, required this.child});
@override
Widget build(BuildContext context, WidgetRef ref) {
final currentRouteName = ref.watch(currentRouteProvider);
const mainTabRoutes = {
ExploreRoute.name,
ChatListRoute.name,
RealmListRoute.name,
AccountRoute.name,
};
debugPrint(currentRouteName);
final shouldShowBottomNav = mainTabRoutes.contains(currentRouteName);
return shouldShowBottomNav ? child : const SizedBox.shrink();
}
}

View File

@ -733,12 +733,16 @@ class _PostTruncateHint extends StatelessWidget {
color: Theme.of(context).colorScheme.secondary, color: Theme.of(context).colorScheme.secondary,
), ),
SizedBox(width: isCompact ? 4 : 6), SizedBox(width: isCompact ? 4 : 6),
Text( Flexible(
'postTruncated'.tr(), child: Text(
style: TextStyle( 'postTruncated'.tr(),
fontSize: isCompact ? 10 : 12, style: TextStyle(
color: Theme.of(context).colorScheme.secondary, fontSize: isCompact ? 10 : 12,
fontStyle: FontStyle.italic, color: Theme.of(context).colorScheme.secondary,
fontStyle: FontStyle.italic,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
), ),
), ),
SizedBox(width: isCompact ? 3 : 4), SizedBox(width: isCompact ? 3 : 4),