Files
MWClash/lib/pages/home.dart
chen08209 2742c570ce Optimize desktop view
Optimize logs, requests, connection pages

Update core
2025-07-24 10:28:33 +08:00

271 lines
8.1 KiB
Dart

import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.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/services.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: Material(
color: context.colorScheme.surface,
child: Consumer(
builder: (context, ref, __) {
final state = ref.watch(navigationStateProvider);
final isMobile = state.viewMode == ViewMode.mobile;
final navigationItems = state.navigationItems;
final pageView = _HomePageView(pageBuilder: (_, index) {
final navigationItem = state.navigationItems[index];
final navigationView = navigationItem.builder(context);
final view = isMobile
? navigationView
: KeepScope(
keep: navigationItem.keep,
child: Navigator(
onGenerateRoute: (_) {
return CommonRoute(
builder: (_) => navigationView,
);
},
),
);
return view;
});
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) {
globalState.appController.toPage(
navigationItems[index].label,
);
},
selectedIndex: currentIndex,
),
);
if (isMobile) {
return AnnotatedRegion<SystemUiOverlayStyle>(
value: globalState.appState.systemUiOverlayStyle.copyWith(
systemNavigationBarColor:
context.colorScheme.surfaceContainer,
),
child: Column(
children: [
Expanded(
flex: 1,
child: MediaQuery.removePadding(
removeTop: false,
removeBottom: true,
removeLeft: true,
removeRight: true,
context: context,
child: pageView,
),
),
MediaQuery.removePadding(
removeTop: true,
removeBottom: false,
removeLeft: true,
removeRight: true,
context: context,
child: bottomNavigationBar,
),
],
),
);
} else {
return pageView;
}
},
),
),
);
}
}
class _HomePageView extends ConsumerStatefulWidget {
final IndexedWidgetBuilder pageBuilder;
const _HomePageView({
required this.pageBuilder,
});
@override
ConsumerState createState() => _HomePageViewState();
}
class _HomePageViewState extends ConsumerState<_HomePageView> {
late PageController _pageController;
@override
initState() {
super.initState();
_pageController = PageController(
initialPage: _pageIndex,
);
ref.listenManual(currentPageLabelProvider, (prev, next) {
if (prev != next) {
_toPage(next);
}
});
ref.listenManual(currentNavigationItemsStateProvider, (prev, next) {
if (prev?.value.length != next.value.length) {
_updatePageController();
}
});
}
int get _pageIndex {
final navigationItems = ref.read(currentNavigationItemsStateProvider).value;
return navigationItems.indexWhere(
(item) => item.label == globalState.appState.pageLabel,
);
}
Future<void> _toPage(PageLabel pageLabel,
[bool ignoreAnimateTo = false]) async {
if (!mounted) {
return;
}
final navigationItems = ref.read(currentNavigationItemsStateProvider).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);
}
}
void _updatePageController() {
final pageLabel = globalState.appState.pageLabel;
_toPage(pageLabel, true);
}
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final itemCount = ref.watch(currentNavigationItemsStateProvider
.select((state) => state.value.length));
return PageView.builder(
controller: _pageController,
physics: const NeverScrollableScrollPhysics(),
itemCount: itemCount,
itemBuilder: (context, index) {
return widget.pageBuilder(context, index);
},
);
}
}
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 (system.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;
}
}