Files
MWClash/lib/pages/home.dart
chen08209 676f2d058a Add windows server mode start process verify
Add linux deb dependencies

Add backup recovery strategy select

Support custom text scaling

Optimize the display of different text scale

Optimize windows setup experience

Optimize startTun performance

Optimize android tv experience

Optimize default option

Optimize computed text size

Optimize hyperOS freeform window

Add developer mode

Update core

Optimize more details
2025-05-01 00:02:29 +08:00

339 lines
10 KiB
Dart

import 'dart:io';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
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(),
),
);
}
}
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;
}
}