Files
MWClash/lib/pages/home.dart

339 lines
10 KiB
Dart
Raw Normal View History

import 'dart:io';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
2024-04-30 23:38:49 +08:00
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/providers/providers.dart';
2024-04-30 23:38:49 +08:00
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
2024-04-30 23:38:49 +08:00
import 'package:flutter/material.dart';
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) {
return HomeBackScope(
child: Consumer(
builder: (_, ref, child) {
final state = ref.watch(homeStateProvider);
final viewMode = state.viewMode;
final navigationItems = state.navigationItems;
final pageLabel = state.pageLabel;
final index = navigationItems.lastIndexWhere(
(element) => element.label == pageLabel,
);
final currentIndex = index == -1 ? 0 : index;
final navigationBar = CommonNavigationBar(
viewMode: viewMode,
navigationItems: navigationItems,
currentIndex: currentIndex,
);
final bottomNavigationBar =
viewMode == ViewMode.mobile ? navigationBar : null;
final sideNavigationBar =
viewMode != ViewMode.mobile ? navigationBar : null;
return CommonScaffold(
key: globalState.homeScaffoldKey,
title: Intl.message(
pageLabel.name,
),
sideNavigationBar: sideNavigationBar,
body: child!,
bottomNavigationBar: bottomNavigationBar,
);
},
child: _HomePageView(),
),
2024-04-30 23:38:49 +08:00
);
}
}
class _HomePageView extends ConsumerStatefulWidget {
const _HomePageView();
@override
ConsumerState createState() => _HomePageViewState();
}
class _HomePageViewState extends ConsumerState<_HomePageView> {
late PageController _pageController;
@override
void initState() {
super.initState();
_pageController = PageController(
initialPage: _pageIndex,
keepPage: true,
);
ref.listenManual(currentPageLabelProvider, (prev, next) {
if (prev != next) {
_toPage(next);
}
});
ref.listenManual(currentNavigationsStateProvider, (prev, next) {
if (prev?.value.length != next.value.length) {
_updatePageController();
}
});
}
int get _pageIndex {
final navigationItems = ref.read(currentNavigationsStateProvider).value;
return navigationItems.indexWhere(
(item) => item.label == globalState.appState.pageLabel,
);
}
_toPage(PageLabel pageLabel, [bool ignoreAnimateTo = false]) async {
if (!mounted) {
return;
}
final navigationItems = ref.read(currentNavigationsStateProvider).value;
final index = navigationItems.indexWhere((item) => item.label == pageLabel);
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,
);
} else {
_pageController.jumpToPage(index);
}
}
_updatePageController() {
final pageLabel = globalState.appState.pageLabel;
_toPage(pageLabel, true);
}
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final navigationItems = ref.watch(currentNavigationsStateProvider).value;
return PageView.builder(
controller: _pageController,
physics: const NeverScrollableScrollPhysics(),
itemCount: navigationItems.length,
// onPageChanged: (index) {
// debouncer.call(DebounceTag.pageChange, () {
// WidgetsBinding.instance.addPostFrameCallback((_) {
// if (_pageIndex != index) {
// final pageLabel = navigationItems[index].label;
// _toPage(pageLabel, true);
// }
// });
// });
// },
itemBuilder: (_, index) {
final navigationItem = navigationItems[index];
return KeepScope(
keep: navigationItem.keep,
key: Key(navigationItem.label.name),
child: navigationItem.fragment,
);
},
);
}
}
class CommonNavigationBar extends ConsumerWidget {
final ViewMode viewMode;
final List<NavigationItem> navigationItems;
final int currentIndex;
const CommonNavigationBar({
super.key,
required this.viewMode,
required this.navigationItems,
required this.currentIndex,
});
@override
Widget build(BuildContext context, ref) {
if (viewMode == ViewMode.mobile) {
return NavigationBarTheme(
data: _NavigationBarDefaultsM3(context),
child: NavigationBar(
destinations: navigationItems
.map(
(e) => NavigationDestination(
icon: e.icon,
label: Intl.message(e.label.name),
),
)
.toList(),
onDestinationSelected: (index) {
globalState.appController.toPage(navigationItems[index].label);
},
selectedIndex: currentIndex,
),
);
}
final showLabel = ref.watch(appSettingProvider).showLabel;
return Material(
color: context.colorScheme.surfaceContainer,
child: Column(
children: [
Expanded(
child: ScrollConfiguration(
behavior: HiddenBarScrollBehavior(),
child: SingleChildScrollView(
child: IntrinsicHeight(
child: NavigationRail(
backgroundColor: context.colorScheme.surfaceContainer,
selectedIconTheme: IconThemeData(
color: context.colorScheme.onSurfaceVariant,
),
unselectedIconTheme: IconThemeData(
color: context.colorScheme.onSurfaceVariant,
),
selectedLabelTextStyle:
context.textTheme.labelLarge!.copyWith(
color: context.colorScheme.onSurface,
),
unselectedLabelTextStyle:
context.textTheme.labelLarge!.copyWith(
color: context.colorScheme.onSurface,
),
destinations: navigationItems
.map(
(e) => NavigationRailDestination(
icon: e.icon,
label: Text(
Intl.message(e.label.name),
),
),
)
.toList(),
onDestinationSelected: (index) {
globalState.appController
.toPage(navigationItems[index].label);
},
extended: false,
selectedIndex: currentIndex,
labelType: showLabel
? NavigationRailLabelType.all
: NavigationRailLabelType.none,
),
),
),
),
),
const SizedBox(
height: 16,
),
IconButton(
onPressed: () {
ref.read(appSettingProvider.notifier).updateState(
(state) => state.copyWith(
showLabel: !state.showLabel,
),
);
},
icon: const Icon(Icons.menu),
),
const SizedBox(
height: 16,
),
],
),
);
}
}
class _NavigationBarDefaultsM3 extends NavigationBarThemeData {
_NavigationBarDefaultsM3(this.context)
: super(
height: 80.0,
elevation: 3.0,
labelBehavior: NavigationDestinationLabelBehavior.alwaysShow,
);
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)
? _colors.onSurfaceVariant.opacity38
: states.contains(WidgetState.selected)
? _colors.onSecondaryContainer
: _colors.onSurfaceVariant,
);
});
}
@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(
overflow: TextOverflow.ellipsis,
color: states.contains(WidgetState.disabled)
? _colors.onSurfaceVariant.opacity38
: states.contains(WidgetState.selected)
? _colors.onSurface
: _colors.onSurfaceVariant);
});
}
}
class HomeBackScope extends StatelessWidget {
final Widget child;
const HomeBackScope({super.key, required this.child});
@override
Widget build(BuildContext context) {
if (Platform.isAndroid) {
return CommonPopScope(
onPop: () async {
final canPop = Navigator.canPop(context);
if (canPop) {
Navigator.pop(context);
} else {
await globalState.appController.handleBackOrExit();
}
return false;
},
child: child,
);
}
return child;
}
}