app.dart 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  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 '../utils/log/logger.dart';
  13. import 'components/ix_snackbar.dart';
  14. import 'constants/configs.dart';
  15. import 'data/sp/ix_sp.dart';
  16. import 'routes/app_pages.dart';
  17. import 'package:flutter_screenutil/flutter_screenutil.dart';
  18. import 'widgets/click_opacity.dart';
  19. import 'widgets/gradient_circle_header.dart';
  20. import 'widgets/triple_tap_detector.dart';
  21. class App extends StatelessWidget {
  22. const App({super.key});
  23. @override
  24. Widget build(BuildContext context) {
  25. return ScreenUtilInit(
  26. designSize: const Size(375, 812),
  27. minTextAdapt: true,
  28. splitScreenMode: true,
  29. useInheritedMediaQuery: true,
  30. rebuildFactor: (old, data) => true,
  31. builder: (context, widget) => RefreshConfiguration(
  32. headerBuilder: () =>
  33. GradientCircleHeader(), // Configure the default header indicator. If you have the same header indicator for each page, you need to set this
  34. footerBuilder: () =>
  35. ClassicFooter(), // Configure default bottom indicator
  36. headerTriggerDistance: 100.0, // header trigger refresh trigger distance
  37. springDescription: SpringDescription(
  38. mass: 1,
  39. stiffness: 1000,
  40. damping: 100,
  41. ), // custom spring back animate,the props meaning see the flutter api
  42. maxOverScrollExtent:
  43. 100, //The maximum dragging range of the head. Set this property if a rush out of the view area occurs
  44. maxUnderScrollExtent: 0, // Maximum dragging range at the bottom
  45. enableScrollWhenRefreshCompleted:
  46. true, //This property is incompatible with PageView and TabBarView. If you need TabBarView to slide left and right, you need to set it to true.
  47. enableLoadingWhenFailed:
  48. true, //In the case of load failure, users can still trigger more loads by gesture pull-up.
  49. hideFooterWhenNotFull:
  50. false, // Disable pull-up to load more functionality when Viewport is less than one screen
  51. enableBallisticLoad:
  52. true, // trigger load more by BallisticScrollActivity
  53. child: _buildMaterialApp(),
  54. ),
  55. );
  56. }
  57. Widget _buildMaterialApp() {
  58. bool isLightTheme = IXSP.getThemeIsLight();
  59. log('App', 'isLightTheme: $isLightTheme');
  60. return GetMaterialApp.router(
  61. title: 'ixVPN',
  62. useInheritedMediaQuery: true,
  63. debugShowCheckedModeBanner: Configs.debug,
  64. backButtonDispatcher: IXBackButtonDispatcher(),
  65. getPages: AppPages.routes,
  66. theme: IXTheme.getThemeData(isLight: true),
  67. darkTheme: IXTheme.getThemeData(isLight: false),
  68. themeMode: isLightTheme ? ThemeMode.light : ThemeMode.dark,
  69. fallbackLocale: LocalizationService.defaultLanguage,
  70. locale: IXSP.getCurrentLocal(),
  71. translations: LocalizationService.getInstance(),
  72. builder: (context, widget) => _buildThemedApp(context, widget),
  73. );
  74. }
  75. Widget _buildThemedApp(BuildContext context, Widget? widget) {
  76. return MediaQuery(
  77. data: MediaQuery.of(context).copyWith(textScaler: TextScaler.noScaling),
  78. child: Stack(
  79. children: [
  80. widget!,
  81. Positioned(
  82. top: 0,
  83. right: 0,
  84. width: 100,
  85. height: 100,
  86. child: TripleTapDetector(
  87. requiredTaps: 10,
  88. onTripleTap: () async {
  89. Clipboard.setData(
  90. ClipboardData(text: await DeviceManager.getDeviceId()),
  91. );
  92. IXSnackBar.showIXSnackBar(
  93. title: Strings.copied.tr,
  94. message: await DeviceManager.getDeviceId(),
  95. );
  96. },
  97. child: const SizedBox.shrink(), // 不显示任何东西
  98. ),
  99. ),
  100. Positioned(
  101. top: 0,
  102. left: 0,
  103. width: 100,
  104. height: 100,
  105. child: TripleTapDetector(
  106. requiredTaps: 10,
  107. onTripleTap: () async {
  108. IXDeveloperTools.show();
  109. },
  110. child: const SizedBox.shrink(), // 不显示任何东西
  111. ),
  112. ),
  113. if (Configs.debug)
  114. Positioned(
  115. bottom: 200,
  116. right: 10,
  117. child: ClickOpacity(
  118. child: Container(
  119. width: 40,
  120. height: 40,
  121. decoration: BoxDecoration(
  122. color: Get.reactiveTheme.primaryColor.withValues(
  123. alpha: 0.5,
  124. ),
  125. borderRadius: BorderRadius.circular(40),
  126. ),
  127. child: Icon(Icons.adb, color: Get.reactiveTheme.primaryColor),
  128. ),
  129. onTap: () {
  130. // 使用简化版开发者工具
  131. IXDeveloperTools.show();
  132. },
  133. ),
  134. ),
  135. Positioned(
  136. bottom: 300,
  137. right: 10,
  138. child: ClickOpacity(
  139. child: Container(
  140. width: 40,
  141. height: 40,
  142. decoration: BoxDecoration(
  143. color: Get.reactiveTheme.primaryColor.withValues(alpha: 0.5),
  144. borderRadius: BorderRadius.circular(40),
  145. ),
  146. child: Icon(
  147. ReactiveTheme.isLightTheme ? Icons.sunny : Icons.nightlight,
  148. color: Get.reactiveTheme.primaryColor,
  149. ),
  150. ),
  151. onTap: () {
  152. // 切换主题
  153. IXTheme.changeTheme();
  154. },
  155. ),
  156. ),
  157. ],
  158. ),
  159. );
  160. }
  161. }