v1.0.1
This commit is contained in:
100
lib/modules/index/index_controller.dart
Normal file
100
lib/modules/index/index_controller.dart
Normal file
@@ -0,0 +1,100 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dmzj/app/platform_utils.dart';
|
||||
import 'package:flutter_dmzj/services/app_settings_service.dart';
|
||||
import 'package:flutter_dmzj/app/dialog_utils.dart';
|
||||
import 'package:flutter_dmzj/app/event_bus.dart';
|
||||
import 'package:flutter_dmzj/app/utils.dart';
|
||||
import 'package:flutter_dmzj/modules/comic/home/comic_home_page.dart';
|
||||
import 'package:flutter_dmzj/modules/news/home/news_home_controller.dart';
|
||||
import 'package:flutter_dmzj/modules/news/home/news_home_page.dart';
|
||||
import 'package:flutter_dmzj/modules/novel/home/novel_home_controller.dart';
|
||||
import 'package:flutter_dmzj/modules/novel/home/novel_home_page.dart';
|
||||
import 'package:flutter_dmzj/modules/user/user_home_page.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:multi_split_view/multi_split_view.dart';
|
||||
|
||||
class IndexController extends GetxController {
|
||||
final index = 0.obs;
|
||||
final showContent = false.obs;
|
||||
final GlobalKey indexKey = GlobalKey();
|
||||
final GlobalKey subRouterKey = GlobalKey();
|
||||
|
||||
final MultiSplitViewController multiSplitViewController =
|
||||
MultiSplitViewController(areas: [
|
||||
Area(minimalSize: 400, size: 500),
|
||||
]);
|
||||
|
||||
/// 双击退出Flag
|
||||
bool doubleClickExit = false;
|
||||
|
||||
/// 双击退出Timer
|
||||
Timer? doubleClickTimer;
|
||||
|
||||
final pages = [
|
||||
const ComicHomePage(),
|
||||
const SizedBox(),
|
||||
const SizedBox(),
|
||||
const UserHomePage(),
|
||||
];
|
||||
@override
|
||||
void onInit() {
|
||||
if (PlatformUtils.isWindows) {
|
||||
// Windows: 预先初始化所有分区控制器,确保NavigationView所有PaneItem.body可用
|
||||
if (!Get.isRegistered<NewsHomeController>()) {
|
||||
Get.put(NewsHomeController());
|
||||
}
|
||||
if (!Get.isRegistered<NovelHomeController>()) {
|
||||
Get.put(NovelHomeController());
|
||||
}
|
||||
pages[1] = const NewsHomePage();
|
||||
pages[2] = const NovelHomePage();
|
||||
}
|
||||
Future.delayed(Duration.zero, showFirstRun);
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {}
|
||||
|
||||
void setIndex(i) {
|
||||
if (i == 1 && pages[i] is SizedBox) {
|
||||
Get.put(NewsHomeController());
|
||||
pages[i] = const NewsHomePage();
|
||||
} else if (i == 2 && pages[i] is SizedBox) {
|
||||
Get.put(NovelHomeController());
|
||||
pages[i] = const NovelHomePage();
|
||||
}
|
||||
if (index.value == i) {
|
||||
EventBus.instance.emit<int>(EventBus.kBottomNavigationBarClicked, i);
|
||||
}
|
||||
index.value = i;
|
||||
}
|
||||
|
||||
void showFirstRun() async {
|
||||
if (AppSettingsService.instance.firstRun) {
|
||||
AppSettingsService.instance.setNoFirstRun();
|
||||
DialogUtils.showStatement();
|
||||
Utils.checkUpdate();
|
||||
} else {
|
||||
Utils.checkUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void setDoubleExitFlag() {
|
||||
if (doubleClickExit) {
|
||||
doubleClickTimer?.cancel();
|
||||
Get.back();
|
||||
return;
|
||||
}
|
||||
doubleClickExit = true;
|
||||
SmartDialog.showToast("再按一次退出应用");
|
||||
doubleClickTimer = Timer(const Duration(seconds: 2), () {
|
||||
doubleClickExit = false;
|
||||
doubleClickTimer!.cancel();
|
||||
});
|
||||
}
|
||||
}
|
||||
223
lib/modules/index/index_page.dart
Normal file
223
lib/modules/index/index_page.dart
Normal file
@@ -0,0 +1,223 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_dmzj/app/app_style.dart';
|
||||
import 'package:flutter_dmzj/app/platform_utils.dart';
|
||||
import 'package:flutter_dmzj/modules/common/empty_page.dart';
|
||||
import 'package:flutter_dmzj/modules/index/index_controller.dart';
|
||||
import 'package:flutter_dmzj/modules/index/windows_index_page.dart';
|
||||
import 'package:flutter_dmzj/routes/app_navigator.dart';
|
||||
import 'package:flutter_dmzj/routes/app_pages.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:remixicon/remixicon.dart';
|
||||
|
||||
class IndexPage extends GetView<IndexController> {
|
||||
const IndexPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Windows平台使用Fluent UI的NavigationView
|
||||
if (PlatformUtils.isWindows) {
|
||||
return const WindowsIndexPage();
|
||||
}
|
||||
final content = _buildContentNavigator();
|
||||
final indexStack = _buildIndexStack();
|
||||
return OrientationBuilder(
|
||||
builder: (context, orientation) {
|
||||
return orientation == Orientation.landscape
|
||||
? _buildWide(context, indexStack, content)
|
||||
: _buildNarrow(context, indexStack, content);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNarrow(BuildContext context, Widget indexStack, Widget content) {
|
||||
return Stack(
|
||||
children: [
|
||||
Obx(
|
||||
() => Scaffold(
|
||||
body: indexStack,
|
||||
bottomNavigationBar: NavigationBar(
|
||||
selectedIndex: controller.index.value,
|
||||
onDestinationSelected: controller.setIndex,
|
||||
destinations: const [
|
||||
NavigationDestination(
|
||||
icon: Icon(Remix.bear_smile_line),
|
||||
selectedIcon: Icon(Remix.bear_smile_fill),
|
||||
label: "漫画",
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: Icon(Remix.article_line),
|
||||
selectedIcon: Icon(Remix.article_fill),
|
||||
label: "资讯",
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: Icon(Remix.book_open_line),
|
||||
selectedIcon: Icon(Remix.book_open_fill),
|
||||
label: "轻小说",
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: Icon(Remix.user_smile_line),
|
||||
selectedIcon: Icon(Remix.user_smile_fill),
|
||||
label: "我的",
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => IgnorePointer(
|
||||
ignoring: !controller.showContent.value,
|
||||
child: content,
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildWide(BuildContext context, Widget indexStack, Widget content) {
|
||||
return Scaffold(
|
||||
body: Row(
|
||||
children: [
|
||||
Obx(
|
||||
() => Padding(
|
||||
padding: const EdgeInsets.only(right: 2),
|
||||
child: NavigationRail(
|
||||
elevation: 2,
|
||||
labelType: NavigationRailLabelType.all,
|
||||
onDestinationSelected: controller.setIndex,
|
||||
selectedIndex: controller.index.value,
|
||||
leading: SizedBox(
|
||||
height: AppStyle.statusBarHeight,
|
||||
),
|
||||
selectedLabelTextStyle: TextStyle(
|
||||
fontSize: 10,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
unselectedLabelTextStyle: TextStyle(
|
||||
fontSize: 10,
|
||||
color: Theme.of(context).textTheme.bodyLarge?.color,
|
||||
),
|
||||
destinations: const [
|
||||
NavigationRailDestination(
|
||||
icon: Icon(Remix.bear_smile_line),
|
||||
label: Text("漫画"),
|
||||
),
|
||||
NavigationRailDestination(
|
||||
icon: Icon(Remix.article_line),
|
||||
label: Text("资讯"),
|
||||
),
|
||||
NavigationRailDestination(
|
||||
icon: Icon(Remix.book_open_line),
|
||||
label: Text("轻小说"),
|
||||
),
|
||||
NavigationRailDestination(
|
||||
icon: Icon(Remix.user_smile_line),
|
||||
label: Text("我的"),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
// constraints: const BoxConstraints(maxWidth: 450),
|
||||
width: 450,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
right: BorderSide(
|
||||
color: Colors.grey.withOpacity(.1),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: indexStack,
|
||||
),
|
||||
Expanded(
|
||||
child: content,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildIndexStack() {
|
||||
return Obx(
|
||||
() => IndexedStack(
|
||||
key: controller.indexKey,
|
||||
index: controller.index.value,
|
||||
children: controller.pages,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 子路由
|
||||
Widget _buildContentNavigator() {
|
||||
/// 拦截子路由的返回
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
onPopInvoked: (didPop) {
|
||||
if (!didPop) {
|
||||
if (Navigator.canPop(Get.context!)) {
|
||||
Get.back();
|
||||
return;
|
||||
} else if (AppNavigator.subNavigatorKey!.currentState!.canPop()) {
|
||||
AppNavigator.subNavigatorKey!.currentState!.pop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (controller.doubleClickExit) {
|
||||
controller.doubleClickTimer?.cancel();
|
||||
SystemNavigator.pop();
|
||||
return;
|
||||
}
|
||||
controller.setDoubleExitFlag();
|
||||
}
|
||||
},
|
||||
// onWillPop: () async {
|
||||
// if (Navigator.canPop(Get.context!)) {
|
||||
// return true;
|
||||
// }
|
||||
// if (AppNavigator.subNavigatorKey!.currentState!.canPop()) {
|
||||
// AppNavigator.subNavigatorKey!.currentState!.pop();
|
||||
// return false;
|
||||
// }
|
||||
// return true;
|
||||
// },
|
||||
child: ClipRect(
|
||||
child: Navigator(
|
||||
key: AppNavigator.subNavigatorKey,
|
||||
initialRoute: '/',
|
||||
onUnknownRoute: (settings) => GetPageRoute(
|
||||
page: () => const EmptyPage(),
|
||||
),
|
||||
observers: [
|
||||
SubNavigatorObserver(),
|
||||
],
|
||||
onGenerateRoute: AppPages.generateSubRoute,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 子路由监听
|
||||
class SubNavigatorObserver extends NavigatorObserver {
|
||||
@override
|
||||
void didPush(Route route, Route? previousRoute) {
|
||||
super.didPush(route, previousRoute);
|
||||
if (previousRoute != null) {
|
||||
var routeName = route.settings.name ?? "";
|
||||
AppNavigator.currentContentRouteName = routeName;
|
||||
Get.find<IndexController>().showContent.value = routeName != '/';
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didPop(Route route, Route? previousRoute) {
|
||||
super.didPop(route, previousRoute);
|
||||
|
||||
var routeName = previousRoute?.settings.name ?? "";
|
||||
AppNavigator.currentContentRouteName = routeName;
|
||||
Get.find<IndexController>().showContent.value = routeName != '/';
|
||||
}
|
||||
}
|
||||
150
lib/modules/index/windows_index_page.dart
Normal file
150
lib/modules/index/windows_index_page.dart
Normal file
@@ -0,0 +1,150 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart' as fluent;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dmzj/app/platform_utils.dart';
|
||||
import 'package:flutter_dmzj/modules/common/empty_page.dart';
|
||||
import 'package:flutter_dmzj/modules/index/index_controller.dart';
|
||||
import 'package:flutter_dmzj/routes/app_navigator.dart';
|
||||
import 'package:flutter_dmzj/routes/app_pages.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:remixicon/remixicon.dart';
|
||||
|
||||
/// Windows平台专用导航页面 - 使用Fluent UI的NavigationView
|
||||
class WindowsIndexPage extends GetView<IndexController> {
|
||||
const WindowsIndexPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return fluent.FluentTheme(
|
||||
data: PlatformUtils.getFluentTheme(context),
|
||||
child: Obx(
|
||||
() => fluent.NavigationView(
|
||||
paneBodyBuilder: (item, body) {
|
||||
// Builder ensures ctx is INSIDE the FluentTheme ancestor tree
|
||||
return Builder(
|
||||
builder: (ctx) => KeyedSubtree(
|
||||
key: const ValueKey('windows_main_content'),
|
||||
child: _buildMasterDetail(ctx),
|
||||
),
|
||||
);
|
||||
},
|
||||
pane: fluent.NavigationPane(
|
||||
selected: controller.index.value,
|
||||
onChanged: controller.setIndex,
|
||||
displayMode: fluent.PaneDisplayMode.auto,
|
||||
indicator: const fluent.StickyNavigationIndicator(),
|
||||
items: [
|
||||
fluent.PaneItem(
|
||||
icon: const Icon(Remix.bear_smile_line),
|
||||
title: const Text('漫画'),
|
||||
body: const SizedBox.shrink(),
|
||||
),
|
||||
fluent.PaneItem(
|
||||
icon: const Icon(Remix.article_line),
|
||||
title: const Text('资讯'),
|
||||
body: const SizedBox.shrink(),
|
||||
),
|
||||
fluent.PaneItem(
|
||||
icon: const Icon(Remix.book_open_line),
|
||||
title: const Text('轻小说'),
|
||||
body: const SizedBox.shrink(),
|
||||
),
|
||||
],
|
||||
footerItems: [
|
||||
fluent.PaneItem(
|
||||
icon: const Icon(Remix.user_smile_line),
|
||||
title: const Text('我的'),
|
||||
body: const SizedBox.shrink(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 主内容区:section列表(左) + 子路由详情(右)
|
||||
/// 使用Material主题颜色,避免FluentTheme.of()需要特定祖先
|
||||
Widget _buildMasterDetail(BuildContext context) {
|
||||
final materialTheme = Theme.of(context);
|
||||
final isDark = materialTheme.brightness == Brightness.dark;
|
||||
// 使用Material主题颜色衍生背景色
|
||||
final scaffoldBg = materialTheme.scaffoldBackgroundColor;
|
||||
final panelBg = isDark ? const Color(0xff202020) : const Color(0xfff0f0f0);
|
||||
final dividerColor = materialTheme.dividerColor;
|
||||
return ColoredBox(
|
||||
color: scaffoldBg,
|
||||
child: Row(
|
||||
children: [
|
||||
// 左侧:各模块首页(IndexedStack切换)
|
||||
SizedBox(
|
||||
width: 450,
|
||||
child: ColoredBox(
|
||||
color: panelBg,
|
||||
child: Obx(
|
||||
() => IndexedStack(
|
||||
key: controller.indexKey,
|
||||
index: controller.index.value,
|
||||
children: controller.pages,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// 分隔线
|
||||
Container(width: 1, color: dividerColor),
|
||||
// 右侧:子路由(详情页、阅读器等)
|
||||
Expanded(
|
||||
child: _buildContentNavigator(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 子路由导航器(处理详情页、阅读器等)
|
||||
Widget _buildContentNavigator() {
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
onPopInvoked: (didPop) {
|
||||
if (!didPop) {
|
||||
if (Navigator.canPop(Get.context!)) {
|
||||
Get.back();
|
||||
return;
|
||||
}
|
||||
if (AppNavigator.subNavigatorKey!.currentState!.canPop()) {
|
||||
AppNavigator.subNavigatorKey!.currentState!.pop();
|
||||
}
|
||||
}
|
||||
},
|
||||
child: ClipRect(
|
||||
child: Navigator(
|
||||
key: AppNavigator.subNavigatorKey,
|
||||
initialRoute: '/',
|
||||
onUnknownRoute: (settings) => GetPageRoute(
|
||||
page: () => const EmptyPage(),
|
||||
),
|
||||
observers: [WindowsSubNavigatorObserver()],
|
||||
onGenerateRoute: AppPages.generateSubRoute,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Windows子路由监听(不需要更新showContent,因为采用固定master-detail布局)
|
||||
class WindowsSubNavigatorObserver extends NavigatorObserver {
|
||||
@override
|
||||
void didPush(Route route, Route? previousRoute) {
|
||||
super.didPush(route, previousRoute);
|
||||
if (previousRoute != null) {
|
||||
final routeName = route.settings.name ?? '';
|
||||
AppNavigator.currentContentRouteName = routeName;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didPop(Route route, Route? previousRoute) {
|
||||
super.didPop(route, previousRoute);
|
||||
final routeName = previousRoute?.settings.name ?? '';
|
||||
AppNavigator.currentContentRouteName = routeName;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user