diff --git a/frontend/mobile-app/android/app/src/main/AndroidManifest.xml b/frontend/mobile-app/android/app/src/main/AndroidManifest.xml index 421c0658..655c9f8b 100644 --- a/frontend/mobile-app/android/app/src/main/AndroidManifest.xml +++ b/frontend/mobile-app/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,6 @@ + + + + + + diff --git a/frontend/mobile-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/frontend/mobile-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index db77bb4b..3d9cf755 100644 Binary files a/frontend/mobile-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/frontend/mobile-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/frontend/mobile-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/frontend/mobile-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index 17987b79..c7cd1458 100644 Binary files a/frontend/mobile-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/frontend/mobile-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/frontend/mobile-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/frontend/mobile-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 09d43914..d758e20c 100644 Binary files a/frontend/mobile-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/frontend/mobile-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/frontend/mobile-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/frontend/mobile-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index d5f1c8d3..faaad85b 100644 Binary files a/frontend/mobile-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/frontend/mobile-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/frontend/mobile-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/frontend/mobile-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 4d6372ee..f010db71 100644 Binary files a/frontend/mobile-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/frontend/mobile-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/frontend/mobile-app/android/app/src/main/res/values/colors.xml b/frontend/mobile-app/android/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..c5d5899f --- /dev/null +++ b/frontend/mobile-app/android/app/src/main/res/values/colors.xml @@ -0,0 +1,4 @@ + + + #FFFFFF + \ No newline at end of file diff --git a/frontend/mobile-app/assets/images/logo/app_icon.png b/frontend/mobile-app/assets/images/logo/app_icon.png new file mode 100644 index 00000000..7fe21e1e Binary files /dev/null and b/frontend/mobile-app/assets/images/logo/app_icon.png differ diff --git a/frontend/mobile-app/ios/Runner.xcodeproj/project.pbxproj b/frontend/mobile-app/ios/Runner.xcodeproj/project.pbxproj index 5129b644..d9d51eaa 100644 --- a/frontend/mobile-app/ios/Runner.xcodeproj/project.pbxproj +++ b/frontend/mobile-app/ios/Runner.xcodeproj/project.pbxproj @@ -427,7 +427,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -484,7 +484,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; diff --git a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index d36b1fab..d0d98aa1 100644 --- a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,122 +1 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} +{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} \ No newline at end of file diff --git a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png index dc9ada47..ec32697c 100644 Binary files a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 7353c41e..446ebae7 100644 Binary files a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 797d452e..482e4a30 100644 Binary files a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index 6ed2d933..4b45b906 100644 Binary files a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 4cd7b009..c6b46735 100644 Binary files a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index fe730945..6248cdf8 100644 Binary files a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index 321773cd..ada3b6d3 100644 Binary files a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 797d452e..482e4a30 100644 Binary files a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index 502f463a..4dea0719 100644 Binary files a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index 0ec30343..e786af7d 100644 Binary files a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png new file mode 100644 index 00000000..4fe5f92b Binary files /dev/null and b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png differ diff --git a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png new file mode 100644 index 00000000..fe858f2b Binary files /dev/null and b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png differ diff --git a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png new file mode 100644 index 00000000..4ddd8b49 Binary files /dev/null and b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ diff --git a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png new file mode 100644 index 00000000..bfa7fe18 Binary files /dev/null and b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ diff --git a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index 0ec30343..e786af7d 100644 Binary files a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index e9f5fea2..b2969975 100644 Binary files a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png new file mode 100644 index 00000000..3d9cf755 Binary files /dev/null and b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ diff --git a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png new file mode 100644 index 00000000..faaad85b Binary files /dev/null and b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ diff --git a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index 84ac32ae..cef5340b 100644 Binary files a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 8953cba0..e7f1f6a8 100644 Binary files a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index 0467bf12..2d976354 100644 Binary files a/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/frontend/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/frontend/mobile-app/lib/app.dart b/frontend/mobile-app/lib/app.dart index e64ed6d2..1c2a6c51 100644 --- a/frontend/mobile-app/lib/app.dart +++ b/frontend/mobile-app/lib/app.dart @@ -12,15 +12,25 @@ class App extends ConsumerWidget { final router = ref.watch(appRouterProvider); return ScreenUtilInit( - designSize: const Size(375, 812), + designSize: const Size(360, 800), // 与 UIPro Figma 设计稿一致 minTextAdapt: true, splitScreenMode: true, + useInheritedMediaQuery: true, builder: (context, child) { return MaterialApp.router( - title: 'RWA榴莲女皇', + title: '榴莲皇后', debugShowCheckedModeBanner: false, theme: AppTheme.light, routerConfig: router, + builder: (context, widget) { + // 限制系统字体缩放,保持 UI 一致性 + return MediaQuery( + data: MediaQuery.of(context).copyWith( + textScaler: TextScaler.noScaling, + ), + child: widget!, + ); + }, ); }, ); diff --git a/frontend/mobile-app/lib/core/extensions/num_extensions.dart b/frontend/mobile-app/lib/core/extensions/num_extensions.dart index 7cec96fc..ccb82b61 100644 --- a/frontend/mobile-app/lib/core/extensions/num_extensions.dart +++ b/frontend/mobile-app/lib/core/extensions/num_extensions.dart @@ -1,5 +1,22 @@ +import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:intl/intl.dart'; +/// 响应式尺寸扩展 +/// 使用方式: 16.w (宽度适配), 16.h (高度适配), 16.sp (字体适配), 16.r (圆角适配) +extension ResponsiveNum on num { + /// 宽度适配 + double get w => ScreenUtil().setWidth(this); + + /// 高度适配 + double get h => ScreenUtil().setHeight(this); + + /// 字体大小适配 + double get sp => ScreenUtil().setSp(this); + + /// 圆角适配 (取宽高最小值) + double get r => ScreenUtil().radius(this); +} + extension NumExtensions on num { String get formatted { return NumberFormat('#,##0.##').format(this); diff --git a/frontend/mobile-app/lib/core/theme/app_dimensions.dart b/frontend/mobile-app/lib/core/theme/app_dimensions.dart index 982bc8c1..15d82399 100644 --- a/frontend/mobile-app/lib/core/theme/app_dimensions.dart +++ b/frontend/mobile-app/lib/core/theme/app_dimensions.dart @@ -1,7 +1,12 @@ +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +/// 应用尺寸常量 +/// 提供静态值和响应式值两种方式 +/// 静态值用于不需要适配的场景,响应式值用于需要适配的场景 class AppDimensions { AppDimensions._(); - // 间距 + // ============ 静态间距 ============ static const double spacingXs = 4.0; static const double spacingSm = 8.0; static const double spacingMd = 16.0; @@ -9,7 +14,15 @@ class AppDimensions { static const double spacingXl = 32.0; static const double spacingXxl = 48.0; - // 圆角 + // ============ 响应式间距 ============ + static double get spacingXsR => 4.w; + static double get spacingSmR => 8.w; + static double get spacingMdR => 16.w; + static double get spacingLgR => 24.w; + static double get spacingXlR => 32.w; + static double get spacingXxlR => 48.w; + + // ============ 静态圆角 ============ static const double radiusXs = 4.0; static const double radiusSm = 8.0; static const double radiusMd = 12.0; @@ -17,34 +30,70 @@ class AppDimensions { static const double radiusXl = 24.0; static const double radiusFull = 999.0; - // 图标尺寸 + // ============ 响应式圆角 ============ + static double get radiusXsR => 4.r; + static double get radiusSmR => 8.r; + static double get radiusMdR => 12.r; + static double get radiusLgR => 16.r; + static double get radiusXlR => 24.r; + + // ============ 静态图标尺寸 ============ static const double iconXs = 16.0; static const double iconSm = 20.0; static const double iconMd = 24.0; static const double iconLg = 32.0; static const double iconXl = 48.0; - // 按钮高度 + // ============ 响应式图标尺寸 ============ + static double get iconXsR => 16.sp; + static double get iconSmR => 20.sp; + static double get iconMdR => 24.sp; + static double get iconLgR => 32.sp; + static double get iconXlR => 48.sp; + + // ============ 静态按钮高度 ============ static const double buttonHeightSm = 36.0; static const double buttonHeightMd = 44.0; static const double buttonHeightLg = 52.0; - // 输入框高度 + // ============ 响应式按钮高度 ============ + static double get buttonHeightSmR => 36.h; + static double get buttonHeightMdR => 44.h; + static double get buttonHeightLgR => 52.h; + + // ============ 静态输入框高度 ============ static const double inputHeight = 48.0; - // 头像尺寸 + // ============ 响应式输入框高度 ============ + static double get inputHeightR => 48.h; + + // ============ 静态头像尺寸 ============ static const double avatarSm = 32.0; static const double avatarMd = 48.0; static const double avatarLg = 64.0; static const double avatarXl = 96.0; - // 卡片 + // ============ 响应式头像尺寸 ============ + static double get avatarSmR => 32.w; + static double get avatarMdR => 48.w; + static double get avatarLgR => 64.w; + static double get avatarXlR => 96.w; + + // ============ 卡片 ============ static const double cardPadding = 16.0; static const double cardRadius = 12.0; - // 底部导航栏 - static const double bottomNavHeight = 56.0; + static double get cardPaddingR => 16.w; + static double get cardRadiusR => 12.r; - // AppBar + // ============ 导航栏 ============ + static const double bottomNavHeight = 65.0; static const double appBarHeight = 56.0; + + static double get bottomNavHeightR => 65.h; + static double get appBarHeightR => 56.h; + + // ============ 最小点击区域 (无障碍规范要求至少 48x48) ============ + static const double minTouchTarget = 48.0; + static double get minTouchTargetR => 48.w; } diff --git a/frontend/mobile-app/lib/features/auth/presentation/pages/guide_page.dart b/frontend/mobile-app/lib/features/auth/presentation/pages/guide_page.dart new file mode 100644 index 00000000..7905a124 --- /dev/null +++ b/frontend/mobile-app/lib/features/auth/presentation/pages/guide_page.dart @@ -0,0 +1,442 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:go_router/go_router.dart'; +import '../../../../routes/route_paths.dart'; +import '../providers/auth_provider.dart'; + +/// 向导页数据模型 +class GuidePageData { + final String? imagePath; + final String title; + final String subtitle; + final Widget? customContent; + + const GuidePageData({ + this.imagePath, + required this.title, + required this.subtitle, + this.customContent, + }); +} + +/// 向导页面 - 用户首次打开应用时展示 +/// 支持左右滑动切换页面 +class GuidePage extends ConsumerStatefulWidget { + const GuidePage({super.key}); + + @override + ConsumerState createState() => _GuidePageState(); +} + +class _GuidePageState extends ConsumerState { + final PageController _pageController = PageController(); + int _currentPage = 0; + + // 向导页1-4的数据 + final List _guidePages = const [ + GuidePageData( + imagePath: 'assets/images/guide_1.png', + title: '认种一棵榴莲树\n拥有真实RWA资产', + subtitle: '绑定真实果园20年收益,让区块链与农业完美结合', + ), + GuidePageData( + imagePath: 'assets/images/guide_2.png', + title: '认种即可开启算力\n自动挖矿持续收益', + subtitle: '每一棵树都对应真实资产注入,为算力提供真实价值支撑', + ), + GuidePageData( + imagePath: 'assets/images/guide_3.png', + title: '分享链接\n获得团队算力与收益', + subtitle: '真实认种数据透明可信 · 团队越大算力越强', + ), + GuidePageData( + imagePath: 'assets/images/guide_4.png', + title: 'MPC多方安全\n所有地址与收益可审计', + subtitle: '你的资产 · 安全透明 · 不可被篡改', + ), + ]; + + @override + void dispose() { + _pageController.dispose(); + super.dispose(); + } + + void _onPageChanged(int page) { + setState(() { + _currentPage = page; + }); + } + + void _goToNextPage() { + if (_currentPage < 4) { + _pageController.nextPage( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + } + } + + void _goToOnboarding() async { + // 标记已查看向导页 + await ref.read(authProvider.notifier).markGuideAsSeen(); + if (!mounted) return; + context.go(RoutePaths.onboarding); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + body: SafeArea( + child: PageView.builder( + controller: _pageController, + onPageChanged: _onPageChanged, + itemCount: 5, // 4个介绍页 + 1个欢迎加入页 + itemBuilder: (context, index) { + if (index < 4) { + return _buildGuidePage(_guidePages[index], index); + } else { + return _buildWelcomePage(); + } + }, + ), + ), + ); + } + + /// 构建向导页 (页面1-4) + Widget _buildGuidePage(GuidePageData data, int index) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 24.w), + child: Column( + children: [ + SizedBox(height: 64.h), + // 图片区域 + Expanded( + flex: 5, + child: Container( + width: 312.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.r), + color: const Color(0xFFFFF8E7), + ), + child: data.imagePath != null + ? ClipRRect( + borderRadius: BorderRadius.circular(12.r), + child: Image.asset( + data.imagePath!, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return _buildPlaceholderImage(index); + }, + ), + ) + : _buildPlaceholderImage(index), + ), + ), + SizedBox(height: 48.h), + // 标题 + Text( + data.title, + style: TextStyle( + fontSize: 24.sp, + fontWeight: FontWeight.w700, + height: 1.33, + color: const Color(0xFF292524), + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 16.h), + // 副标题 + Padding( + padding: EdgeInsets.symmetric(horizontal: 24.w), + child: Text( + data.subtitle, + style: TextStyle( + fontSize: 14.sp, + height: 1.43, + color: const Color(0xFF57534E), + ), + textAlign: TextAlign.center, + ), + ), + SizedBox(height: 48.h), + // 页面指示器 + _buildPageIndicator(), + SizedBox(height: 80.h), + ], + ), + ); + } + + /// 构建占位图片 + Widget _buildPlaceholderImage(int index) { + final icons = [ + Icons.nature, + Icons.memory, + Icons.people, + Icons.security, + ]; + final colors = [ + const Color(0xFF8BC34A), + const Color(0xFFD4AF37), + const Color(0xFFFF9800), + const Color(0xFF2196F3), + ]; + + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + icons[index], + size: 80.sp, + color: colors[index], + ), + SizedBox(height: 16.h), + Text( + '向导页 ${index + 1}', + style: TextStyle( + fontSize: 16.sp, + color: const Color(0xFF57534E), + ), + ), + ], + ), + ); + } + + /// 构建欢迎加入页面 (页面5) + Widget _buildWelcomePage() { + return _WelcomePageContent( + onNext: _goToOnboarding, + ); + } + + /// 构建页面指示器 + Widget _buildPageIndicator() { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate(5, (index) { + final isActive = index == _currentPage; + return Container( + width: 8.w, + height: 8.w, + margin: EdgeInsets.symmetric(horizontal: 4.w), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: isActive + ? const Color(0xFF8E794A) + : const Color(0xFFEAE0CD), + ), + ); + }), + ); + } +} + +/// 欢迎加入页面内容 (第5页) +class _WelcomePageContent extends StatefulWidget { + final VoidCallback onNext; + + const _WelcomePageContent({required this.onNext}); + + @override + State<_WelcomePageContent> createState() => _WelcomePageContentState(); +} + +class _WelcomePageContentState extends State<_WelcomePageContent> { + bool _hasReferrer = true; + final TextEditingController _referralCodeController = TextEditingController(); + + @override + void dispose() { + _referralCodeController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 24.w), + child: Column( + children: [ + // 退出按钮 + Align( + alignment: Alignment.topRight, + child: Padding( + padding: EdgeInsets.only(top: 32.h), + child: GestureDetector( + onTap: () { + // 退出向导 + Navigator.of(context).maybePop(); + }, + child: Text( + '退出 Exit', + style: TextStyle( + fontSize: 14.sp, + color: const Color(0xFFA99F93), + ), + ), + ), + ), + ), + SizedBox(height: 100.h), + // 欢迎标题 + Text( + '欢迎加入', + style: TextStyle( + fontSize: 24.sp, + fontWeight: FontWeight.w700, + height: 1.33, + color: const Color(0xFF6F6354), + ), + ), + SizedBox(height: 12.h), + // 副标题 + Text( + '创建账号前的最后一步 · 请选择是否有推荐人', + style: TextStyle( + fontSize: 14.sp, + height: 1.43, + color: const Color(0xFFA99F93), + ), + ), + SizedBox(height: 62.h), + // 选项区域 + _buildReferrerOptions(), + const Spacer(), + // 下一步按钮 + GestureDetector( + onTap: widget.onNext, + child: Container( + width: double.infinity, + padding: EdgeInsets.symmetric(vertical: 16.h), + child: Text( + '下一步 (创建账号)', + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + height: 1.5, + color: const Color(0xFFD9C8A9), + ), + textAlign: TextAlign.right, + ), + ), + ), + SizedBox(height: 80.h), + ], + ), + ); + } + + /// 构建推荐人选项 + Widget _buildReferrerOptions() { + return Column( + children: [ + // 有推荐人选项 + GestureDetector( + onTap: () { + setState(() { + _hasReferrer = true; + }); + }, + child: Row( + children: [ + _buildRadio(_hasReferrer), + SizedBox(width: 12.w), + Text( + '我有推荐人', + style: TextStyle( + fontSize: 16.sp, + height: 1.5, + color: const Color(0xFF6F6354), + ), + ), + const Spacer(), + // 扫码图标 + Icon( + Icons.qr_code_scanner, + size: 24.sp, + color: const Color(0xFFA99F93), + ), + ], + ), + ), + SizedBox(height: 14.h), + // 推荐码输入框 + if (_hasReferrer) + Container( + padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 4.w), + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide( + width: 1, + color: Color(0xFFEAE1D2), + ), + ), + ), + child: TextField( + controller: _referralCodeController, + decoration: InputDecoration( + hintText: '请输入推荐码 / 序列号', + hintStyle: TextStyle( + fontSize: 16.sp, + color: const Color(0xFFA99F93), + ), + border: InputBorder.none, + isDense: true, + contentPadding: EdgeInsets.zero, + ), + style: TextStyle( + fontSize: 16.sp, + color: const Color(0xFF6F6354), + ), + ), + ), + SizedBox(height: 24.h), + // 没有推荐人选项 + GestureDetector( + onTap: () { + setState(() { + _hasReferrer = false; + }); + }, + child: Row( + children: [ + _buildRadio(!_hasReferrer), + SizedBox(width: 12.w), + Text( + '我没有推荐人', + style: TextStyle( + fontSize: 16.sp, + height: 1.5, + color: const Color(0xFF6F6354), + ), + ), + ], + ), + ), + ], + ); + } + + /// 构建单选按钮 + Widget _buildRadio(bool isSelected) { + return Container( + width: 20.w, + height: 20.w, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + width: isSelected ? 6.w : 2.w, + color: isSelected + ? const Color(0xFF2563EB) + : const Color(0xFFA99F93), + ), + ), + ); + } +} diff --git a/frontend/mobile-app/lib/features/auth/presentation/pages/splash_page.dart b/frontend/mobile-app/lib/features/auth/presentation/pages/splash_page.dart index d5c93c99..ec74f147 100644 --- a/frontend/mobile-app/lib/features/auth/presentation/pages/splash_page.dart +++ b/frontend/mobile-app/lib/features/auth/presentation/pages/splash_page.dart @@ -36,8 +36,11 @@ class _SplashPageState extends ConsumerState { if (authState.isWalletCreated) { // 已创建钱包,进入主页面(龙虎榜) context.go(RoutePaths.ranking); + } else if (authState.isFirstLaunch || !authState.hasSeenGuide) { + // 首次打开或未看过向导,进入向导页 + context.go(RoutePaths.guide); } else { - // 未创建钱包,进入引导页面 + // 已看过向导但未创建钱包,直接进入创建账户页面 context.go(RoutePaths.onboarding); } } diff --git a/frontend/mobile-app/lib/features/auth/presentation/providers/auth_provider.dart b/frontend/mobile-app/lib/features/auth/presentation/providers/auth_provider.dart index c9fba9be..4ae38ccc 100644 --- a/frontend/mobile-app/lib/features/auth/presentation/providers/auth_provider.dart +++ b/frontend/mobile-app/lib/features/auth/presentation/providers/auth_provider.dart @@ -14,12 +14,16 @@ class AuthState { final AuthStatus status; final String? walletAddress; final bool isWalletCreated; + final bool isFirstLaunch; + final bool hasSeenGuide; final String? errorMessage; const AuthState({ this.status = AuthStatus.initial, this.walletAddress, this.isWalletCreated = false, + this.isFirstLaunch = true, + this.hasSeenGuide = false, this.errorMessage, }); @@ -27,12 +31,16 @@ class AuthState { AuthStatus? status, String? walletAddress, bool? isWalletCreated, + bool? isFirstLaunch, + bool? hasSeenGuide, String? errorMessage, }) { return AuthState( status: status ?? this.status, walletAddress: walletAddress ?? this.walletAddress, isWalletCreated: isWalletCreated ?? this.isWalletCreated, + isFirstLaunch: isFirstLaunch ?? this.isFirstLaunch, + hasSeenGuide: hasSeenGuide ?? this.hasSeenGuide, errorMessage: errorMessage, ); } @@ -47,6 +55,11 @@ class AuthNotifier extends StateNotifier { state = state.copyWith(status: AuthStatus.checking); try { + // 检查是否首次启动 + final isFirstLaunchStr = await _secureStorage.read(key: StorageKeys.isFirstLaunch); + final isFirstLaunch = isFirstLaunchStr == null || isFirstLaunchStr != 'false'; + + // 检查钱包状态 final walletAddress = await _secureStorage.read(key: StorageKeys.walletAddress); final isWalletCreated = walletAddress != null && walletAddress.isNotEmpty; @@ -55,11 +68,15 @@ class AuthNotifier extends StateNotifier { status: AuthStatus.authenticated, walletAddress: walletAddress, isWalletCreated: true, + isFirstLaunch: false, + hasSeenGuide: true, ); } else { state = state.copyWith( status: AuthStatus.unauthenticated, isWalletCreated: false, + isFirstLaunch: isFirstLaunch, + hasSeenGuide: !isFirstLaunch, ); } } catch (e) { @@ -70,6 +87,15 @@ class AuthNotifier extends StateNotifier { } } + /// 标记已查看向导页 + Future markGuideAsSeen() async { + await _secureStorage.write(key: StorageKeys.isFirstLaunch, value: 'false'); + state = state.copyWith( + isFirstLaunch: false, + hasSeenGuide: true, + ); + } + Future saveWallet(String walletAddress, String privateKey) async { await _secureStorage.write(key: StorageKeys.walletAddress, value: walletAddress); await _secureStorage.write(key: StorageKeys.privateKey, value: privateKey); diff --git a/frontend/mobile-app/lib/features/home/presentation/widgets/bottom_nav_bar.dart b/frontend/mobile-app/lib/features/home/presentation/widgets/bottom_nav_bar.dart index 003f2521..febe19b2 100644 --- a/frontend/mobile-app/lib/features/home/presentation/widgets/bottom_nav_bar.dart +++ b/frontend/mobile-app/lib/features/home/presentation/widgets/bottom_nav_bar.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; /// 底部导航栏组件 /// 包含四个Tab:龙虎榜、矿机、交易、我 +/// 使用响应式设计适配各种屏幕尺寸 class BottomNavBar extends StatelessWidget { final int currentIndex; final Function(int) onTap; @@ -15,7 +17,7 @@ class BottomNavBar extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - height: 65, + height: 65.h, decoration: const BoxDecoration( color: Color(0xFFFFF5E6), border: Border( @@ -77,14 +79,14 @@ class BottomNavBar extends StatelessWidget { children: [ Icon( isSelected ? activeIcon : icon, - size: 24, + size: 24.sp, color: isSelected ? const Color(0xFFD4AF37) : const Color(0xFF8B5A2B), ), - const SizedBox(height: 2), + SizedBox(height: 2.h), Text( label, style: TextStyle( - fontSize: 12, + fontSize: 12.sp, fontFamily: 'Inter', fontWeight: FontWeight.w500, height: 1.33, diff --git a/frontend/mobile-app/lib/routes/app_router.dart b/frontend/mobile-app/lib/routes/app_router.dart index f56b6f09..c18592ef 100644 --- a/frontend/mobile-app/lib/routes/app_router.dart +++ b/frontend/mobile-app/lib/routes/app_router.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../features/auth/presentation/pages/splash_page.dart'; +import '../features/auth/presentation/pages/guide_page.dart'; import '../features/auth/presentation/pages/onboarding_page.dart'; import '../features/auth/presentation/pages/backup_mnemonic_page.dart'; import '../features/auth/presentation/pages/verify_mnemonic_page.dart'; @@ -67,6 +68,13 @@ final appRouterProvider = Provider((ref) { builder: (context, state) => const SplashPage(), ), + // Guide Pages (向导页) + GoRoute( + path: RoutePaths.guide, + name: RouteNames.guide, + builder: (context, state) => const GuidePage(), + ), + // Onboarding / Create Account GoRoute( path: RoutePaths.onboarding, diff --git a/frontend/mobile-app/lib/routes/route_names.dart b/frontend/mobile-app/lib/routes/route_names.dart index 8dab1b8e..15cb351e 100644 --- a/frontend/mobile-app/lib/routes/route_names.dart +++ b/frontend/mobile-app/lib/routes/route_names.dart @@ -3,6 +3,7 @@ class RouteNames { // Auth static const splash = 'splash'; + static const guide = 'guide'; static const onboarding = 'onboarding'; static const createWallet = 'create-wallet'; static const backupMnemonic = 'backup-mnemonic'; diff --git a/frontend/mobile-app/lib/routes/route_paths.dart b/frontend/mobile-app/lib/routes/route_paths.dart index 76012fe8..de8ec03b 100644 --- a/frontend/mobile-app/lib/routes/route_paths.dart +++ b/frontend/mobile-app/lib/routes/route_paths.dart @@ -3,6 +3,7 @@ class RoutePaths { // Auth static const splash = '/'; + static const guide = '/guide'; static const onboarding = '/onboarding'; static const createWallet = '/auth/create'; static const backupMnemonic = '/auth/backup-mnemonic'; diff --git a/frontend/mobile-app/pubspec.lock b/frontend/mobile-app/pubspec.lock index 37551d7a..61743c09 100644 --- a/frontend/mobile-app/pubspec.lock +++ b/frontend/mobile-app/pubspec.lock @@ -169,6 +169,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.4" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + url: "https://pub.dev" + source: hosted + version: "0.4.2" clock: dependency: transitive description: @@ -398,6 +406,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.4.1" + flutter_launcher_icons: + dependency: "direct dev" + description: + name: flutter_launcher_icons + sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7" + url: "https://pub.dev" + source: hosted + version: "0.14.4" flutter_lints: dependency: "direct dev" description: @@ -605,6 +621,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + image: + dependency: transitive + description: + name: image + sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" + url: "https://pub.dev" + source: hosted + version: "4.5.4" image_picker: dependency: "direct main" description: diff --git a/frontend/mobile-app/pubspec.yaml b/frontend/mobile-app/pubspec.yaml index 154542ea..4b635326 100644 --- a/frontend/mobile-app/pubspec.yaml +++ b/frontend/mobile-app/pubspec.yaml @@ -75,6 +75,16 @@ dev_dependencies: # 测试 mocktail: ^1.0.3 + # 应用图标生成 + flutter_launcher_icons: ^0.14.3 + +flutter_launcher_icons: + android: true + ios: true + image_path: "assets/images/logo/app_icon.png" + adaptive_icon_background: "#FFFFFF" + adaptive_icon_foreground: "assets/images/logo/app_icon.png" + flutter: uses-material-design: true