app.dart 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter/services.dart';
  3. import 'package:get/get.dart';
  4. import 'package:nomo/config/theme/theme_extensions/theme_extension.dart';
  5. import 'package:pull_to_refresh_flutter3/pull_to_refresh_flutter3.dart';
  6. import '../config/theme/ix_theme.dart';
  7. import '../config/translations/localization_service.dart';
  8. import '../config/translations/strings_enum.dart';
  9. import '../utils/developer/ix_developer_tools.dart';
  10. import '../utils/device_manager.dart';
  11. import '../utils/ix_back_button_dispatcher.dart';
  12. import 'components/ix_snackbar.dart';
  13. import 'constants/configs.dart';
  14. import 'data/sp/ix_sp.dart';
  15. import 'routes/app_pages.dart';
  16. import 'package:flutter_screenutil/flutter_screenutil.dart';
  17. import 'widgets/click_opacity.dart';
  18. import 'widgets/gradient_circle_header.dart';
  19. import 'widgets/triple_tap_detector.dart';
  20. class App extends StatelessWidget {
  21. const App({super.key});
  22. @override
  23. Widget build(BuildContext context) {
  24. return ScreenUtilInit(
  25. designSize: const Size(375, 812),
  26. minTextAdapt: true,
  27. splitScreenMode: true,
  28. useInheritedMediaQuery: true,
  29. // 只在屏幕尺寸真正变化时才重建(如旋转屏幕),而不是每次 MediaQuery 变化都重建
  30. rebuildFactor: (old, data) => old.size != data.size,
  31. builder: (context, widget) => RefreshConfiguration(
  32. headerBuilder: () =>
  33. GradientCircleHeader(), // 配置默认页眉指示器。如果您每个页面都有相同的页眉指示器,您需要设置此
  34. footerBuilder: () => ClassicFooter(), // 配置默认底部指示器
  35. headerTriggerDistance: 80.0, // 标题触发刷新触发距离
  36. springDescription: SpringDescription(
  37. mass: 1,
  38. stiffness: 1000,
  39. damping: 100,
  40. ), // 设置没有回弹动画,属性含义请参阅Flutter API
  41. maxOverScrollExtent: 40, //头部最大拖动范围。如果发生超出视图区域的情况,请设置此属性
  42. maxUnderScrollExtent: 0, // 底部最大拖动范围
  43. enableScrollWhenRefreshCompleted:
  44. true, //此属性与PageView和TabBarView不兼容。如果您需要TabBarView左右滑动,则需要将其设置为true。
  45. enableLoadingWhenFailed: true, //在负载失败的情况下,用户仍然可以通过手势下拉触发更多负载。
  46. hideFooterWhenNotFull: false, //禁用上拉加载更多功能,当视口小于一屏时
  47. enableBallisticLoad: true, //触发通过BallisticScrollActivity加载更多
  48. child: _buildMaterialApp(),
  49. ),
  50. );
  51. }
  52. Widget _buildMaterialApp() {
  53. // 只在初始化时读取一次主题设置,后续通过 Get.changeThemeMode() 动态切换
  54. final initialThemeMode = ReactiveTheme.isLightTheme
  55. ? ThemeMode.light
  56. : ThemeMode.dark;
  57. return GetMaterialApp.router(
  58. title: 'ixVPN',
  59. useInheritedMediaQuery: true,
  60. debugShowCheckedModeBanner: Configs.debug,
  61. backButtonDispatcher: IXBackButtonDispatcher(),
  62. getPages: AppPages.routes,
  63. theme: IXTheme.getThemeData(isLight: true),
  64. darkTheme: IXTheme.getThemeData(isLight: false),
  65. themeMode: initialThemeMode,
  66. fallbackLocale: LocalizationService.defaultLanguage,
  67. locale: IXSP.getCurrentLocal(),
  68. translations: LocalizationService.getInstance(),
  69. builder: (context, widget) => _buildThemedApp(context, widget),
  70. );
  71. }
  72. Widget _buildThemedApp(BuildContext context, Widget? widget) {
  73. return MediaQuery(
  74. data: MediaQuery.of(context).copyWith(textScaler: TextScaler.noScaling),
  75. child: Stack(
  76. children: [
  77. widget!,
  78. Positioned(
  79. top: 0,
  80. right: 0,
  81. width: 100,
  82. height: 100,
  83. child: TripleTapDetector(
  84. requiredTaps: 10,
  85. onTripleTap: () async {
  86. Clipboard.setData(
  87. ClipboardData(text: await DeviceManager.getDeviceId()),
  88. );
  89. IXSnackBar.showIXSnackBar(
  90. title: Strings.copied.tr,
  91. message: await DeviceManager.getDeviceId(),
  92. );
  93. },
  94. child: const SizedBox.shrink(), // 不显示任何东西
  95. ),
  96. ),
  97. Positioned(
  98. top: 0,
  99. left: 0,
  100. width: 100,
  101. height: 100,
  102. child: TripleTapDetector(
  103. requiredTaps: 10,
  104. onTripleTap: () async {
  105. IXDeveloperTools.show();
  106. },
  107. child: const SizedBox.shrink(), // 不显示任何东西
  108. ),
  109. ),
  110. if (Configs.debug)
  111. Positioned(
  112. bottom: 200,
  113. right: 10,
  114. child: ClickOpacity(
  115. child: Container(
  116. width: 40,
  117. height: 40,
  118. decoration: BoxDecoration(
  119. color: Get.reactiveTheme.primaryColor.withValues(
  120. alpha: 0.5,
  121. ),
  122. borderRadius: BorderRadius.circular(40),
  123. ),
  124. child: Icon(Icons.adb, color: Get.reactiveTheme.primaryColor),
  125. ),
  126. onTap: () {
  127. // 使用简化版开发者工具
  128. IXDeveloperTools.show();
  129. },
  130. ),
  131. ),
  132. Positioned(
  133. bottom: 300,
  134. right: 10,
  135. child: ClickOpacity(
  136. child: Container(
  137. width: 40,
  138. height: 40,
  139. decoration: BoxDecoration(
  140. color: Get.reactiveTheme.primaryColor.withValues(alpha: 0.5),
  141. borderRadius: BorderRadius.circular(40),
  142. ),
  143. child: Icon(
  144. ReactiveTheme.isLightTheme ? Icons.sunny : Icons.nightlight,
  145. color: Get.reactiveTheme.primaryColor,
  146. ),
  147. ),
  148. onTap: () {
  149. // 切换主题
  150. IXTheme.changeTheme();
  151. },
  152. ),
  153. ),
  154. ],
  155. ),
  156. );
  157. }
  158. }