diff --git a/README-EN.md b/README-EN.md index 9f879dc50..23cf6068c 100644 --- a/README-EN.md +++ b/README-EN.md @@ -4,11 +4,7 @@ ## English | [中文](README.md) -This project is an exercise in learning Flutter for personal growth and development. - -To achieve specific design outcomes and meet the demands of daily development, one may employ the methods of configuring, modifying, combining pre-existing components, and customizing. - -The design plans for this project can be found in the "design" directory. You may utilize these plans to practice with a specific goal in mind. Any implementation is solely based on personal comprehension and learning. Should there be any superior implementation strategies, I welcome the opportunity for discussion. +A continuously maintained, practice-oriented Flutter project that targets real-world business scenarios. It focuses on essential capabilities and engineering best practices through configuration, encapsulation, and moderate customization. Design assets are located in the `design` directory for guided implementation. ## Preview @@ -25,34 +21,17 @@ Some of the page effects are as follows: **If you find this project satisfactory, kindly show your support by giving it a Star or Fork. Rest assured, this project is being continuously maintained and any issues can be brought to our attention by submitting an Issue.** -## Realizing the content. - -* MVP pattern -* State management using `provider` (version 6.x) -* Network request encapsulation based on `dio` (version 5.x) -* Integration testing and accessibility testing -* Support for dark mode -* Localization(Thanks to @ghedwards) -* Implementation of complex scrolling effects using `Sliver` series components -* Location selection using AMap (supports Web) -* Encapsulation of common widgets handling -* Pull-to-refresh and load-more functionality -* Application update check -* PopupWindow -* QR code scanning functionality (using the qr_code_scanner plugin) -* Menu switching animations (circular expansion, 3D flip) -* Swipe-to-delete -* City selection -* Three-level linkage selection similar to JD's city selection -* Various custom dialogs -* Sticky header for lists -* Password input keyboard -* Verification code input box -* Custom simple calendar -* Line chart and [pie charts](https://dartpad.cn/d06f8f737d6eb2d87978eb2d14b87864) -* Modularized route management -* More demos (ripple animation, scratch card, lottie) -* More detailed optimizations +## Features + +- Routing: migrated to Navigator 2.0 using `go_router`, with a compatibility layer for gradual adoption +- Networking: `dio` encapsulation with unified error handling +- State: `provider` for state management, theme and locale +- UI/UX: dark mode, localization, complex scrolling (Sliver), WebView +- Device: image picking, vibration, device info +- Map: AMap 2D (Web supported) +- Engineering: integration & accessibility tests, modularized route management +- Animations/Charts: Lottie, line/pie charts, more demos (ripple, scratch card, etc.) +- QR scanning: `qr_code_scanner` (enable on demand) You may download and experience it specifically by accessing the following links: @@ -62,29 +41,47 @@ As for iOS, you will need to download and run the code on your own. For web experience, please visit: https://simplezhli.github.io/flutter_deer/ -## The project's operational environment. +## Requirements [![flutter_deer driver](https://github.com/simplezhli/flutter_deer/actions/workflows/flutter-drive.yml/badge.svg?branch=master)](https://github.com/simplezhli/flutter_deer/actions/workflows/flutter-drive.yml) - 1. Flutter version 3.38.1 +- Flutter 3.38.x (stable) +- Dart 3.10.x +- iOS minimum `iOS 14.0+` +- Android `compileSdk/targetSdk 36` - 2. Dart version 3.10.0 +Note: On Windows/macOS, the project mainly serves UI preview; verify native capabilities on device/simulator. -## Precautions to be taken. +## Getting Started -- In debug mode, there may be some lagging, which is considered a normal occurrence. A satisfactory experience requires the creation of a release package. To create a release version for iOS, execute the command `flutter build ios`. For Android, execute the command `flutter build apk`. +1. Install Flutter (stable channel), ensure `flutter --version` is 3.38.x +2. Fetch dependencies: `flutter pub get` +3. Run + - Android: `flutter run -d android` + - iOS: `flutter run -d ios` (for native libs, run `cd ios && pod install` first) + - Web: `flutter run -d chrome` +4. Build + - Android: `flutter build apk` + - iOS: `flutter build ios` -- If there are any issues with the project's execution, please refer to the [iOS issue summary](./docs/iOS问题汇总.md) and [Android issue summary](./docs/Android问题汇总.md) for possible solutions. +Optional: to enable QR scanning, uncomment the related entries in `pubspec.yaml` and configure platform permissions. -- Due to certain plugin limitations, this project is only available for preview on Windows and macOS. Those interested may run and experience it themselves. - -- To view the functionality demonstration, execute the integration test command `flutter drive --target=test_driver/driver.dart`. +## Routing Migration -- Due to the abundance of pages, it may be difficult to match the design at first. However, I have added the relative path of the design in the code comments, which can be searched or located for the corresponding page. I hope this will be helpful to you. +- Introduced `go_router` for Navigator 2.0 +- Existing `NavigatorUtils` calls remain functional via a compatibility layer +- Modules including WebView, Login, Goods, Orders, Store, Account, Settings, and Statistics are integrated with the new routing configuration -- This project uses the [FlutterJsonBeanFactory](https://github.com/zhangruiyu/FlutterJsonBeanFactory) plugin to generate Beans. +## Build & Testing -- Web performance may be slower due to large resource files such as js and deployment on Github. +- For best performance, build release packages: `flutter build apk` / `flutter build ios` +- Integration tests and demo: `flutter drive --target=test_driver/driver.dart` + +## Troubleshooting + +- iOS CocoaPods/FFI architecture mismatch: use ARM-native `ffi 1.15.5` and ensure Pod sources are accessible; consider using mirrors when needed +- `qr_code_scanner` may fail to fetch sub-dependencies under certain network conditions; temporarily disable or configure mirrors before re-enabling +- Refer to `docs` for iOS/Android issue summaries ## Summary of Experience @@ -128,7 +125,7 @@ For web experience, please visit: https://simplezhli.github.io/flutter_deer/ | [provider](https://github.com/rrousselGit/provider) | **State management** | | [flutter_2d_amap](https://github.com/simplezhli/flutter_2d_amap) | **2D map from Amap** | | [cached_network_image](https://github.com/renefloor/flutter_cached_network_image) | **Image loading** | -| [fluro](https://github.com/theyakka/fluro) | **Routing management** | +| [go_router](https://github.com/flutter/packages/tree/main/packages/go_router) | **Routing (Navigator 2.0)** | | [flutter_oktoast](https://github.com/OpenFlutter/flutter_oktoast) | **Toast notifications** | | [common_utils](https://github.com/Sky24n/common_utils) | **Common Dart utility library** | | [flutter_slidable](https://github.com/letsar/flutter_slidable) | **Swipe-to-delete** | @@ -151,13 +148,12 @@ For web experience, please visit: https://simplezhli.github.io/flutter_deer/ For details, please refer to the [pubspec.yaml](https://github.com/simplezhli/flutter_deer/blob/master/pubspec.yaml) file. -## Plan: - -* [x] Web support. - -* [x] Migrate to null-safety. +## Plan -* [ ] Migrate to Navigator 2.0. +- [x] Web support +- [x] Null-safety migration +- [x] Navigator 2.0 (go_router) migration & compatibility +- [ ] Ongoing modular improvements and cross-platform validation ## Thanks For diff --git a/README.md b/README.md index c5c35fb16..085f7e95c 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,7 @@ ## [English](README-EN.md) | 中文 -本项目为个人学习Flutter的练习项目。 - -通过设置、修改、组合自带部件以及自定义来实现具体的设计效果,满足日常开发的需求。 - -本项目设计图见design目录,你可以通过我提供的设计图有目标的去练习。所有的实现仅是个人的学习理解,如果有更好的实现方案欢迎交流。 +一个以真实业务场景为目标的 Flutter 学习与实践项目,持续维护中。通过配置、封装与适度自定义,聚焦常见业务能力与工程化最佳实践。设计图位于 `design` 目录,便于对照练习与实现。 ## 预览 @@ -25,34 +21,17 @@ **觉得还可以的话,来个Star、Fork支持一波!本项目持续维护中,有问题欢迎提Issue。** -## 实现内容(已迁移到空安全) - -* mvp模式 -* 使用`provider` (6.x 版本)做状态管理 -* 基于`dio` (5.x 版本)的网络请求封装 -* 完整的集成测试、可访问性测试。 -* 支持深色模式 -* 本地化(感谢 @ghedwards) -* 使用`Sliver` 系列组件实现复杂滚动效果 -* 使用高德地图定位选择地址(支持Web) -* 通用Widget的处理封装 -* 下拉刷新 + 上拉加载更多 -* 应用检查更新 -* PopupWindow -* 扫码功能(qr_code_scanner插件) -* 菜单切换动画(圆形扩散、3D翻转) -* 侧滑删除 -* 城市选择 -* 类似京东选择城市的三级联动 -* 各种自定义Dialog -* 列表头部吸顶 -* 密码输入键盘 -* 验证码输入框 -* 自定义简易日历 -* 曲线图及[饼状图](https://dartpad.cn/d06f8f737d6eb2d87978eb2d14b87864) -* 模块化路由管理 -* 更多Demo(水波纹动画、刮刮卡、lottie) -* 更多的细节优化 +## 主要特性 + +- 路由:已迁移至 Navigator 2.0(基于 `go_router`),保留兼容层便于渐进迁移 +- 网络:`dio` 封装与统一错误处理 +- 状态:`provider` 状态管理与主题/语言设定 +- UI/UX:深色模式、本地化、复杂滚动(Sliver)、WebView +- 设备能力:图片选择、震动、设备信息 +- 地图定位:高德 2D 地图(支持 Web) +- 工程化:集成测试与可访问性测试、模块化路由管理 +- 动画/图表:Lottie、曲线图/饼图、更多 Demo(涟漪、刮刮卡等) +- 扫码能力:`qr_code_scanner`(按需启用) 具体可以下载体验: @@ -62,31 +41,41 @@ iOS需要自行下载代码运行。 Web体验地址:https://simplezhli.github.io/flutter_deer/ -## 项目运行环境 +## 环境与平台 [![flutter_deer driver](https://github.com/simplezhli/flutter_deer/actions/workflows/flutter-drive.yml/badge.svg?branch=master)](https://github.com/simplezhli/flutter_deer/actions/workflows/flutter-drive.yml) - 1. Flutter version 3.38.1 - - 2. Dart version 3.10.0 +- Flutter 3.38.x(稳定版) +- Dart 3.10.x +- iOS 最低版本 `iOS 14.0+` +- Android `compileSdk/targetSdk 36` -## 注意事项 +提示:本项目在 Windows、macOS 以 UI 预览为主,原生能力请在真机/模拟器验证。 -- `debug`模式下会有部分卡顿现象,这属于正常现象。良好的体验需要打`release` 包。 - iOS可以执行命令`flutter build ios` 以创建`release`版本。 - Android可以执行命令`flutter build apk` 以创建`release`版本。 +## 快速开始 -- 项目运行有问题可以在[iOS问题汇总](./docs/iOS问题汇总.md)、[Android问题汇总](./docs/Android问题汇总.md)中尝试寻找解决办法。 +1. 安装 Flutter(稳定通道),确保 `flutter --version` 在 3.38.x +2. 拉取依赖:`flutter pub get` +3. 运行 + - Android:`flutter run -d android` + - iOS:`flutter run -d ios`(若使用原生库,需先 `cd ios && pod install`) + - Web:`flutter run -d chrome` +4. 构建发布 + - Android:`flutter build apk` + - iOS:`flutter build ios` -- 由于部分插件的原因,本项目在Windows、macOS仅做预览(主要为原生功能方面,UI问题不大)。有兴趣的可自行运行体验。 +可选:如需启用扫码,取消 `pubspec.yaml` 中相关注释并按平台完成权限配置。 -- 可以执行集成测试命令`flutter drive --target=test_driver/driver.dart` 查看功能演示。 +## 构建与测试 -- 因为页面有点多,一开始可能会导致页面无法与设计图对应上。我在代码注释中有添加设计图的相对路径,可以搜索或查找到对应页面,希望对你有帮助。 +- 体验更流畅的效果请使用 `release` 包:`flutter build apk` / `flutter build ios` +- 集成测试与演示:`flutter drive --target=test_driver/driver.dart` + +## 疑难排查 -- 本项目使用[FlutterJsonBeanFactory](https://github.com/zhangruiyu/FlutterJsonBeanFactory)插件来生成Bean。 - -- Web受制于js等资源过大和部署在Github上,访问会慢一些。 +- iOS 端 CocoaPods/FFI 架构问题:建议使用 ARM 原生 `ffi 1.15.5` 并确保 Pod 源可用;必要时使用镜像源 +- `qr_code_scanner` 在国内网络环境下可能拉取子依赖失败:可先按需注释依赖或配置镜像源后再启用 +- 更多问题参见 `docs` 目录的 iOS/Android 问题汇总 ## 心得总结(推荐阅读) @@ -132,7 +121,7 @@ Web体验地址:https://simplezhli.github.io/flutter_deer/ | [provider](https://github.com/rrousselGit/provider) | **状态管理** | | [flutter_2d_amap](https://github.com/simplezhli/flutter_2d_amap) | **高德2D地图** | | [cached_network_image](https://github.com/renefloor/flutter_cached_network_image) | **图片加载** | -| [fluro](https://github.com/theyakka/fluro) | **路由管理** | +| [go_router](https://github.com/flutter/packages/tree/main/packages/go_router) | **路由管理(Navigator 2.0)** | | [flutter_oktoast](https://github.com/OpenFlutter/flutter_oktoast) | **Toast** | | [common_utils](https://github.com/Sky24n/common_utils) | **Dart 常用工具类库** | | [flutter_slidable](https://github.com/letsar/flutter_slidable) | **侧滑删除** | @@ -155,27 +144,14 @@ Web体验地址:https://simplezhli.github.io/flutter_deer/ 详细内容可以参看[pubspec.yaml](https://github.com/simplezhli/flutter_deer/blob/master/pubspec.yaml)文件 -## 后续计划: - -* [x] 添加地图功能,具体实现插件见 [flutter_2d_amap](https://github.com/simplezhli/flutter_2d_amap) - -* [x] 下拉刷新 + 上拉加载更多 - -* [x] 引入状态管理,预计使用 [provider](https://github.com/rrousselGit/provider) - -* [x] 页面添加设计图路径注释,方便寻找对应的设计图。 - -* [x] 添加集成测试。 - -* [x] 深色模式支持。 - -* [x] 添加`Semantics`(语义) - -* [x] Web端支持。 - -* [x] 迁移到空安全。(安装包减少135KB,10.3M -> 10.1M) +## 计划 -* [ ] 迁移至Navigator 2.0。 +- [x] Web 支持 +- [x] 迁移至空安全 +- [x] 深色模式与可访问性 +- [x] 集成测试 +- [x] Navigator 2.0(go_router)迁移与兼容 +- [ ] 持续完善模块化与跨端验证 ## 已知存在问题: diff --git a/android/app/build.gradle b/android/app/build.gradle index 671685083..7e8716378 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -32,7 +32,7 @@ android { defaultConfig { applicationId "com.weilu.deer" minSdkVersion flutter.minSdkVersion - targetSdkVersion 35 + targetSdkVersion 36 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } diff --git a/ios/Podfile b/ios/Podfile index 373842b25..1847a9ac3 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,8 +1,7 @@ # Uncomment this line to define a global platform for your project -platform :ios, '13.0' +platform :ios, '14.0' -# source 'https://github.com/CocoaPods/Specs.git' -# source 'https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git' +source 'https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' @@ -46,7 +45,7 @@ post_install do |installer| config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0' end # https://github.com/flutter/flutter/issues/90504 #84562 - config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64 i386' + config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'i386' end end end diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 4aacb6605..f894e8708 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -114,7 +114,6 @@ C12284CE5B4DBB4D70DFD4E7 /* Pods-RunnerTests.release.xcconfig */, 1D2071D64ED8C17A4B9C8079 /* Pods-RunnerTests.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -488,15 +487,20 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = YFDWPMC9XD; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = AllenBase; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.weilu.flutterDeer; + PRODUCT_BUNDLE_IDENTIFIER = xyz.tinglo.AllenBase; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; @@ -670,15 +674,20 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = YFDWPMC9XD; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = AllenBase; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.weilu.flutterDeer; + PRODUCT_BUNDLE_IDENTIFIER = xyz.tinglo.AllenBase; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -692,15 +701,20 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = YFDWPMC9XD; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = AllenBase; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.weilu.flutterDeer; + PRODUCT_BUNDLE_IDENTIFIER = xyz.tinglo.AllenBase; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index cbc408b3f..cdf750b50 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,23 +2,10 @@ - CFBundleAllowMixedLocalizations + CADisableMinimumFrameDurationOnPhone + + CFBundleAllowMixedLocalizations - CFBundleDevelopmentRegion - zh_CN - CFBundleLocalizations - - en - zh_CN - - NSCameraUsageDescription - “Flutter Deer”请求在您使用期间获取您的相机权限,使用扫描条形码等功能 - NSLocationWhenInUseUsageDescription - “Flutter Deer”请求在您使用期间获取定位权限,这将带来更好的用户体验 - NSPhotoLibraryUsageDescription - “Flutter Deer”请求在您使用期间获取您的相册权限 - io.flutter.embedded_views_preview - CFBundleDevelopmentRegion en CFBundleExecutable @@ -27,6 +14,11 @@ $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 + CFBundleLocalizations + + en + zh_CN + CFBundleName Flutter Deer CFBundlePackageType @@ -39,6 +31,14 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + NSCameraUsageDescription + “Flutter Deer”请求在您使用期间获取您的相机权限,使用扫描条形码等功能 + NSLocationWhenInUseUsageDescription + “Flutter Deer”请求在您使用期间获取定位权限,这将带来更好的用户体验 + NSPhotoLibraryUsageDescription + “Flutter Deer”请求在您使用期间获取您的相册权限 + UIApplicationSupportsIndirectInputEvents + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -54,9 +54,7 @@ UIViewControllerBasedStatusBarAppearance - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents + io.flutter.embedded_views_preview diff --git a/lib/goods/page/qr_code_scanner_page.dart b/lib/goods/page/qr_code_scanner_page.dart index dfb6545a7..f5bf301fe 100644 --- a/lib/goods/page/qr_code_scanner_page.dart +++ b/lib/goods/page/qr_code_scanner_page.dart @@ -1,70 +1,15 @@ - -import 'dart:io'; - import 'package:flutter/material.dart'; -import 'package:flutter_deer/routers/fluro_navigator.dart'; import 'package:flutter_deer/widgets/my_app_bar.dart'; -import 'package:qr_code_scanner/qr_code_scanner.dart'; - -class QrCodeScannerPage extends StatefulWidget { +import 'package:flutter_deer/routers/fluro_navigator.dart'; +class QrCodeScannerPage extends StatelessWidget { const QrCodeScannerPage({super.key}); - @override - _QrCodeScannerPageState createState() => _QrCodeScannerPageState(); -} - -class _QrCodeScannerPageState extends State { - final GlobalKey qrKey = GlobalKey(debugLabel: 'QR'); - QRViewController? controller; - - /// In order to get hot reload to work we need to pause the camera if the platform - /// is android, or resume the camera if the platform is iOS. - @override - void reassemble() { - super.reassemble(); - if (Platform.isAndroid) { - controller?.pauseCamera(); - controller?.resumeCamera(); - } else if (Platform.isIOS) { - controller?.resumeCamera(); - } - } - @override Widget build(BuildContext context) { - final scanArea = (MediaQuery.of(context).size.width < 400 || - MediaQuery.of(context).size.height < 400) - ? 250.0 - : 300.0; return Scaffold( body: Stack( children: [ - Positioned.fill( - child: QRView( - key: qrKey, - onQRViewCreated: _onQRViewCreated, - overlay: QrScannerOverlayShape( - borderRadius: 10, - borderLength: 20, - borderWidth: 5, - cutOutSize: scanArea, - ), - ), - ), - Positioned( - bottom: 60, - left: 0, - right: 0, - child: Center( - child: IconButton( - icon: const Icon(Icons.highlight_outlined, size: 32, color: Colors.white,), - onPressed: () { - controller?.toggleFlash(); - }, - ), - ), - ), const Positioned( top: 0, left: 0, @@ -74,34 +19,33 @@ class _QrCodeScannerPageState extends State { backImgColor: Colors.white, ), ), + Positioned.fill( + child: Container( + color: Colors.black, + alignment: Alignment.center, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.qr_code, size: 72, color: Colors.white), + const SizedBox(height: 16), + const Text( + '当前未启用扫码插件,已提供占位页面。', + style: TextStyle(color: Colors.white, fontSize: 16), + textAlign: TextAlign.center, + ), + const SizedBox(height: 24), + ElevatedButton( + onPressed: () { + NavigatorUtils.goBackWithParams(context, ''); + }, + child: const Text('返回'), + ), + ], + ), + ), + ), ], ), ); } - - void _onQRViewCreated(QRViewController? controller) { - setState(() { - this.controller = controller; - if (Platform.isAndroid) { - controller?.pauseCamera(); - controller?.resumeCamera(); - } else if (Platform.isIOS) { - controller?.resumeCamera(); - } - }); - controller?.scannedDataStream.listen((scanData) { - /// 避免扫描结果多次回调 - controller.dispose(); - if (!mounted) { - return; - } - NavigatorUtils.goBackWithParams(context, scanData.code ?? ''); - }); - } - - @override - void dispose() { - controller?.dispose(); - super.dispose(); - } } diff --git a/lib/l10n/deer_localizations.dart b/lib/l10n/deer_localizations.dart index 477d7cde2..6dcd4649a 100644 --- a/lib/l10n/deer_localizations.dart +++ b/lib/l10n/deer_localizations.dart @@ -84,11 +84,11 @@ abstract class DeerLocalizations { /// of delegates is preferred or required. static const List> localizationsDelegates = >[ - delegate, - GlobalMaterialLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - ]; + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; /// A list of this localizations delegate's supported locales. static const List supportedLocales = [Locale('en'), Locale('zh')]; @@ -227,8 +227,9 @@ DeerLocalizations lookupDeerLocalizations(Locale locale) { } throw FlutterError( - 'DeerLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' - 'an issue with the localizations generation tool. Please file an issue ' - 'on GitHub with a reproducible sample app and the gen-l10n configuration ' - 'that was used.'); + 'DeerLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.', + ); } diff --git a/lib/main.dart b/lib/main.dart index 696116049..ddffbf308 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,12 +2,10 @@ import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_deer/demo/demo_page.dart'; -import 'package:flutter_deer/home/splash_page.dart'; import 'package:flutter_deer/net/dio_utils.dart'; import 'package:flutter_deer/net/intercept.dart'; import 'package:flutter_deer/res/constant.dart'; -import 'package:flutter_deer/routers/not_found_page.dart'; -import 'package:flutter_deer/routers/routers.dart'; +import 'package:flutter_deer/routers/app_router.dart'; import 'package:flutter_deer/setting/provider/locale_provider.dart'; import 'package:flutter_deer/setting/provider/theme_provider.dart'; import 'package:flutter_deer/util/device_utils.dart'; @@ -77,13 +75,12 @@ class MyApp extends StatelessWidget { MyApp({super.key, this.home, this.theme}) { Log.init(); initDio(); - Routes.initRoutes(); initQuickActions(); } final Widget? home; final ThemeData? theme; - static GlobalKey navigatorKey = GlobalKey(); + static GlobalKey navigatorKey = AppRouter.navigatorKey; void initDio() { final List interceptors = []; @@ -157,7 +154,7 @@ class MyApp extends StatelessWidget { } Widget _buildMaterialApp(ThemeProvider provider, LocaleProvider localeProvider) { - return MaterialApp( + return MaterialApp.router( title: 'Flutter Deer', // showPerformanceOverlay: true, //显示性能标签 // debugShowCheckedModeBanner: false, // 去除右上角debug的标签 @@ -168,12 +165,10 @@ class MyApp extends StatelessWidget { theme: theme ?? provider.getTheme(), darkTheme: provider.getTheme(isDarkMode: true), themeMode: provider.getThemeMode(), - home: home ?? const SplashPage(), - onGenerateRoute: Routes.router.generator, + routerConfig: AppRouter.router, localizationsDelegates: DeerLocalizations.localizationsDelegates, supportedLocales: DeerLocalizations.supportedLocales, locale: localeProvider.locale, - navigatorKey: navigatorKey, builder: (BuildContext context, Widget? child) { /// 保证文字大小不受手机系统设置影响 https://www.kikt.top/posts/flutter/layout/dynamic-text/ return MediaQuery( @@ -182,12 +177,6 @@ class MyApp extends StatelessWidget { ); }, - /// 因为使用了fluro,这里设置主要针对Web - onUnknownRoute: (_) { - return MaterialPageRoute( - builder: (BuildContext context) => const NotFoundPage(), - ); - }, restorationScopeId: 'app', ); } diff --git a/lib/routers/app_router.dart b/lib/routers/app_router.dart new file mode 100644 index 000000000..2f003a7ee --- /dev/null +++ b/lib/routers/app_router.dart @@ -0,0 +1,158 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:flutter_deer/home/splash_page.dart'; +import 'package:flutter_deer/home/home_page.dart'; +import 'package:flutter_deer/home/webview_page.dart'; +import 'package:flutter_deer/routers/not_found_page.dart' as nf; +import 'package:flutter_deer/shop/page/shop_page.dart'; +import 'package:flutter_deer/shop/page/shop_setting_page.dart'; +import 'package:flutter_deer/shop/page/message_page.dart'; +import 'package:flutter_deer/shop/page/freight_config_page.dart'; +import 'package:flutter_deer/shop/page/select_address_page.dart'; +import 'package:flutter_deer/shop/page/input_text_page.dart'; +import 'package:flutter_deer/login/page/login_page.dart'; +import 'package:flutter_deer/login/page/register_page.dart'; +import 'package:flutter_deer/login/page/sms_login_page.dart'; +import 'package:flutter_deer/login/page/reset_password_page.dart'; +import 'package:flutter_deer/login/page/update_password_page.dart'; +import 'package:flutter_deer/goods/page/goods_page.dart'; +import 'package:flutter_deer/goods/page/goods_edit_page.dart'; +import 'package:flutter_deer/goods/page/goods_search_page.dart'; +import 'package:flutter_deer/goods/page/goods_size_page.dart'; +import 'package:flutter_deer/goods/page/goods_size_edit_page.dart'; +import 'package:flutter_deer/goods/page/qr_code_scanner_page.dart'; +import 'package:flutter_deer/order/page/order_page.dart'; +import 'package:flutter_deer/order/page/order_info_page.dart'; +import 'package:flutter_deer/order/page/order_search_page.dart'; +import 'package:flutter_deer/order/page/order_track_page.dart'; +import 'package:flutter_deer/statistics/page/order_statistics_page.dart'; +import 'package:flutter_deer/statistics/page/goods_statistics_page.dart'; +import 'package:flutter_deer/store/page/store_audit_page.dart'; +import 'package:flutter_deer/store/page/store_audit_result_page.dart'; +import 'package:flutter_deer/setting/page/setting_page.dart'; +import 'package:flutter_deer/setting/page/about_page.dart'; +import 'package:flutter_deer/setting/page/theme_page.dart'; +import 'package:flutter_deer/setting/page/locale_page.dart'; +import 'package:flutter_deer/setting/page/account_manager_page.dart'; +import 'package:flutter_deer/account/page/account_page.dart'; +import 'package:flutter_deer/account/page/account_record_list_page.dart'; +import 'package:flutter_deer/account/page/add_withdrawal_account_page.dart'; +import 'package:flutter_deer/account/page/bank_select_page.dart'; +import 'package:flutter_deer/account/page/city_select_page.dart'; +import 'package:flutter_deer/account/page/withdrawal_account_list_page.dart'; +import 'package:flutter_deer/account/page/withdrawal_account_page.dart'; +import 'package:flutter_deer/account/page/withdrawal_page.dart'; +import 'package:flutter_deer/account/page/withdrawal_password_page.dart'; +import 'package:flutter_deer/account/page/withdrawal_record_list_page.dart'; +import 'package:flutter_deer/account/page/withdrawal_result_page.dart'; +import 'package:common_utils/common_utils.dart'; + +class AppRouter { + static final GlobalKey navigatorKey = GlobalKey(); + static final GoRouter router = GoRouter( + navigatorKey: navigatorKey, + initialLocation: '/', + routes: [ + GoRoute( + path: '/', + builder: (_, __) => const SplashPage(), + ), + GoRoute( + path: '/home', + builder: (_, __) => const Home(), + ), + GoRoute( + path: '/webView', + builder: (_, state) { + final qp = state.uri.queryParameters; + final title = qp['title'] ?? ''; + final url = qp['url'] ?? ''; + return WebViewPage(title: title, url: url); + }, + ), + // Shop + GoRoute(path: '/shop', builder: (_, __) => const ShopPage()), + GoRoute(path: '/shop/shopSetting', builder: (_, __) => const ShopSettingPage()), + GoRoute(path: '/shop/message', builder: (_, __) => const MessagePage()), + GoRoute(path: '/shop/freightConfig', builder: (_, __) => const FreightConfigPage()), + GoRoute(path: '/shop/addressSelect', builder: (_, __) => const AddressSelectPage()), + GoRoute( + path: '/shop/inputText', + builder: (_, state) { + final args = state.extra as InputTextPageArgumentsData?; + return InputTextPage( + title: args?.title ?? '', + hintText: args?.hintText ?? '', + content: args?.content ?? '', + keyboardType: args?.keyboardType, + ); + }, + ), + // Login + GoRoute(path: '/login', builder: (_, __) => const LoginPage()), + GoRoute(path: '/login/register', builder: (_, __) => const RegisterPage()), + GoRoute(path: '/login/smsLogin', builder: (_, __) => const SMSLoginPage()), + GoRoute(path: '/login/resetPassword', builder: (_, __) => const ResetPasswordPage()), + GoRoute(path: '/login/updatePassword', builder: (_, __) => const UpdatePasswordPage()), + // Goods + GoRoute(path: '/goods', builder: (_, __) => const GoodsPage()), + GoRoute( + path: '/goods/edit', + builder: (_, state) { + final qp = state.uri.queryParameters; + final isAdd = qp['isAdd'] == 'true'; + final isScan = qp['isScan'] == 'true'; + final url = EncryptUtil.decodeBase64(qp['url'] ?? ''); + final heroTag = qp['heroTag'] ?? 'heroTag'; + return GoodsEditPage(isAdd: isAdd, isScan: isScan, goodsImageUrl: url, heroTag: heroTag); + }, + ), + GoRoute(path: '/goods/search', builder: (_, __) => const GoodsSearchPage()), + GoRoute(path: '/goods/size', builder: (_, __) => const GoodsSizePage()), + GoRoute(path: '/goods/sizeEdit', builder: (_, __) => const GoodsSizeEditPage()), + GoRoute(path: '/goods/qrCodeScanner', builder: (_, __) => const QrCodeScannerPage()), + // Order + GoRoute(path: '/order', builder: (_, __) => const OrderPage()), + GoRoute(path: '/order/info', builder: (_, __) => const OrderInfoPage()), + GoRoute(path: '/order/search', builder: (_, __) => const OrderSearchPage()), + GoRoute(path: '/order/track', builder: (_, __) => const OrderTrackPage()), + // Statistics + GoRoute( + path: '/statistics/order', + builder: (_, state) { + final index = int.tryParse(state.uri.queryParameters['index'] ?? '0') ?? 0; + return OrderStatisticsPage(index); + }, + ), + GoRoute(path: '/statistics/goods', builder: (_, __) => const GoodsStatisticsPage()), + // Store + GoRoute(path: '/store/audit', builder: (_, __) => const StoreAuditPage()), + GoRoute(path: '/store/auditResult', builder: (_, __) => const StoreAuditResultPage()), + // Setting + GoRoute(path: '/setting', builder: (_, __) => const SettingPage()), + GoRoute(path: '/setting/about', builder: (_, __) => const AboutPage()), + GoRoute(path: '/setting/theme', builder: (_, __) => const ThemePage()), + GoRoute(path: '/setting/locale', builder: (_, __) => const LocalePage()), + GoRoute(path: '/setting/accountManager', builder: (_, __) => const AccountManagerPage()), + // Account + GoRoute(path: '/account', builder: (_, __) => const AccountPage()), + GoRoute(path: '/account/recordList', builder: (_, __) => const AccountRecordListPage()), + GoRoute(path: '/account/addWithdrawal', builder: (_, __) => const AddWithdrawalAccountPage()), + GoRoute( + path: '/account/bankSelect', + builder: (_, state) { + final type = int.tryParse(state.uri.queryParameters['type'] ?? '0') ?? 0; + return BankSelectPage(type: type); + }, + ), + GoRoute(path: '/account/citySelect', builder: (_, __) => const CitySelectPage()), + GoRoute(path: '/account/withdrawalAccountList', builder: (_, __) => const WithdrawalAccountListPage()), + GoRoute(path: '/account/withdrawalAccount', builder: (_, __) => const WithdrawalAccountPage()), + GoRoute(path: '/account/withdrawal', builder: (_, __) => const WithdrawalPage()), + GoRoute(path: '/account/withdrawalPassword', builder: (_, __) => const WithdrawalPasswordPage()), + GoRoute(path: '/account/withdrawalRecordList', builder: (_, __) => const WithdrawalRecordListPage()), + GoRoute(path: '/account/withdrawalResult', builder: (_, __) => const WithdrawalResultPage()), + ], + errorBuilder: (_, __) => const nf.NotFoundPage(), + ); +} diff --git a/lib/routers/fluro_navigator.dart b/lib/routers/fluro_navigator.dart index 0d7be3e54..1b4ad7df7 100644 --- a/lib/routers/fluro_navigator.dart +++ b/lib/routers/fluro_navigator.dart @@ -1,36 +1,35 @@ -import 'package:fluro/fluro.dart'; import 'package:flutter/material.dart'; - +import 'package:go_router/go_router.dart'; import 'routers.dart'; -/// fluro的路由跳转工具类 class NavigatorUtils { - static void push(BuildContext context, String path, {bool replace = false, bool clearStack = false, Object? arguments}) { unfocus(); - Routes.router.navigateTo(context, path, - replace: replace, - clearStack: clearStack, - transition: TransitionType.native, - routeSettings: RouteSettings( - arguments: arguments, - ), - ); + final router = GoRouter.of(context); + if (clearStack) { + router.go(path, extra: arguments); + } else if (replace) { + router.replace(path, extra: arguments); + } else { + router.push(path, extra: arguments); + } } static void pushResult(BuildContext context, String path, void Function(Object) function, {bool replace = false, bool clearStack = false, Object? arguments}) { unfocus(); - Routes.router.navigateTo(context, path, - replace: replace, - clearStack: clearStack, - transition: TransitionType.native, - routeSettings: RouteSettings( - arguments: arguments, - ), - ).then((Object? result) { - // 页面返回result为null + final router = GoRouter.of(context); + Future fut; + if (clearStack) { + router.go(path, extra: arguments); + fut = Future.value(null); + } else if (replace) { + fut = router.replace(path, extra: arguments); + } else { + fut = router.push(path, extra: arguments); + } + fut.then((Object? result) { if (result == null) { return; } @@ -40,28 +39,21 @@ class NavigatorUtils { }); } - /// 返回 static void goBack(BuildContext context) { unfocus(); Navigator.pop(context); } - /// 带参数返回 static void goBackWithParams(BuildContext context, Object result) { unfocus(); Navigator.pop(context, result); } - - /// 跳到WebView页 + static void goWebViewPage(BuildContext context, String title, String url) { - //fluro 不支持传中文,需转换 push(context, '${Routes.webViewPage}?title=${Uri.encodeComponent(title)}&url=${Uri.encodeComponent(url)}'); } static void unfocus() { - // 使用下面的方式,会触发不必要的build。 - // FocusScope.of(context).unfocus(); - // https://github.com/flutter/flutter/issues/47128#issuecomment-627551073 FocusManager.instance.primaryFocus?.unfocus(); } } diff --git a/pubspec.yaml b/pubspec.yaml index 2ed407ee5..389d30a23 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,8 +8,8 @@ homepage: https://weilu.blog.csdn.net/ publish_to: 'none' environment: - sdk: ">=3.4.0 <4.0.0" - flutter: ">=3.19.0" + sdk: ">=3.10.0 <4.0.0" + flutter: ">=3.38.0" dependencies: flutter: @@ -34,14 +34,14 @@ dependencies: # Flutter 轮播图 https://github.com/mdddj/flutter_swiper_null_safety flutter_swiper_null_safety_flutter3: ^4.0.3 # flutter_swiper很久不维护,可以使用这个替代 # 启动URL的插件(支持Web) https://github.com/flutter/packages/tree/main/packages/url_launcher - url_launcher: 6.3.0 + url_launcher: 6.3.2 # 图片选择插件 https://github.com/flutter/packages/tree/main/packages/image_picker - image_picker: 1.2.0 + image_picker: 1.2.1 # 侧滑删除 https://github.com/letsar/flutter_slidable flutter_slidable: ^4.0.0 # WebView插件 https://github.com/flutter/packages/tree/main/packages/webview_flutter webview_flutter: 4.13.0 - webview_flutter_wkwebview: 3.23.4 + webview_flutter_wkwebview: 3.23.5 # 处理键盘事件 https://github.com/diegoveloper/flutter_keyboard_actions keyboard_actions: ^4.2.1 # 城市选择列表 https://github.com/flutterchina/azlistview @@ -54,18 +54,18 @@ dependencies: sprintf: ^7.0.0 # 状态管理 https://github.com/rrousselGit/provider provider: ^6.1.2 - # 扫码 https://github.com/juliuscanute/qr_code_scanner - qr_code_scanner: - git: - url: 'https://github.com/NeverOvO/qr_code_scanner.git' - ref: '3fe7b88' + go_router: ^14.3.0 + # qr_code_scanner: + # git: + # url: 'https://github.com/NeverOvO/qr_code_scanner.git' + # ref: '3fe7b88' # App Shortcuts https://github.com/flutter/packages/tree/main/packages/quick_actions quick_actions: 1.1.0 # 振动 https://github.com/benjamindean/flutter_vibration - vibration: 3.1.3 + vibration: 3.1.5 vibration_web: 1.6.8 # 获取当前设备信息 https://github.com/fluttercommunity/plus_plugins/tree/main/packages/device_info_plus - device_info_plus: 11.5.0 + device_info_plus: 12.3.0 # 桌面应用调整窗口的大小和位置 https://github.com/leanflutter/window_manager window_manager: 0.5.1 # 高德2D地图插件(支持Web) https://github.com/simplezhli/flutter_2d_amap diff --git a/test/net/dio_test.dart b/test/net/dio_test.dart index ee9f6fe68..db477aa6b 100644 --- a/test/net/dio_test.dart +++ b/test/net/dio_test.dart @@ -21,6 +21,7 @@ void main() { }, onError: (code, msg) { debugPrint('$code, $msg'); + fail('Request failed with code $code: $msg'); } ); });