From f233f1c642f142a3a2218bfe977c182dfb95210e Mon Sep 17 00:00:00 2001 From: phoenix--zhang <471155679@qq.com> Date: Fri, 3 Jul 2026 09:24:19 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=B8=BB=E9=A1=B5=E9=9D=A2=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/main/app_bottom.png | Bin 0 -> 18489 bytes assets/main/space_img.png | Bin 0 -> 12705 bytes ios/Runner.xcodeproj/project.pbxproj | 112 +++++++ .../contents.xcworkspacedata | 3 + lib/common/const/const_color.dart | 19 ++ lib/common/const/const_string.dart | 0 lib/common/help/app_nav.dart | 117 ++++++++ lib/common/help/help_image.dart | 89 ++++++ lib/main.dart | 242 ++++++++------- lib/pages/collect/pages/collection_page.dart | 18 ++ lib/pages/first/pages/first_page.dart | 18 ++ lib/pages/my/pages/my_page.dart | 18 ++ lib/pages/play/pages/play_page.dart | 18 ++ lib/root/lib_export.dart | 7 + lib/root/root_page.dart | 135 +++++++++ lib/tools/util/app_screen.dart | 51 ++++ lib/tools/widgets/extend_widget.dart | 158 ++++++++++ lib/tools/widgets/novy_com_widget.dart | 277 ++++++++++++++++++ lib/tools/widgets/novy_keep_alive.dart | 25 ++ pubspec.lock | 88 ++++++ pubspec.yaml | 4 + 21 files changed, 1295 insertions(+), 104 deletions(-) create mode 100644 assets/main/app_bottom.png create mode 100644 assets/main/space_img.png create mode 100644 lib/common/const/const_color.dart create mode 100644 lib/common/const/const_string.dart create mode 100644 lib/common/help/app_nav.dart create mode 100644 lib/common/help/help_image.dart create mode 100644 lib/pages/collect/pages/collection_page.dart create mode 100644 lib/pages/first/pages/first_page.dart create mode 100644 lib/pages/my/pages/my_page.dart create mode 100644 lib/pages/play/pages/play_page.dart create mode 100644 lib/root/lib_export.dart create mode 100644 lib/root/root_page.dart create mode 100644 lib/tools/util/app_screen.dart create mode 100644 lib/tools/widgets/extend_widget.dart create mode 100644 lib/tools/widgets/novy_com_widget.dart create mode 100644 lib/tools/widgets/novy_keep_alive.dart diff --git a/assets/main/app_bottom.png b/assets/main/app_bottom.png new file mode 100644 index 0000000000000000000000000000000000000000..eb3fa7ca82809ebb50a7e116e007d3abdfdfa565 GIT binary patch literal 18489 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 FV1oYBk?jBg literal 0 HcmV?d00001 diff --git a/assets/main/space_img.png b/assets/main/space_img.png new file mode 100644 index 0000000000000000000000000000000000000000..e18f2734ad5a7907390cb041c16b58fcf3fe29a2 GIT binary patch literal 12705 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 /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -241,6 +311,45 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 6C235EC4F300ECD7625CDB9A /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 96179195EAB296DE04B2BB0F /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -383,6 +492,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 2041FD845ED16692F904D1BB /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -400,6 +510,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 0B14B00D5159CDD6C80C9153 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -415,6 +526,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 5AE4FDA3B70EE917ED395F4E /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/lib/common/const/const_color.dart b/lib/common/const/const_color.dart new file mode 100644 index 0000000..f39406c --- /dev/null +++ b/lib/common/const/const_color.dart @@ -0,0 +1,19 @@ + +import 'dart:ui'; + +class ConstColor { + + // #100C08 + static Color themeColor = Color(0xff100C08); + // #EEEEEE + static Color btWhite = Color(0xffEEEEEE); + // #89F011 + static Color btGreen = Color(0xff89F011); + + static Color withValue(String value) { + String colorValue = value.replaceAll('#', ''); + String res = '0xff$colorValue'; + int valueInt = int.parse(res); + return Color(valueInt); + } +} \ No newline at end of file diff --git a/lib/common/const/const_string.dart b/lib/common/const/const_string.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/common/help/app_nav.dart b/lib/common/help/app_nav.dart new file mode 100644 index 0000000..a79da83 --- /dev/null +++ b/lib/common/help/app_nav.dart @@ -0,0 +1,117 @@ +import 'package:get/get.dart'; +import 'package:flutter/material.dart'; + +/// navigator +class AppNav { + /// clear all route + static Future? offAllNamed( + String newRouteName, { + Map? arguments, + RoutePredicate? predicate, + String? tag, + Map? parameters, + }) { + return Get.offAllNamed( + newRouteName, + arguments: arguments, + predicate: predicate, + parameters: {...?parameters}, + ); + } + + static Future? off(Widget page, {arguments, preventDuplicates}) { + return Get.off( + () => page, + arguments: arguments, + preventDuplicates: preventDuplicates ?? false, + ); + } + + static Future? offAll(Widget page, {arguments, preventDuplicates}) { + return Get.offAll(() => page, arguments: arguments); + } + + static Future? offNamed( + String newRouteName, { + arguments, + preventDuplicates, + }) { + return Get.offNamed( + newRouteName, + arguments: arguments, + preventDuplicates: preventDuplicates ?? true, + ); + } + + /// to name + static Future? toNamed( + String newRouteName, { + arguments, + String? tag, + Map? parameters, + }) { + return Get.toNamed( + newRouteName, + arguments: arguments, + parameters: {...?parameters}, + ); + } + + /// to route + static Future? to(Widget page, {arguments, binding}) { + return Get.to( + () => page, + arguments: arguments, + binding: binding, + preventDuplicates: false, + ); + } + + static void _until({String? routeName, T? result}) { + if (result == null) { + Get.back(result: result); + return; + } + // Get.back(result: result); + // + if (routeName == null) { + Get.back(result: result); + return; + } + } + + /// back pre + /// [result] + static void back({T? result, String? routeName}) { + _until(routeName: routeName, result: result); + } + + /// show dialog + static Future showDialog( + Widget child, { + bool barrierDismissible = false, + }) { + return Get.dialog( + Center(child: child), + barrierDismissible: barrierDismissible, + ); + } + + /// show bottom sheet + static Future showBottom( + Widget child, { + Color? safeAreaColor, + + double? borderRadius, + bool isDismissible = false, + bool isScrollControlled = false, + bool? enableDrag, + }) { + return Get.bottomSheet( + child, + isScrollControlled: isScrollControlled, + enableDrag: enableDrag ?? true, + isDismissible: isDismissible, + ); + } +} diff --git a/lib/common/help/help_image.dart b/lib/common/help/help_image.dart new file mode 100644 index 0000000..6116356 --- /dev/null +++ b/lib/common/help/help_image.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; + +class ConstImage { + ConstImage._(); + + /// main + static const String appBottom = "assets/main/app_bottom.png"; + static const String pageBg = "assets/main/page_bg.png"; + static const String btHomeUnsel = "assets/main/bt_home_unsel.png"; + static const String btPlayUnsel = "assets/main/bt_play_unsel.png"; + static const String btCollectUnsel = "assets/main/bt_collect_unsel.png"; + static const String btMineUnsel = "assets/main/bt_mine_unsel.png"; + static const String btHomeSel = "assets/main/bt_home_seled.png"; + static const String btPlaySel = "assets/main/bt_play_seled.png"; + static const String btCollectSel = "assets/main/bt_collect_seled.png"; + static const String btMineSel = "assets/main/bt_mine_seled.png"; + static const String spaceImg = "assets/main/space_img.png"; + + +} + +class HelpImage { + static Widget asset( + String path, { + BoxFit? fit, + double? width, + double? height, + Color? color, + bool isDartData = false, + String? dartPath, + }) { + + return Image.asset( + path, + fit: fit, + width: width, + height: height, + color: color, + ); + } + + static Widget networkRadius( + String path, { + BoxFit? fit, + double? width, + double? height, + BoxShape? shape, + BorderRadius? borderRadius, + Widget? errorWidget, + }) { + return ClipRRect( + borderRadius: borderRadius ?? BorderRadius.circular(3), + child: network( + path, + fit: fit, + width: width, + height: height, + shape: shape, + errorWidget: errorWidget, + ), + ); + } + + /// load network + static Widget network( + String path, { + BoxFit? fit, + double? width, + double? height, + Color? color, + BoxShape? shape, + BorderRadius? borderRadius, + Widget? errorWidget, + }) { + + return FadeInImage.assetNetwork( + placeholder: ConstImage.spaceImg, + image: path, + width: width, height: height, fit: BoxFit.cover, + imageErrorBuilder: (context, error, stackTrace) { + return asset( + ConstImage.spaceImg, + fit: BoxFit.fill, + width: width, height: height + ); + }, + ); + } +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 244a702..763945d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,9 +1,145 @@ import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; +import 'package:novyronst/root/lib_export.dart'; +import 'package:novyronst/root/root_page.dart'; void main() { - runApp(const MyApp()); + + WidgetsFlutterBinding.ensureInitialized(); + + runApp(const RootApp()); } + + + +class RootApp extends StatefulWidget { + const RootApp({ + super.key, + this.home, + this.locale + }); + + final Widget? home; + final Locale? locale; + + @override + State createState() { + return _RootAppState(); + } +} + +class _RootAppState extends State with WidgetsBindingObserver { + + bool _appInForeground = true; // + + + @override + Widget build(BuildContext context) { + return ScreenUtilInit( + designSize: Size(375, 812), + child: GetMaterialApp( + debugShowCheckedModeBanner: false, + // translations: AppLanguage(), + locale: widget.locale ?? Get.deviceLocale, + fallbackLocale: const Locale('en'), + initialBinding: AppBindings(), + // initialRoute: SkyPages.initial, + // getPages: SkyPages.routes, + theme: ThemeData( + colorScheme: ColorScheme.fromSeed( + seedColor: ConstColor.themeColor, + ), + appBarTheme: AppBarTheme(elevation: 0, centerTitle: true), + splashColor: Colors.transparent, + useMaterial3: true, + scaffoldBackgroundColor: Colors.white, + ), + builder: FlutterSmartDialog.init( + builder: (context, child) { + return MediaQuery( + data: MediaQuery.of( + context, + ).copyWith(textScaler: TextScaler.linear(1.0)), + child: child!, + ); + }, + ), + home: widget.home ?? RootPage(), + onInit: () {}, + ), + ); + } + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + // _startPeriodicOnlineReport(); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + super.didChangeAppLifecycleState(state); + + switch (state) { + case AppLifecycleState.inactive: + case AppLifecycleState.paused: + // 应用进入后台或不活跃状态 + if (_appInForeground) { + print('💛应用不活跃'); + _appInForeground = false; + _onAppPaused(); + } + break; + case AppLifecycleState.resumed: + // 应用回到前台 + if (!_appInForeground) { + print('💚应用活跃'); + _appInForeground = true; + _onAppResumed(); + } + break; + case AppLifecycleState.detached: + case AppLifecycleState.hidden: + break; + } + } + + // 应用进入后台时的处理 + void _onAppPaused() { + try { + // EventClient.eventOLeaveApp(); + } catch (err) { print(err.toString()); } + } + + /// 应用回到前台时的处理 + void _onAppResumed() { + try { + // EventClient.eventEnterApp(); + // EventClient.eventOnline(); + } catch (err) { print(err.toString()); } + } +} + +class AppBindings extends Bindings { + + @override + void dependencies() { + // Get.lazyPut(() => UserController()); + } +} + + + class MyApp extends StatelessWidget { const MyApp({super.key}); @@ -13,110 +149,8 @@ class MyApp extends StatelessWidget { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( - // This is the theme of your application. - // - // TRY THIS: Try running your application with "flutter run". You'll see - // the application has a purple toolbar. Then, without quitting the app, - // try changing the seedColor in the colorScheme below to Colors.green - // and then invoke "hot reload" (save your changes or press the "hot - // reload" button in a Flutter-supported IDE, or press "r" if you used - // the command line to start the app). - // - // Notice that the counter didn't reset back to zero; the application - // state is not lost during the reload. To reset the state, use hot - // restart instead. - // - // This works for code too, not just values: Most code changes can be - // tested with just a hot reload. colorScheme: .fromSeed(seedColor: Colors.deepPurple), ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), ); } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } - - @override - Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // TRY THIS: Try changing the color here to a specific color (to - // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar - // change color while the other colors stay the same. - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - // - // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" - // action in the IDE, or press "p" in the console), to see the - // wireframe for each widget. - mainAxisAlignment: .center, - children: [ - const Text('You have pushed the button this many times:'), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), - ); - } -} +} \ No newline at end of file diff --git a/lib/pages/collect/pages/collection_page.dart b/lib/pages/collect/pages/collection_page.dart new file mode 100644 index 0000000..8493b0a --- /dev/null +++ b/lib/pages/collect/pages/collection_page.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; + +class CollectionPage extends StatefulWidget { + const CollectionPage({super.key}); + + @override + State createState() { + return _RootPageState(); + } +} + +class _RootPageState extends State { + + @override + Widget build(BuildContext context) { + return Scaffold(); + } +} \ No newline at end of file diff --git a/lib/pages/first/pages/first_page.dart b/lib/pages/first/pages/first_page.dart new file mode 100644 index 0000000..b655946 --- /dev/null +++ b/lib/pages/first/pages/first_page.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; + +class FirstPage extends StatefulWidget { + const FirstPage({super.key}); + + @override + State createState() { + return _RootPageState(); + } +} + +class _RootPageState extends State { + + @override + Widget build(BuildContext context) { + return Scaffold(); + } +} \ No newline at end of file diff --git a/lib/pages/my/pages/my_page.dart b/lib/pages/my/pages/my_page.dart new file mode 100644 index 0000000..df8f61f --- /dev/null +++ b/lib/pages/my/pages/my_page.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; + +class MyPage extends StatefulWidget { + const MyPage({super.key}); + + @override + State createState() { + return _RootPageState(); + } +} + +class _RootPageState extends State { + + @override + Widget build(BuildContext context) { + return Scaffold(); + } +} \ No newline at end of file diff --git a/lib/pages/play/pages/play_page.dart b/lib/pages/play/pages/play_page.dart new file mode 100644 index 0000000..6c0f24c --- /dev/null +++ b/lib/pages/play/pages/play_page.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; + +class PlayPage extends StatefulWidget { + const PlayPage({super.key}); + + @override + State createState() { + return _RootPageState(); + } +} + +class _RootPageState extends State { + + @override + Widget build(BuildContext context) { + return Scaffold(); + } +} \ No newline at end of file diff --git a/lib/root/lib_export.dart b/lib/root/lib_export.dart new file mode 100644 index 0000000..a875b71 --- /dev/null +++ b/lib/root/lib_export.dart @@ -0,0 +1,7 @@ + +export 'package:novyronst/common/const/const_color.dart'; +export 'package:novyronst/common/const/const_string.dart'; +export 'package:novyronst/common/help/help_image.dart'; + + +export 'package:novyronst/tools/util/app_screen.dart'; \ No newline at end of file diff --git a/lib/root/root_page.dart b/lib/root/root_page.dart new file mode 100644 index 0000000..f52dee0 --- /dev/null +++ b/lib/root/root_page.dart @@ -0,0 +1,135 @@ +import 'package:flutter/material.dart'; +import 'package:novyronst/common/help/help_image.dart'; +import 'package:novyronst/pages/collect/pages/collection_page.dart'; +import 'package:novyronst/pages/first/pages/first_page.dart'; +import 'package:novyronst/pages/my/pages/my_page.dart'; +import 'package:novyronst/pages/play/pages/play_page.dart'; +import 'package:novyronst/root/lib_export.dart'; +import 'package:novyronst/tools/widgets/extend_widget.dart'; +import 'package:novyronst/tools/widgets/novy_com_widget.dart'; +import 'package:novyronst/tools/widgets/novy_keep_alive.dart'; + +class RootPage extends StatefulWidget { + const RootPage({super.key}); + + @override + State createState() { + return _RootPageState(); + } +} + +class _RootPageState extends State { + + final _pageController = PageController(); + final List _pages = [ + NovyKeepAlive(child: FirstPage()), + NovyKeepAlive(child: PlayPage()), + CollectionPage(), + NovyKeepAlive(child: MyPage()), + ]; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Stack( + children: [ + PageView( + physics: const NeverScrollableScrollPhysics(), + controller: _pageController, + children: _pages, + ), + Positioned( + left: 0, right: 0, bottom: 0, + child: SafeArea( + top: false, + child: AppBottomView( + onSelectedIndex: (index) { + _pageController.jumpToPage(index); + }, + ), + ), + ) + ], + ) + ); + } +} + +/// 底部tabbar +class AppBottomView extends StatefulWidget { + const AppBottomView({super.key, + required this.onSelectedIndex, + }); + + final Function(int) onSelectedIndex; + + @override + State createState() { + return _AppBottomViewState(); + } +} + +class _AppBottomViewState extends State { + + final List _unselectedIcons = [ + ConstImage.btHomeUnsel, ConstImage.btPlayUnsel, + ConstImage.btCollectUnsel, ConstImage.btMineUnsel + ]; + final List _selectedIcons = [ + ConstImage.btHomeSel, ConstImage.btPlaySel, + ConstImage.btCollectSel, ConstImage.btMineSel + ]; + final List _titles = ['Home', 'Play', 'Collect', 'Me']; + int _currentIndex = 0; + + @override + Widget build(BuildContext context) { + const double _height = 65; + return Container( + height: _height, + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Stack( + children: [ + HelpImage.asset(ConstImage.appBottom, + width: AppScreen.screenWidth-32, height: _height, + fit: BoxFit.fill, + ), + Positioned.fill( + child: Padding(padding: const EdgeInsets.symmetric(horizontal: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: List.generate(_unselectedIcons.length, (index) { + return _buildTabItem(_titles[index], index, onTap: () { + setState(() { + _currentIndex = index; + }); + widget.onSelectedIndex(index); + + }); + }), + ), + ), + ) + ], + ), + ); + } + + Widget _buildTabItem(String title, int index, {required Function() onTap}) { + String icon = _currentIndex == index ? _selectedIcons[index] : _unselectedIcons[index]; + Color tcolor = _currentIndex == index ? ConstColor.btGreen : ConstColor.btWhite; + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 7), + width: 60, height: 70, + child: Column( + children: [ + HelpImage.asset(icon, width: 32, height: 32), + const SizedBox(height: 2), + NovyText(title, fontSize: 11, + color: tcolor, + ) + ], + ) + ).addInkWell(onTap: onTap); + } +} \ No newline at end of file diff --git a/lib/tools/util/app_screen.dart b/lib/tools/util/app_screen.dart new file mode 100644 index 0000000..fdd62d5 --- /dev/null +++ b/lib/tools/util/app_screen.dart @@ -0,0 +1,51 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +/// AppScreen class +class AppScreen { + static double get screenWidth => _viewSize.width; + static double get screenHeight => _viewSize.height; + // static double get screenWidth => MediaQuery.of(Get.context!).size.width; + // static double get screenHeight => MediaQuery.of(Get.context!).size.height; + + static Size size(BuildContext context) { + return MediaQuery.of(context).size; + } + + static bool get isDebug => kDebugMode; + + static double get statusBarHeight => Get.statusBarHeight / Get.pixelRatio; + + static double get bottomBarHeight => Get.bottomBarHeight / Get.pixelRatio; + + // static double get tabBarHeight => kToolbarHeight; + + static double get navBarHeight => kToolbarHeight + statusBarHeight; + + static bool get isDark => Get.isDarkMode; + + static bool get isAndroid { + return GetPlatform.isAndroid; + } + + static bool get isIos { + return GetPlatform.isIOS; + } + + static Size get _viewSize { + if (Get.context != null) { + return MediaQuery.of(Get.context!).size; + } + + final view = WidgetsBinding.instance.platformDispatcher.views.first; + return view.physicalSize / view.devicePixelRatio; + } + + static double aRatioWidth({double? h, double aspectRatio = 1}) { + h ??= screenHeight; + return screenHeight * 1 / aspectRatio; + } + + static double get keyboardHeight => Get.mediaQuery.viewInsets.bottom; +} diff --git a/lib/tools/widgets/extend_widget.dart b/lib/tools/widgets/extend_widget.dart new file mode 100644 index 0000000..0559a13 --- /dev/null +++ b/lib/tools/widgets/extend_widget.dart @@ -0,0 +1,158 @@ +import 'package:flutter/material.dart'; + +extension ExtendWidget on Widget { + + Widget addContainer({ + Color? color, + double? height, + double? width, + EdgeInsets? margin, + EdgeInsets? padding, + double? radius, + BorderRadius? borderRadius, + Border? border, + GestureTapCallback? onTap, + AlignmentGeometry? alignment, + Gradient? gradient, + List? boxShadow, + BoxConstraints? constraints, + Matrix4? transform, + Clip clipBehavior = Clip.none, + DecorationImage? image, + BlendMode? backgroundBlendMode, + BoxShape shape = BoxShape.rectangle, + double? minWidth, + double? maxWidth, + double? minHeight, + double? maxHeight, + + /// 打印 widget size 只在 debug 有效 + bool isPrintSize = false, + }) { + return Container( + width: width, + height: height, + margin: margin, + padding: padding, + alignment: alignment, + clipBehavior: clipBehavior, + constraints: + constraints ?? + BoxConstraints( + minWidth: minWidth ?? 0.0, + maxWidth: maxWidth ?? double.infinity, + minHeight: minHeight ?? 0.0, + maxHeight: maxHeight ?? double.infinity, + ), + transform: transform, + decoration: BoxDecoration( + color: color, + boxShadow: boxShadow, + backgroundBlendMode: backgroundBlendMode, + shape: shape, + image: image, + gradient: gradient, + border: border, + borderRadius: radius != null + ? BorderRadius.circular(radius) + : borderRadius, + ), + child: this, + ).addInkWell(onTap: onTap); + } + + Widget addPadding({EdgeInsets? padding}) { + return Padding(padding: padding ?? EdgeInsets.zero, child: this); + } + + /// 在当前Widget外层添加一个Align + /// + /// 通过此方法可以设置控件的对齐方式 + Widget addAlign({AlignmentGeometry alignment = Alignment.centerLeft}) { + return Align(alignment: alignment, child: this); + } + + Widget addCenter() { + return Center(child: this); + } + + /// 在当前Widget外层添加一个GestureDetector + /// + /// 通过此方法可以为控件添加点击事件处理函数 + Widget addGestureDetector({ + GestureTapCallback? onTap, + GestureTapCallback? onDoubleTap, + GestureLongPressCallback? onLongPress, + }) { + return GestureDetector( + onTap: onTap, + onDoubleTap: onDoubleTap, + onLongPress: onLongPress, + child: this, + ); + // return GestureDetectorDebounced( + // onTap: onTap, + // child: this, + // ); + } + + /// 在当前Widget外层添加一个Visibility + /// + /// 通过此方法可以控制控件的显示和隐藏 + Widget addVisibility({bool visible = true}) { + return Visibility(visible: visible, child: this); + } + + + /// 在当前Widget外层添加一个InkWell + /// + /// 通过此方法可以为控件添加点击事件处理函数 + Widget addInkWell({GestureTapCallback? onTap}) { + if (onTap == null) { + return this; + } + return InkWell(onTap: onTap, child: this); + } + + /// 在当前Widget外层添加一个SizedBox + /// + /// 通过此方法可以设置控件的宽高 + Widget addSize({double? width, double? height}) { + return SizedBox(width: width, height: height, child: this); + } + + /// 在当前Widget外层添加一个Opacity + /// + /// 通过此方法可以设置控件的透明度 + Widget addOpacity({double opacity = 1}) { + return Opacity(opacity: opacity, child: this); + } +} + +extension OnRenderObjectWidget on RenderObjectWidget { + SliverPadding addPaddingSliver({EdgeInsets? padding}) { + return SliverPadding(padding: padding ?? EdgeInsets.zero, sliver: this); + } + + DecoratedSliver addDecoratedSliver({ + Color? color, + BorderRadius? borderRadius, + Border? border, + BoxShape? shape, + Gradient? gradient, + List? boxShadow, + EdgeInsets? padding, + }) { + return DecoratedSliver( + decoration: BoxDecoration( + color: color ?? Colors.white, + border: border, + borderRadius: borderRadius, + boxShadow: boxShadow, + gradient: gradient, + shape: shape ?? BoxShape.rectangle, + ), + sliver: addPaddingSliver(padding: padding), + ); + } +} diff --git a/lib/tools/widgets/novy_com_widget.dart b/lib/tools/widgets/novy_com_widget.dart new file mode 100644 index 0000000..0fd1d8c --- /dev/null +++ b/lib/tools/widgets/novy_com_widget.dart @@ -0,0 +1,277 @@ +import 'package:flutter/material.dart'; +import 'package:novyronst/common/const/const_color.dart'; +import 'package:novyronst/common/help/app_nav.dart'; + +import 'extend_widget.dart'; + + +class NovyText extends StatelessWidget { + const NovyText( this.text, { + super.key, + this.color = Colors.white, + this.fontSize = 14, + this.shadow, + this.decoration, + this.decorationColor, + this.textAlign, + this.fontWeight, + this.maxLines, + this.lineHeight, + this.letterSpacing, + this.overflow = TextOverflow.ellipsis, + this.softWrap, + this.fontFamily, + }); + + + final String text; + final Color? color; + final double? fontSize; + final TextDecoration? decoration; + final Color? decorationColor; + final TextAlign? textAlign; + final FontWeight? fontWeight; + final int? maxLines; + final Shadow? shadow; + final double? lineHeight; + final double? letterSpacing; + final TextOverflow? overflow; + final bool? softWrap; + final String? fontFamily; + + + @override + Widget build(BuildContext context) { + final FontWeight resolvedWeight = fontWeight ?? FontWeight.normal; + + return Text(text, style: TextStyle( + fontSize: fontSize, + fontWeight: resolvedWeight, + decoration: decoration, + fontFamily: fontFamily ?? 'Inter', + color: color, + shadows: shadow == null ? null : [shadow!], + decorationColor: decorationColor, + height: lineHeight, + letterSpacing: letterSpacing, + ), maxLines: maxLines, + overflow: overflow, + softWrap: softWrap, + textAlign: textAlign, + ); + } + +} + + + + + + + + + + + + + + + + + + + + + + +class ComConfirmWidget extends StatelessWidget { + + final String? title; + final String? content; + final Widget? contentWidget; + final String? cancelStr, confirmStr; + final double? height; + final Function? onConfirmed; + final Function? onCanceled; + final bool? showCancel; + final bool? clickHideAlert; + + const ComConfirmWidget({ + super.key, + this.title, + this.content, + this.contentWidget, + this.cancelStr, + this.confirmStr, + this.height, + this.onConfirmed, + this.onCanceled, + this.showCancel = true, + this.clickHideAlert = true, + }); + + @override + Widget build(BuildContext context) { + return Container( + height: height ?? 160, + margin: EdgeInsets.only(left: 25, right: 25), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + ), + child: Column( + // mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 15,), + + title==null ? SizedBox() : Padding(padding: EdgeInsets.only(left:15, right: 15, bottom: 5), + child: NovyText(title!, fontSize: 16, color: Colors.black, + ) + ), + const SizedBox(height: 10), + Expanded( + child: Padding(padding: EdgeInsets.only(left:14, right: 14), + child: contentWidget ?? Center( + child: NovyText(content!, + fontSize: 15, color: ConstColor.themeColor, + maxLines: 3), + ), + ) + ), + + const SizedBox(height: 10), + Divider(thickness: 0.6, height: 1), + SizedBox( + height: 44, + child: IntrinsicHeight( + child: showCancel! ? Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + + TextButton(onPressed: () { + Navigator.pop(context); + onCanceled?.call(); + }, + child: Center( + child: NovyText(cancelStr ?? 'Cancel', + color: Colors.grey, fontSize: 14)) + ), + + VerticalDivider(width: 5, thickness: 0.6,), + + TextButton(onPressed: () { + + if (clickHideAlert == true) { + Navigator.pop(context); + } + onConfirmed?.call(); + }, + child: Center( + child:NovyText(confirmStr ?? 'Confirm', + color: ConstColor.themeColor, fontSize: 14)) + ), + ], + ) : TextButton(onPressed: () { + + Navigator.pop(context); + onConfirmed?.call(); + }, + child: Center( + child:NovyText(confirmStr ?? 'Confirm', + color: ConstColor.themeColor, fontSize: 14,)) + ), + ) + ) + ], + ), + ); + } +} + + + + + + + + +class ComSheetWidget extends StatelessWidget { + + final Function(int)? onSelected; + final String? title; + final List items; + final TextAlign? textAlign; + + const ComSheetWidget({super.key, + this.onSelected, + this.title, + this.textAlign = TextAlign.left, + required this.items, + }); + + @override + Widget build(BuildContext context) { + return SafeArea(child: _mainView()); + } + + _mainView() { + return Container( + padding: EdgeInsets.only(top: 10), + height: items.length>5 ? 360 : 40+items.length*47 + 50, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20) + ), + ), + child: _ifElseWidget(), + ); + } + + _ifElseWidget() { + if (title != null) { + return Column( + children: [ + const SizedBox(height: 6), + Text('$title', style: TextStyle( + color: Colors.black, fontSize: 16 + )), + const SizedBox(height: 5), + const Divider(height: 8, thickness: 0.6,), + // Divider(), + const SizedBox(height: 5), + Expanded(child: ListView.separated(itemBuilder: (context, index) { + return _itemCreateer(index); + + }, separatorBuilder: (context, index) { + return Divider( indent: 15, endIndent: 15, thickness: 0.6,); + }, itemCount: items.length)) + ], + ); + } else { + return ListView.separated(itemBuilder: (context, index) { + return _itemCreateer(index); + + }, separatorBuilder: (context, index) { + return Divider(indent: 15, endIndent: 15, thickness: 0.6,); + }, itemCount: items.length); + } + } + + _itemCreateer(int index) { + return SizedBox( + height: 47, + child: Center( + child: Text(items[index], style: TextStyle( + fontSize: 15, color: Colors.black, + )), + ).addInkWell( + onTap: () { + + AppNav.back(); + onSelected?.call(index); + }) + ); + } +} \ No newline at end of file diff --git a/lib/tools/widgets/novy_keep_alive.dart b/lib/tools/widgets/novy_keep_alive.dart new file mode 100644 index 0000000..3ca2dfa --- /dev/null +++ b/lib/tools/widgets/novy_keep_alive.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; + +/// keep alive +class NovyKeepAlive extends StatefulWidget { + final Widget child; + final bool? wantKeepAlive; + + const NovyKeepAlive( + {super.key, required this.child, this.wantKeepAlive = true}); + + @override + State createState() => _CmKeepAlivePageState(); +} + +class _CmKeepAlivePageState extends State + with AutomaticKeepAliveClientMixin { + @override + Widget build(BuildContext context) { + super.build(context); + return widget.child; + } + + @override + bool get wantKeepAlive => widget.wantKeepAlive!; +} diff --git a/pubspec.lock b/pubspec.lock index a951010..50c91a1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -97,6 +97,22 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.3" + dio: + dependency: "direct main" + description: + name: dio + sha256: ea2bad3c89a27635ce2d85cce4d6b199da49a5a48ec77b03e45b65a3b90922b0 + url: "https://pub.dev" + source: hosted + version: "5.10.0" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: dd58dc3861eb36edb13b217efc006a1c21e5bbc341de8c229b85634fa5e362e4 + url: "https://pub.dev" + source: hosted + version: "2.2.0" fake_async: dependency: transitive description: @@ -190,6 +206,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.2" + flutter_smart_dialog: + dependency: "direct main" + description: + name: flutter_smart_dialog + sha256: "18ee3a53e898e4b0c2898e498bb61ab65cedf56f00fc412ac66d986d33e246bf" + url: "https://pub.dev" + source: hosted + version: "5.1.0" flutter_test: dependency: "direct dev" description: flutter @@ -328,6 +352,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.17.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" objective_c: dependency: transitive description: @@ -448,6 +480,62 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf + url: "https://pub.dev" + source: hosted + version: "2.5.5" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53 + url: "https://pub.dev" + source: hosted + version: "2.4.23" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" sky_engine: dependency: transitive description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 1d53528..592ab7e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,6 +23,10 @@ dependencies: get_storage: ^2.1.1 package_info_plus: ^8.3.0 device_info_plus: ^11.5.0 + + dio: ^5.9.2 + shared_preferences: ^2.5.3 + flutter_smart_dialog: ^5.1.0 flutter_secure_storage: ^9.2.4 android_id: ^0.5.1