2024-06-03 18:02:05 +08:00
|
|
|
import 'package:fl_clash/common/common.dart';
|
2025-12-16 11:23:09 +08:00
|
|
|
import 'package:fl_clash/controller.dart';
|
2025-02-09 18:39:38 +08:00
|
|
|
import 'package:fl_clash/enum/enum.dart';
|
2025-07-31 17:09:18 +08:00
|
|
|
import 'package:fl_clash/manager/app_manager.dart';
|
2025-12-16 11:23:09 +08:00
|
|
|
import 'package:fl_clash/models/common.dart';
|
2025-02-09 18:39:38 +08:00
|
|
|
import 'package:fl_clash/providers/providers.dart';
|
|
|
|
|
import 'package:fl_clash/widgets/widgets.dart';
|
2024-04-30 23:38:49 +08:00
|
|
|
import 'package:flutter/material.dart';
|
2025-06-07 01:48:34 +08:00
|
|
|
import 'package:flutter/services.dart';
|
2025-02-09 18:39:38 +08:00
|
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
2024-04-30 23:38:49 +08:00
|
|
|
import 'package:intl/intl.dart';
|
|
|
|
|
|
|
|
|
|
typedef OnSelected = void Function(int index);
|
|
|
|
|
|
|
|
|
|
class HomePage extends StatelessWidget {
|
|
|
|
|
const HomePage({super.key});
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
2025-07-31 17:09:18 +08:00
|
|
|
return HomeBackScopeContainer(
|
|
|
|
|
child: AppSidebarContainer(
|
|
|
|
|
child: Material(
|
|
|
|
|
color: context.colorScheme.surface,
|
|
|
|
|
child: Consumer(
|
2025-12-16 11:23:09 +08:00
|
|
|
builder: (context, ref, child) {
|
2025-07-31 17:09:18 +08:00
|
|
|
final state = ref.watch(navigationStateProvider);
|
2025-12-16 11:23:09 +08:00
|
|
|
final systemUiOverlayStyle = ref.read(
|
|
|
|
|
systemUiOverlayStyleStateProvider,
|
|
|
|
|
);
|
2025-07-31 17:09:18 +08:00
|
|
|
final isMobile = state.viewMode == ViewMode.mobile;
|
|
|
|
|
final navigationItems = state.navigationItems;
|
|
|
|
|
final currentIndex = state.currentIndex;
|
|
|
|
|
final bottomNavigationBar = NavigationBarTheme(
|
|
|
|
|
data: _NavigationBarDefaultsM3(context),
|
|
|
|
|
child: NavigationBar(
|
|
|
|
|
destinations: navigationItems
|
|
|
|
|
.map(
|
|
|
|
|
(e) => NavigationDestination(
|
|
|
|
|
icon: e.icon,
|
|
|
|
|
label: Intl.message(e.label.name),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.toList(),
|
|
|
|
|
onDestinationSelected: (index) {
|
2025-12-16 11:23:09 +08:00
|
|
|
appController.toPage(navigationItems[index].label);
|
2025-07-31 17:09:18 +08:00
|
|
|
},
|
|
|
|
|
selectedIndex: currentIndex,
|
2025-06-07 01:48:34 +08:00
|
|
|
),
|
2025-07-31 17:09:18 +08:00
|
|
|
);
|
|
|
|
|
if (isMobile) {
|
|
|
|
|
return AnnotatedRegion<SystemUiOverlayStyle>(
|
2025-12-16 11:23:09 +08:00
|
|
|
value: systemUiOverlayStyle.copyWith(
|
2025-07-31 17:09:18 +08:00
|
|
|
systemNavigationBarColor:
|
|
|
|
|
context.colorScheme.surfaceContainer,
|
|
|
|
|
),
|
|
|
|
|
child: Column(
|
|
|
|
|
children: [
|
|
|
|
|
Flexible(
|
|
|
|
|
flex: 1,
|
|
|
|
|
child: MediaQuery.removePadding(
|
|
|
|
|
removeTop: false,
|
|
|
|
|
removeBottom: true,
|
|
|
|
|
removeLeft: true,
|
|
|
|
|
removeRight: true,
|
|
|
|
|
context: context,
|
2025-12-16 11:23:09 +08:00
|
|
|
child: child!,
|
2025-07-31 17:09:18 +08:00
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
MediaQuery.removePadding(
|
|
|
|
|
removeTop: true,
|
|
|
|
|
removeBottom: false,
|
2025-06-07 01:48:34 +08:00
|
|
|
removeLeft: true,
|
|
|
|
|
removeRight: true,
|
|
|
|
|
context: context,
|
2025-07-31 17:09:18 +08:00
|
|
|
child: bottomNavigationBar,
|
2025-06-07 01:48:34 +08:00
|
|
|
),
|
2025-07-31 17:09:18 +08:00
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
} else {
|
2025-12-16 11:23:09 +08:00
|
|
|
return child!;
|
2025-07-31 17:09:18 +08:00
|
|
|
}
|
|
|
|
|
},
|
2025-12-16 11:23:09 +08:00
|
|
|
child: Consumer(
|
|
|
|
|
builder: (_, ref, _) {
|
|
|
|
|
final navigationItems = ref
|
|
|
|
|
.watch(currentNavigationItemsStateProvider)
|
|
|
|
|
.value;
|
|
|
|
|
final isMobile = ref.watch(isMobileViewProvider);
|
|
|
|
|
return _HomePageView(
|
|
|
|
|
navigationItems: navigationItems,
|
|
|
|
|
pageBuilder: (_, index) {
|
|
|
|
|
final navigationItem = navigationItems[index];
|
|
|
|
|
final navigationView = navigationItem.builder(context);
|
|
|
|
|
final view = KeepScope(
|
|
|
|
|
keep: navigationItem.keep,
|
|
|
|
|
child: isMobile
|
|
|
|
|
? navigationView
|
|
|
|
|
: Navigator(
|
|
|
|
|
pages: [MaterialPage(child: navigationView)],
|
|
|
|
|
onDidRemovePage: (_) {},
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
return view;
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
),
|
2025-07-31 17:09:18 +08:00
|
|
|
),
|
2025-06-07 01:48:34 +08:00
|
|
|
),
|
2024-07-24 01:27:49 +08:00
|
|
|
),
|
2024-04-30 23:38:49 +08:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-12-09 01:40:39 +08:00
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
class _HomePageView extends ConsumerStatefulWidget {
|
2025-06-07 01:48:34 +08:00
|
|
|
final IndexedWidgetBuilder pageBuilder;
|
2025-12-16 11:23:09 +08:00
|
|
|
final List<NavigationItem> navigationItems;
|
2025-06-07 01:48:34 +08:00
|
|
|
|
2025-12-16 11:23:09 +08:00
|
|
|
const _HomePageView({
|
|
|
|
|
required this.pageBuilder,
|
|
|
|
|
required this.navigationItems,
|
|
|
|
|
});
|
2025-02-09 18:39:38 +08:00
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
ConsumerState createState() => _HomePageViewState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class _HomePageViewState extends ConsumerState<_HomePageView> {
|
2025-03-12 17:15:31 +08:00
|
|
|
late PageController _pageController;
|
|
|
|
|
|
|
|
|
|
@override
|
2025-06-07 01:48:34 +08:00
|
|
|
initState() {
|
2025-03-12 17:15:31 +08:00
|
|
|
super.initState();
|
2025-07-31 17:09:18 +08:00
|
|
|
_pageController = PageController(initialPage: _pageIndex);
|
2025-03-12 17:15:31 +08:00
|
|
|
ref.listenManual(currentPageLabelProvider, (prev, next) {
|
|
|
|
|
if (prev != next) {
|
|
|
|
|
_toPage(next);
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-12-16 11:23:09 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void didUpdateWidget(covariant _HomePageView oldWidget) {
|
|
|
|
|
super.didUpdateWidget(oldWidget);
|
|
|
|
|
if (oldWidget.navigationItems.length != widget.navigationItems.length) {
|
|
|
|
|
_updatePageController();
|
|
|
|
|
}
|
2025-03-12 17:15:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int get _pageIndex {
|
2025-12-16 11:23:09 +08:00
|
|
|
final pageLabel = ref.read(currentPageLabelProvider);
|
|
|
|
|
return widget.navigationItems.indexWhere((item) => item.label == pageLabel);
|
2025-03-12 17:15:31 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-31 17:09:18 +08:00
|
|
|
Future<void> _toPage(
|
|
|
|
|
PageLabel pageLabel, [
|
|
|
|
|
bool ignoreAnimateTo = false,
|
|
|
|
|
]) async {
|
2025-03-12 17:15:31 +08:00
|
|
|
if (!mounted) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-12-16 11:23:09 +08:00
|
|
|
final index = widget.navigationItems.indexWhere(
|
|
|
|
|
(item) => item.label == pageLabel,
|
|
|
|
|
);
|
2025-03-12 17:15:31 +08:00
|
|
|
if (index == -1) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
final isAnimateToPage = ref.read(appSettingProvider).isAnimateToPage;
|
|
|
|
|
final isMobile = ref.read(isMobileViewProvider);
|
|
|
|
|
if (isAnimateToPage && isMobile && !ignoreAnimateTo) {
|
|
|
|
|
await _pageController.animateToPage(
|
|
|
|
|
index,
|
|
|
|
|
duration: kTabScrollDuration,
|
|
|
|
|
curve: Curves.easeOut,
|
2025-02-09 18:39:38 +08:00
|
|
|
);
|
2025-03-12 17:15:31 +08:00
|
|
|
} else {
|
|
|
|
|
_pageController.jumpToPage(index);
|
2025-02-09 18:39:38 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-07 01:48:34 +08:00
|
|
|
void _updatePageController() {
|
2025-12-16 11:23:09 +08:00
|
|
|
final pageLabel = ref.read(currentPageLabelProvider);
|
2025-03-12 17:15:31 +08:00
|
|
|
_toPage(pageLabel, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void dispose() {
|
|
|
|
|
_pageController.dispose();
|
|
|
|
|
super.dispose();
|
|
|
|
|
}
|
2025-02-09 18:39:38 +08:00
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
2025-07-31 17:09:18 +08:00
|
|
|
final itemCount = ref.watch(
|
|
|
|
|
currentNavigationItemsStateProvider.select((state) => state.value.length),
|
|
|
|
|
);
|
2025-02-09 18:39:38 +08:00
|
|
|
return PageView.builder(
|
2025-03-12 17:15:31 +08:00
|
|
|
controller: _pageController,
|
2025-02-09 18:39:38 +08:00
|
|
|
physics: const NeverScrollableScrollPhysics(),
|
2025-06-07 01:48:34 +08:00
|
|
|
itemCount: itemCount,
|
|
|
|
|
itemBuilder: (context, index) {
|
|
|
|
|
return widget.pageBuilder(context, index);
|
2025-02-09 18:39:38 +08:00
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-05 16:08:50 +08:00
|
|
|
class _NavigationBarDefaultsM3 extends NavigationBarThemeData {
|
|
|
|
|
_NavigationBarDefaultsM3(this.context)
|
2025-07-31 17:09:18 +08:00
|
|
|
: super(
|
|
|
|
|
height: 80.0,
|
|
|
|
|
elevation: 3.0,
|
|
|
|
|
labelBehavior: NavigationDestinationLabelBehavior.alwaysShow,
|
|
|
|
|
);
|
2025-03-05 16:08:50 +08:00
|
|
|
|
|
|
|
|
final BuildContext context;
|
|
|
|
|
late final ColorScheme _colors = Theme.of(context).colorScheme;
|
|
|
|
|
late final TextTheme _textTheme = Theme.of(context).textTheme;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Color? get backgroundColor => _colors.surfaceContainer;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Color? get shadowColor => Colors.transparent;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Color? get surfaceTintColor => Colors.transparent;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
WidgetStateProperty<IconThemeData?>? get iconTheme {
|
|
|
|
|
return WidgetStateProperty.resolveWith((Set<WidgetState> states) {
|
|
|
|
|
return IconThemeData(
|
|
|
|
|
size: 24.0,
|
|
|
|
|
color: states.contains(WidgetState.disabled)
|
2025-03-12 17:15:31 +08:00
|
|
|
? _colors.onSurfaceVariant.opacity38
|
2025-03-05 16:08:50 +08:00
|
|
|
: states.contains(WidgetState.selected)
|
2025-07-31 17:09:18 +08:00
|
|
|
? _colors.onSecondaryContainer
|
|
|
|
|
: _colors.onSurfaceVariant,
|
2025-03-05 16:08:50 +08:00
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Color? get indicatorColor => _colors.secondaryContainer;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
ShapeBorder? get indicatorShape => const StadiumBorder();
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
WidgetStateProperty<TextStyle?>? get labelTextStyle {
|
|
|
|
|
return WidgetStateProperty.resolveWith((Set<WidgetState> states) {
|
|
|
|
|
final TextStyle style = _textTheme.labelMedium!;
|
|
|
|
|
return style.apply(
|
2025-07-31 17:09:18 +08:00
|
|
|
overflow: TextOverflow.ellipsis,
|
|
|
|
|
color: states.contains(WidgetState.disabled)
|
|
|
|
|
? _colors.onSurfaceVariant.opacity38
|
|
|
|
|
: states.contains(WidgetState.selected)
|
|
|
|
|
? _colors.onSurface
|
|
|
|
|
: _colors.onSurfaceVariant,
|
|
|
|
|
);
|
2025-03-05 16:08:50 +08:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-12 17:15:31 +08:00
|
|
|
|
2025-07-31 17:09:18 +08:00
|
|
|
class HomeBackScopeContainer extends ConsumerWidget {
|
2025-03-12 17:15:31 +08:00
|
|
|
final Widget child;
|
|
|
|
|
|
2025-07-31 17:09:18 +08:00
|
|
|
const HomeBackScopeContainer({super.key, required this.child});
|
2025-03-12 17:15:31 +08:00
|
|
|
|
|
|
|
|
@override
|
2025-07-31 17:09:18 +08:00
|
|
|
Widget build(BuildContext context, ref) {
|
|
|
|
|
return CommonPopScope(
|
|
|
|
|
onPop: (context) async {
|
|
|
|
|
final pageLabel = ref.read(currentPageLabelProvider);
|
|
|
|
|
final realContext =
|
|
|
|
|
GlobalObjectKey(pageLabel).currentContext ?? context;
|
|
|
|
|
final canPop = Navigator.canPop(realContext);
|
|
|
|
|
if (canPop) {
|
|
|
|
|
Navigator.of(realContext).pop();
|
|
|
|
|
} else {
|
2025-12-16 11:23:09 +08:00
|
|
|
await appController.handleBackOrExit();
|
2025-07-31 17:09:18 +08:00
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
},
|
|
|
|
|
child: child,
|
|
|
|
|
);
|
2025-03-12 17:15:31 +08:00
|
|
|
}
|
|
|
|
|
}
|