├── .gitignore ├── .metadata ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── ali-push-proguard-rules.pro │ ├── app-proguard-rules.pro │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── tech │ │ │ │ └── ucoon │ │ │ │ └── flutter_app_template │ │ │ │ ├── MainActivity.java │ │ │ │ └── YardiApplication.java │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ ├── values │ │ │ └── styles.xml │ │ │ └── xml │ │ │ └── network_security_config.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── config.json ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── 2.0x │ └── ic_loading_dialog.gif ├── 3.0x │ └── ic_loading_dialog.gif ├── bridge.js ├── demo.html └── ic_loading_dialog.gif ├── ios ├── .gitignore ├── AliyunEmasServices-Info.plist ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── main.m ├── lib ├── app │ ├── base │ │ ├── base_body_widget.dart │ │ ├── base_i_model.dart │ │ ├── base_model.dart │ │ ├── base_model_mixin.dart │ │ ├── controller │ │ │ ├── base_controller.dart │ │ │ ├── page_state.dart │ │ │ └── page_state_controller.dart │ │ └── model │ │ │ ├── base_page_list_req.dart │ │ │ ├── base_page_list_resp.dart │ │ │ └── enum.dart │ ├── middleware │ │ └── router_login.dart │ ├── modules │ │ ├── classify │ │ │ ├── bindings │ │ │ │ └── classify_binding.dart │ │ │ ├── controllers │ │ │ │ ├── classify_controller.dart │ │ │ │ ├── classify_model.dart │ │ │ │ ├── classify_service_api.dart │ │ │ │ └── classify_state.dart │ │ │ ├── index.dart │ │ │ └── views │ │ │ │ └── classify_view.dart │ │ ├── global │ │ │ ├── bindings │ │ │ │ └── global_purchasing_binding.dart │ │ │ ├── controllers │ │ │ │ ├── global_order_model.dart │ │ │ │ ├── global_order_service_api.dart │ │ │ │ ├── global_order_state.dart │ │ │ │ └── global_purchasing_controller.dart │ │ │ ├── index.dart │ │ │ ├── views │ │ │ │ └── global_purchasing_view.dart │ │ │ └── widget │ │ │ │ └── text_format.dart │ │ ├── home │ │ │ ├── bindings │ │ │ │ └── home_binding.dart │ │ │ ├── controllers │ │ │ │ ├── home_controller.dart │ │ │ │ ├── home_model.dart │ │ │ │ ├── home_service_api.dart │ │ │ │ └── home_state.dart │ │ │ ├── index.dart │ │ │ └── views │ │ │ │ └── home_view.dart │ │ ├── login │ │ │ ├── bindings │ │ │ │ └── login_binding.dart │ │ │ ├── controllers │ │ │ │ ├── login_controller.dart │ │ │ │ ├── login_model.dart │ │ │ │ ├── login_service_api.dart │ │ │ │ └── login_state.dart │ │ │ ├── index.dart │ │ │ ├── views │ │ │ │ └── login_view.dart │ │ │ └── widget │ │ │ │ ├── sms_code_input_widget.dart │ │ │ │ ├── sms_code_text_widget.dart │ │ │ │ └── third_partner_login_widget.dart │ │ ├── personal │ │ │ ├── bindings │ │ │ │ └── personal_binding.dart │ │ │ ├── controllers │ │ │ │ ├── personal_controller.dart │ │ │ │ ├── personal_model.dart │ │ │ │ ├── personal_service_api.dart │ │ │ │ └── personal_state.dart │ │ │ ├── index.dart │ │ │ └── views │ │ │ │ └── personal_view.dart │ │ ├── tab │ │ │ ├── bindings │ │ │ │ └── tab_home_binding.dart │ │ │ ├── controllers │ │ │ │ ├── tab_home_controller.dart │ │ │ │ └── tab_state.dart │ │ │ ├── index.dart │ │ │ └── views │ │ │ │ └── tab_home_view.dart │ │ ├── webview │ │ │ ├── bindings │ │ │ │ └── custom_webview_binding.dart │ │ │ ├── controllers │ │ │ │ ├── js_bridge_controller.dart │ │ │ │ └── js_bridge_model.dart │ │ │ ├── index.dart │ │ │ ├── views │ │ │ │ └── custom_webview.dart │ │ │ └── widget │ │ │ │ ├── custom_webview_widget.dart │ │ │ │ └── webview_jsbridge.dart │ │ └── welcome │ │ │ ├── bindings │ │ │ └── welcome_binding.dart │ │ │ ├── controllers │ │ │ ├── welcome_controller.dart │ │ │ ├── welcome_model.dart │ │ │ └── welcome_service_api.dart │ │ │ ├── index.dart │ │ │ └── views │ │ │ └── welcome_view.dart │ └── routes │ │ ├── app_pages.dart │ │ └── app_routes.dart ├── common │ ├── langs │ │ ├── en_us.dart │ │ ├── translation_service.dart │ │ └── zh_cn.dart │ ├── utils │ │ ├── ali_push_kit.dart │ │ ├── alipay_kit.dart │ │ ├── app_kit.dart │ │ ├── app_upgrade_util.dart │ │ ├── clipboard_kit.dart │ │ ├── date_util.dart │ │ ├── local_util.dart │ │ ├── lodash.dart │ │ ├── logger.dart │ │ ├── permission.dart │ │ ├── photo_camera_kit.dart │ │ ├── regexp_kit.dart │ │ ├── sina_kit.dart │ │ ├── statusbar_kit.dart │ │ ├── storage.dart │ │ ├── sys_badge_util.dart │ │ ├── tencent_kit.dart │ │ ├── third_plateform_login.dart │ │ ├── utils.dart │ │ ├── wechat_kit.dart │ │ └── widget_util.dart │ └── values │ │ ├── border.dart │ │ ├── font_size.dart │ │ ├── gradient.dart │ │ ├── server.dart │ │ ├── storage.dart │ │ └── values.dart ├── global.dart ├── http │ ├── entity │ │ └── base_resp_entity.dart │ ├── http_error.dart │ ├── interceptor │ │ ├── local_log_interceptor.dart │ │ └── token_interceptor.dart │ ├── net_cancel_scope.dart │ ├── net_exception.dart │ └── net_work.dart ├── initial_binding.dart ├── main.dart └── widget │ ├── async_image_banner_widget.dart │ ├── auto_scroll_list_view.dart │ ├── auto_scroll_to_item_widget.dart │ ├── badge.dart │ ├── badge_widget.dart │ ├── bottom_height.dart │ ├── buttons.dart │ ├── check_box.dart │ ├── circle_indicator.dart │ ├── city_picker │ ├── city_picker.dart │ └── src │ │ ├── check.dart │ │ ├── city_picker.dart │ │ └── locations_data.dart │ ├── copy_button.dart │ ├── count_down.dart │ ├── custom_list_tile.dart │ ├── dashed_rect.dart │ ├── dialog.dart │ ├── easy_refresh │ ├── easy_refresh.dart │ └── src │ │ ├── empty_result_widget.dart │ │ ├── no_more_result.dart │ │ └── paged_list_view.dart │ ├── element.dart │ ├── empty_card_widget.dart │ ├── first_animation_widget.dart │ ├── flow_animation_widget.dart │ ├── html_widget.dart │ ├── icon_stack_flow_delegate.dart │ ├── image │ ├── image.dart │ └── src │ │ ├── big_image_list_widget.dart │ │ ├── big_image_widget.dart │ │ ├── image.dart │ │ └── image_holder.dart │ ├── inner_scroll_widget.dart │ ├── input.dart │ ├── label.dart │ ├── layout │ ├── layout.dart │ └── src │ │ ├── common │ │ ├── app_bar.dart │ │ └── common_layout.dart │ │ ├── tab │ │ ├── bottom_tab_bar_view.dart │ │ └── round_underline_tab_indicator.dart │ │ └── un_focus_wrapper.dart │ ├── loading_dialog.dart │ ├── loading_widget.dart │ ├── lock_mixin.dart │ ├── number_input.dart │ ├── preference.dart │ ├── rating_bar.dart │ ├── refresh_widget.dart │ ├── search_app_bar.dart │ ├── second_tap_exit_app.dart │ ├── shake_button │ ├── shake_animation_builder.dart │ ├── shake_animation_controller.dart │ ├── shake_animation_type.dart │ └── shake_animation_widget.dart │ ├── switch_field.dart │ ├── toast.dart │ ├── uploader.dart │ ├── verification_box │ ├── verification_box.dart │ ├── verification_box_cursor.dart │ └── verification_box_item.dart │ ├── waterfall_flow_widget.dart │ ├── widget.dart │ └── wrap_widget.dart ├── pubspec.lock ├── pubspec.yaml ├── test └── widget_test.dart └── web ├── favicon.png ├── icons ├── Icon-192.png ├── Icon-512.png ├── Icon-maskable-192.png └── Icon-maskable-512.png ├── index.html └── manifest.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | .fvm/ 34 | 35 | # Web related 36 | lib/generated_plugin_registrant.dart 37 | 38 | # Symbolication related 39 | app.*.symbols 40 | 41 | # Obfuscation related 42 | app.*.map.json 43 | 44 | # Android Studio will place build artifacts here 45 | /android/app/debug 46 | /android/app/profile 47 | /android/app/release 48 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: db747aa1331bd95bc9b3874c842261ca2d302cd5 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /android/app/ali-push-proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -keepclasseswithmembernames class ** { 2 | native ; 3 | } 4 | -keepattributes Signature 5 | -keep class sun.misc.Unsafe { *; } 6 | -keep class com.taobao.** {*;} 7 | -keep class com.alibaba.** {*;} 8 | -keep class com.alipay.** {*;} 9 | -keep class com.ut.** {*;} 10 | -keep class com.ta.** {*;} 11 | -keep class anet.**{*;} 12 | -keep class anetwork.**{*;} 13 | -keep class org.android.spdy.**{*;} 14 | -keep class org.android.agoo.**{*;} 15 | -keep class android.os.**{*;} 16 | -keep class org.json.**{*;} 17 | -dontwarn com.taobao.** 18 | -dontwarn com.alibaba.** 19 | -dontwarn com.alipay.** 20 | -dontwarn anet.** 21 | -dontwarn org.android.spdy.** 22 | -dontwarn org.android.agoo.** 23 | -dontwarn anetwork.** 24 | -dontwarn com.ut.** 25 | -dontwarn com.ta.** 26 | 27 | # 小米通道 28 | -keep class com.xiaomi.** {*;} 29 | -dontwarn com.xiaomi.** 30 | # 华为通道 31 | -keep class com.huawei.** {*;} 32 | -dontwarn com.huawei.** 33 | # OPPO通道 34 | -keep public class * extends android.app.Service 35 | # VIVO通道 36 | -keep class com.vivo.** {*;} 37 | -dontwarn com.vivo.** -------------------------------------------------------------------------------- /android/app/app-proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # app 混淆规则 2 | 3 | #指定代码的压缩级别 4 | -optimizationpasses 5 5 | 6 | # 指定不去忽略非公共的库的类的成员 7 | -dontskipnonpubliclibraryclassmembers 8 | 9 | # 有了verbose这句话,混淆后就会生成映射文件 10 | # 包含有类名->混淆后类名的映射关系 11 | # 然后使用printmapping指定映射文件的名称 12 | -verbose 13 | -printmapping priguardMapping.txt 14 | 15 | 16 | # 指定混淆时采用的算法,后面的参数是一个过滤器 17 | # 这个过滤器是谷歌推荐的算法,一般不改变 18 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* 19 | 20 | # 抛出异常时保留代码行号 21 | -keepattributes SourceFile,LineNumberTable 22 | #不混淆内部类 23 | -keepattributes InnerClasses 24 | #不混淆注解类 25 | -keepattributes *Annotation* 26 | #不混淆异常 27 | -keepattributes Exceptions 28 | #不混淆签名 29 | -keepattributes Signature 30 | 31 | 32 | # 枚举类不能被混淆 33 | -keepclassmembers enum * { 34 | public static **[] values(); 35 | public static ** valueOf(java.lang.String); 36 | } 37 | # 阿里云推送的混淆规则 38 | -include ali-push-proguard-rules.pro 39 | 40 | 41 | #apk 包内所有 class 的内部结构 42 | -dump class_files.txt 43 | #未混淆的类和成员 44 | -printseeds seeds.txt 45 | #列出从 apk 中删除的代码 46 | -printusage unused.txt -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | apply plugin: 'walle' 29 | 30 | walle { 31 | // 指定渠道包的输出路径 32 | apkOutputFolder = new File("${project.rootDir}/output/channels/") 33 | // 定制渠道包的APK的文件名称 34 | apkFileNameFormat = '大象汇藏-${channel}-v${versionName}-${versionCode}-${buildTime}.apk' 35 | // 渠道&额外信息配置文件 36 | configFile = new File("${project.rootDir}/config.json") 37 | } 38 | 39 | android { 40 | compileSdkVersion flutter.compileSdkVersion 41 | namespace "tech.ucoon.flutter_app_template" 42 | lintOptions { 43 | disable 'InvalidPackage' 44 | checkReleaseBuilds false 45 | abortOnError false 46 | } 47 | 48 | compileOptions { 49 | sourceCompatibility JavaVersion.VERSION_17 50 | targetCompatibility JavaVersion.VERSION_17 51 | } 52 | 53 | defaultConfig { 54 | applicationId "tech.ucoon.flutter_app_template" 55 | minSdkVersion 21 56 | targetSdkVersion flutter.targetSdkVersion 57 | versionCode flutterVersionCode.toInteger() 58 | versionName flutterVersionName 59 | ndk { 60 | abiFilters 'armeabi' , 'armeabi-v7a', 'arm64-v8a' 61 | } 62 | manifestPlaceholders = [ 63 | ALI_PUSH_APP_KEY : "333765354", 64 | ALI_PUSH_APP_SECRET : "c603704cd6ce4e3e870201ca85c1c8a4", 65 | HUAWEI_PUSH_APP_ID : "", 66 | XIAOMI_PUSH_APP_ID : "", 67 | XIAOMI_PUSH_APP_KEY : "", 68 | OPPO_PUSH_APP_KEY : "", 69 | OPPO_PUSH_APP_SECRET: "", 70 | VIVO_PUSH_APP_ID : "", 71 | VIVO_PUSH_APP_KEY : "", 72 | TENCENT_APP_ID: "", 73 | ] 74 | } 75 | 76 | buildTypes { 77 | release { 78 | //开启混淆 79 | minifyEnabled true 80 | //Zipalign优化 81 | zipAlignEnabled true 82 | // 移除无用的resource文件 83 | shrinkResources true 84 | signingConfig signingConfigs.debug 85 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'app-proguard-rules.pro' 86 | } 87 | debug { 88 | //开启混淆 89 | minifyEnabled false 90 | signingConfig signingConfigs.debug 91 | } 92 | } 93 | } 94 | 95 | flutter { 96 | source '../..' 97 | } 98 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/java/tech/ucoon/flutter_app_template/MainActivity.java: -------------------------------------------------------------------------------- 1 | package tech.ucoon.flutter_app_template; 2 | 3 | import io.flutter.embedding.android.FlutterActivity; 4 | 5 | public class MainActivity extends FlutterActivity { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/java/tech/ucoon/flutter_app_template/YardiApplication.java: -------------------------------------------------------------------------------- 1 | package tech.ucoon.flutter_app_template; 2 | 3 | import com.jarvanmo.rammus.RammusPlugin; 4 | 5 | import io.flutter.app.FlutterApplication; 6 | 7 | public class YardiApplication extends FlutterApplication { 8 | @Override 9 | public void onCreate() { 10 | super.onCreate(); 11 | //初始化阿里云推送 12 | RammusPlugin.initPushService(this); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | subprojects { 2 | afterEvaluate { project -> 3 | if (project.hasProperty('android')) { 4 | project.android { 5 | if (namespace == null) { 6 | namespace project.group 7 | } 8 | } 9 | } 10 | } 11 | } 12 | 13 | buildscript { 14 | ext.kotlin_version = '1.8.22' 15 | repositories { 16 | google() 17 | mavenCentral() 18 | maven { url 'https://maven.aliyun.com/repository/google' } 19 | maven { url 'https://maven.aliyun.com/repository/jcenter' } 20 | maven { url 'https://maven.aliyun.com/nexus/content/groups/public' } 21 | } 22 | 23 | dependencies { 24 | classpath 'com.android.tools.build:gradle:8.3.0' 25 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 26 | classpath 'com.meituan.android.walle:plugin:1.1.7' 27 | } 28 | } 29 | 30 | allprojects { 31 | repositories { 32 | google() 33 | mavenCentral() 34 | maven { url 'https://maven.aliyun.com/repository/google' } 35 | maven { url 'https://maven.aliyun.com/repository/jcenter' } 36 | maven { url 'https://maven.aliyun.com/nexus/content/groups/public' } 37 | } 38 | } 39 | 40 | rootProject.buildDir = '../build' 41 | subprojects { 42 | project.buildDir = "${rootProject.buildDir}/${project.name}" 43 | } 44 | subprojects { 45 | project.evaluationDependsOn(':app') 46 | } 47 | 48 | tasks.register("clean", Delete) { 49 | delete rootProject.buildDir 50 | } 51 | -------------------------------------------------------------------------------- /android/config.json: -------------------------------------------------------------------------------- 1 | { 2 | //extraInfo 不要出现以`channel`为key的情况 3 | /* 4 | 不声明extraInfo的channel默认使用的extraInfo 5 | 如果没有此项则没有默认extraInfo 6 | */ 7 | "defaultExtraInfo": { 8 | "channelId": "0" 9 | }, 10 | /* 11 | strategy: 12 | 1. ifNone (默认适用此策略) : 仅当对应channel没有extraInfo时生效 13 | 2. always : 所有channel都生效,channel中extraInfo的key与defaultExtraInfo重复时,覆盖defaultExtraInfo中的内容。 14 | */ 15 | 16 | //"defaultExtraInfoStrategy": "always", 17 | 18 | "channelInfoList": [ 19 | { 20 | "channel": "huawei", 21 | // 此channel将使用自己声明的extraInfo 22 | /* 23 | 此alias可以做到写入apk的channel是meituan,而打包时输出的文件名是美团 24 | 注意:alias不声明时,walle配置apkFileNameFormat中channel就是channel,否则为alias 25 | */ 26 | "alias": "华为", 27 | "extraInfo": { 28 | "channelId": "1" 29 | } 30 | }, 31 | { 32 | "channel": "xiaomi", 33 | "alias": "小米", 34 | "extraInfo": { 35 | "channelId": "2" 36 | } 37 | }, 38 | { 39 | "channel": "yingyongbao", 40 | "alias": "应用宝", 41 | "extraInfo": { 42 | "channelId": "3" 43 | } 44 | } 45 | ] 46 | } -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.4-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /assets/2.0x/ic_loading_dialog.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/assets/2.0x/ic_loading_dialog.gif -------------------------------------------------------------------------------- /assets/3.0x/ic_loading_dialog.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/assets/3.0x/ic_loading_dialog.gif -------------------------------------------------------------------------------- /assets/ic_loading_dialog.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/assets/ic_loading_dialog.gif -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '11.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | 36 | pod 'AlicloudPush', '~> 1.9.9' 37 | end 38 | 39 | source 'https://github.com/CocoaPods/Specs.git' 40 | source 'https://github.com/aliyun/aliyun-specs.git' 41 | 42 | post_install do |installer| 43 | installer.pods_project.targets.each do |target| 44 | flutter_additional_ios_build_settings(target) 45 | ## 修改所有三方库的部署版本号 46 | target.build_configurations.each do |config| 47 | if config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'].to_f < 11.0 48 | config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '11.0' 49 | end 50 | ## permission_handler设置 51 | config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ 52 | '$(inherited)', 53 | ## dart: PermissionGroup.camera 54 | 'PERMISSION_CAMERA=1', 55 | ## dart: PermissionGroup.notification 56 | 'PERMISSION_NOTIFICATIONS=1', 57 | ## dart: PermissionGroup.location 58 | 'PERMISSION_LOCATION=1' 59 | ] 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | #import "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Flutter App Template 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | flutter_app_template 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | CADisableMinimumFrameDurationOnPhone 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/app/base/base_body_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/widget/widget.dart'; 4 | import 'controller/base_controller.dart'; 5 | import 'model/enum.dart'; 6 | 7 | class BaseBodyWidget extends GetWidget { 8 | const BaseBodyWidget( 9 | this.body, { 10 | Key? key, 11 | this.loading, 12 | this.emptyIcon = 'ic_empty', 13 | this.emptyText = '', 14 | this.netErrorIcon = 'ic_net_error', 15 | this.onReload, 16 | }) : super(key: key); 17 | 18 | final Widget body; 19 | final Widget? loading; 20 | final String? emptyIcon; 21 | final String? emptyText; 22 | final String? netErrorIcon; 23 | final VoidCallback? onReload; 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return Obx( 28 | () { 29 | EmptyState state = controller.pageState.emptyState; 30 | if (state == EmptyState.progress) { 31 | return loading ?? 32 | const Center( 33 | child: LoadingWidget( 34 | stop: false, 35 | ), 36 | ); 37 | } else if (state == EmptyState.normal) { 38 | return body; 39 | } else if (state == EmptyState.empty) { 40 | return EmptyResultWidget( 41 | icon: emptyIcon ?? 'ic_empty', 42 | text: emptyText ?? '', 43 | showReload: false, 44 | ); 45 | } else if (state == EmptyState.netError) { 46 | return EmptyResultWidget( 47 | icon: netErrorIcon ?? 'ic_net_error', 48 | text: emptyText ?? '', 49 | onReload: onReload, 50 | showReload: true, 51 | ); 52 | } 53 | return body; 54 | }, 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/app/base/base_i_model.dart: -------------------------------------------------------------------------------- 1 | abstract class IModel { 2 | void onClear(); 3 | } 4 | -------------------------------------------------------------------------------- /lib/app/base/base_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import '/http/net_cancel_scope.dart'; 3 | import '/http/net_work.dart'; 4 | import 'base_i_model.dart'; 5 | 6 | ///基础数据仓库层 7 | class BaseModel implements IModel { 8 | late RequestClient _apiService; 9 | String? tag; 10 | final CancelScope _cancelScope = CancelScope(); 11 | 12 | BaseModel() { 13 | _apiService = RequestClient(); 14 | tag = _randomBit(100); 15 | _cancelScope.key = tag; 16 | _apiService.cancelScope = _cancelScope; 17 | } 18 | 19 | @override 20 | void onClear() { 21 | // _cancelScope.cancel(tag!); 22 | } 23 | 24 | ///获取随机数 25 | String _randomBit(int len) { 26 | String scopeF = '123456789'; //首位 27 | String scopeC = '0123456789'; //中间 28 | String result = ''; 29 | for (int i = 0; i < len; i++) { 30 | if (i == 0) { 31 | result = scopeF[Random().nextInt(scopeF.length)]; 32 | } else { 33 | result = result + scopeC[Random().nextInt(scopeC.length)]; 34 | } 35 | } 36 | return result; 37 | } 38 | 39 | RequestClient get apiService => _apiService; 40 | } 41 | -------------------------------------------------------------------------------- /lib/app/base/base_model_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'base_model.dart'; 2 | 3 | mixin BaseModelMixin on BaseModel {} 4 | -------------------------------------------------------------------------------- /lib/app/base/controller/base_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/http/http_error.dart'; 4 | import '/http/net_work.dart'; 5 | import '/widget/widget.dart'; 6 | import '../base_model.dart'; 7 | import 'page_state.dart'; 8 | 9 | class BaseController extends GetxController { 10 | final PageState pageState = PageState(); 11 | late T model; 12 | BaseController(this.model); 13 | 14 | @override 15 | void onClose() { 16 | super.onClose(); 17 | model.onClear(); 18 | } 19 | 20 | void showLoading() { 21 | pageState.showLoading(); 22 | } 23 | 24 | void showNormal() { 25 | pageState.showNormal(); 26 | } 27 | 28 | void showNetError() { 29 | pageState.showNetError(); 30 | } 31 | 32 | void showEmpty() { 33 | pageState.showEmpty(); 34 | } 35 | 36 | void request( 37 | Future request, 38 | HttpSuccessCallback success, 39 | HttpFailureCallback err, { 40 | bool showLoadingIndicator = true, 41 | }) { 42 | if (showLoadingIndicator) { 43 | showLoadingDialog(); 44 | } 45 | request.catchError((onError) { 46 | if (showLoadingIndicator) { 47 | Get.back(); 48 | } 49 | 50 | ///错误所有的网络异常 51 | debugPrint("啥错误:${onError.toString()}"); 52 | HttpError error = HttpError.checkNetError(onError); 53 | error.handleError(); 54 | err.call(error); 55 | }).then((value) { 56 | if (showLoadingIndicator) { 57 | Get.back(); 58 | } 59 | success.call(value); 60 | }); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/app/base/controller/page_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import '../model/enum.dart'; 3 | 4 | class PageState { 5 | final Rx _emptyState = EmptyState.normal.obs; 6 | EmptyState get emptyState => _emptyState.value; 7 | set emptyState(value) => _emptyState.value = value; 8 | 9 | void showLoading() { 10 | emptyState = EmptyState.progress; 11 | } 12 | 13 | void showNormal() { 14 | emptyState = EmptyState.normal; 15 | } 16 | 17 | void showNetError() { 18 | emptyState = EmptyState.netError; 19 | } 20 | 21 | void showEmpty() { 22 | emptyState = EmptyState.empty; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/app/base/controller/page_state_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'page_state.dart'; 3 | 4 | class PageStateController extends GetxController { 5 | final PageState pageState = PageState(); 6 | 7 | void showLoading() { 8 | pageState.showLoading(); 9 | } 10 | 11 | void showNormal() { 12 | pageState.showNormal(); 13 | } 14 | 15 | void showNetError() { 16 | pageState.showNetError(); 17 | } 18 | 19 | void showEmpty() { 20 | pageState.showEmpty(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/app/base/model/base_page_list_req.dart: -------------------------------------------------------------------------------- 1 | class PagedReqEntry { 2 | PagedReqEntry({ 3 | this.pageNum = 1, 4 | this.pageSize = 10, 5 | }); 6 | 7 | int pageNum; //页码 8 | int pageSize; //分页大小 9 | ///业务参数 10 | Map? getParam() { 11 | return null; 12 | } 13 | 14 | Map toJson() { 15 | final Map data = {}; 16 | data['pageNum'] = pageNum; 17 | data['pageSize'] = pageSize; 18 | if (getParam() != null) { 19 | data['param'] = getParam(); 20 | } 21 | return data; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/app/base/model/base_page_list_resp.dart: -------------------------------------------------------------------------------- 1 | abstract class BasePageRespEntry { 2 | BasePageRespEntry(); 3 | 4 | BasePageRespEntry.fromJson(Map json); 5 | 6 | Map toJson(); 7 | } 8 | 9 | abstract class PagedList { 10 | PagedList({ 11 | this.total = 0, 12 | this.size = 0, 13 | this.current = 0, 14 | this.pages = 0, 15 | this.items, 16 | }); 17 | 18 | int total = 0; 19 | int size = 0; 20 | int current = 0; 21 | int pages = 0; 22 | List? items; 23 | 24 | bool get hasMore => current * size < total; 25 | 26 | PagedList.fromJson(Map json) { 27 | total = json["total"] ?? 0; 28 | size = json["size"] ?? 0; 29 | current = json["current"] ?? 0; 30 | pages = json["pages"] ?? 0; 31 | items = []; 32 | if (json['records'] != null) { 33 | json['records'].forEach((v) { 34 | items!.add(mapItem(v)); 35 | }); 36 | } 37 | } 38 | 39 | T mapItem(dynamic value); 40 | 41 | Map toJson() => { 42 | "total": total, 43 | "size": size, 44 | "current": current, 45 | "pages": pages, 46 | "records": items == null 47 | ? null 48 | : List.from(items!.map((x) => x.toJson())), 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /lib/app/base/model/enum.dart: -------------------------------------------------------------------------------- 1 | ///全局枚举类型 2 | enum EmptyState { 3 | progress, 4 | normal, 5 | empty, 6 | netError, 7 | } 8 | -------------------------------------------------------------------------------- /lib/app/middleware/router_login.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/global.dart'; 4 | import '../routes/app_pages.dart'; 5 | 6 | /// 第一次欢迎页面 7 | class RouteLoginMiddleware extends GetMiddleware { 8 | // priority 数字小优先级高 9 | @override 10 | int? priority = 0; 11 | 12 | RouteLoginMiddleware({required this.priority}); 13 | 14 | @override 15 | RouteSettings? redirect(String? route) { 16 | debugPrint('redirect ${Global.isOfflineLogin} route $route'); 17 | if (Global.isOfflineLogin) { 18 | if (route?.contains('tab') ?? false) { 19 | return null; 20 | } 21 | _showRegisterDialog(); 22 | } else { 23 | if (route?.contains('setting') ?? false) { 24 | return null; 25 | } 26 | } 27 | return const RouteSettings(name: Routes.tab); 28 | } 29 | 30 | _showRegisterDialog() { 31 | Future.delayed(const Duration(seconds: 2), () { 32 | if (!Get.currentRoute.contains(Routes.login(''))) { 33 | ///todo 隐私弹窗 34 | } 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/app/modules/classify/bindings/classify_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | class ClassifyBinding extends Bindings { 4 | @override 5 | void dependencies() {} 6 | } 7 | -------------------------------------------------------------------------------- /lib/app/modules/classify/controllers/classify_controller.dart: -------------------------------------------------------------------------------- 1 | import '/app/base/controller/base_controller.dart'; 2 | import '../index.dart'; 3 | 4 | class ClassifyController extends BaseController { 5 | final classifyState = ClassifyState(); 6 | 7 | ClassifyController(ClassifyModel model) : super(model); 8 | 9 | @override 10 | void onReady() {} 11 | } 12 | -------------------------------------------------------------------------------- /lib/app/modules/classify/controllers/classify_model.dart: -------------------------------------------------------------------------------- 1 | import '/app/base/base_model.dart'; 2 | 3 | class ClassifyModel extends BaseModel {} 4 | -------------------------------------------------------------------------------- /lib/app/modules/classify/controllers/classify_service_api.dart: -------------------------------------------------------------------------------- 1 | class ClassifyApiService { 2 | ///查询所有商品分类 3 | static const String categoryList = '/api/category/list'; 4 | } 5 | -------------------------------------------------------------------------------- /lib/app/modules/classify/controllers/classify_state.dart: -------------------------------------------------------------------------------- 1 | class ClassifyState {} 2 | -------------------------------------------------------------------------------- /lib/app/modules/classify/index.dart: -------------------------------------------------------------------------------- 1 | library classify; 2 | 3 | export './controllers/classify_controller.dart'; 4 | export './controllers/classify_state.dart'; 5 | export './controllers/classify_model.dart'; 6 | export './bindings/classify_binding.dart'; 7 | export './views/classify_view.dart'; 8 | -------------------------------------------------------------------------------- /lib/app/modules/classify/views/classify_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '../index.dart'; 4 | 5 | class ClassifyPage extends GetView { 6 | const ClassifyPage({Key? key}) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Center( 11 | child: Text( 12 | 'classify'.tr, 13 | ), 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/app/modules/global/bindings/global_purchasing_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | class GlobalPurchasingBinding extends Bindings { 4 | @override 5 | void dependencies() {} 6 | } 7 | -------------------------------------------------------------------------------- /lib/app/modules/global/controllers/global_order_model.dart: -------------------------------------------------------------------------------- 1 | import '/app/base/base_model.dart'; 2 | import '/app/base/base_model_mixin.dart'; 3 | 4 | class GlobalOrderModel extends BaseModel with BaseModelMixin {} 5 | -------------------------------------------------------------------------------- /lib/app/modules/global/controllers/global_order_service_api.dart: -------------------------------------------------------------------------------- 1 | class GlobalOrderApiService { 2 | ///添加海外订单 3 | static const String addOverseaOrder = '/api/overseas/orders/add'; 4 | 5 | ///查询订单列表 6 | static const String getOverseaOrderList = '/api/overseas/orders/list'; 7 | 8 | ///查询订单详情 9 | static const String getOverseaOrderInfo = '/api/overseas/orders/query_info'; 10 | 11 | ///取消订单 12 | static const String cancelOrder = '/api/overseas/orders/cancel'; 13 | 14 | ///修改订单 15 | static const String editOrder = '/api/overseas/orders/edit'; 16 | 17 | ///删除订单 18 | static const String deleteOrder = '/api/overseas/orders/del'; 19 | 20 | ///订单确认收货 21 | static const String confirmReceive = '/api/overseas/orders/ok'; 22 | 23 | ///去支付 24 | static const String orderPay = '/api/overseas/orders/pay'; 25 | 26 | ///模糊查询订单(商品名称) 27 | static const String queryOrder = '/api/overseas/orders/query'; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /lib/app/modules/global/controllers/global_order_state.dart: -------------------------------------------------------------------------------- 1 | class GlobalOrderState {} 2 | -------------------------------------------------------------------------------- /lib/app/modules/global/controllers/global_purchasing_controller.dart: -------------------------------------------------------------------------------- 1 | import '/app/base/controller/base_controller.dart'; 2 | import '../index.dart'; 3 | 4 | class GlobalPurchasingController extends BaseController { 5 | final state = GlobalOrderState(); 6 | 7 | GlobalPurchasingController(GlobalOrderModel model) : super(model); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /lib/app/modules/global/index.dart: -------------------------------------------------------------------------------- 1 | library global_purchasing; 2 | 3 | export './controllers/global_purchasing_controller.dart'; 4 | export './controllers/global_order_model.dart'; 5 | export './controllers/global_order_state.dart'; 6 | export './bindings/global_purchasing_binding.dart'; 7 | export './views/global_purchasing_view.dart'; 8 | -------------------------------------------------------------------------------- /lib/app/modules/global/views/global_purchasing_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '../index.dart'; 4 | 5 | class GlobalPurchasingPage extends GetView { 6 | const GlobalPurchasingPage({Key? key}) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Center( 11 | child: Text( 12 | 'global'.tr, 13 | ), 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/app/modules/global/widget/text_format.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'dart:math' as math; 3 | 4 | class CustomTextFieldFormatter extends TextInputFormatter { 5 | static const defaultDouble = 0.001; 6 | 7 | ///允许的小数位数,-1代表不限制位数,默认为-1 8 | final int digit; 9 | //重写构造方法,可以对位数进行直接限制 10 | CustomTextFieldFormatter({this.digit = -1}); 11 | 12 | static double strToFloat(String str, [double defaultValue = defaultDouble]) { 13 | try { 14 | return double.parse(str); 15 | } catch (e) { 16 | return defaultValue; 17 | } 18 | } 19 | 20 | ///获取目前的小数位数 21 | static int getValueDigit(String value) { 22 | if (value.contains(".")) { 23 | return value.split(".")[1].length; 24 | } else { 25 | return -1; 26 | } 27 | } 28 | 29 | @override 30 | TextEditingValue formatEditUpdate( 31 | TextEditingValue oldValue, TextEditingValue newValue) { 32 | String value = newValue.text; 33 | int selectionIndex = newValue.selection.end; 34 | // 如果输入框内容为.直接将输入框赋值为0. 35 | if (value == ".") { 36 | value = "0."; 37 | selectionIndex++; 38 | } else if (value != "" && 39 | value != defaultDouble.toString() && 40 | strToFloat(value, defaultDouble) == defaultDouble || 41 | getValueDigit(value) == 0 && digit == 0 || 42 | getValueDigit(value) > digit) { 43 | value = oldValue.text; 44 | selectionIndex = oldValue.selection.end; 45 | } 46 | // 通过最上面的判断,这里返回的都是有限金额形式 47 | return TextEditingValue( 48 | text: value, 49 | selection: TextSelection.collapsed(offset: selectionIndex), 50 | ); 51 | } 52 | } 53 | 54 | class ClampInputTextFiledFormatter extends TextInputFormatter { 55 | final int maxLength; 56 | ClampInputTextFiledFormatter({this.maxLength = 0}); 57 | @override 58 | TextEditingValue formatEditUpdate( 59 | TextEditingValue oldValue, TextEditingValue newValue) { 60 | if (maxLength < 1) { 61 | return newValue; 62 | } 63 | final offset = newValue.selection.end; 64 | final newOffset = math.min(maxLength, offset); 65 | final text = newValue.text; 66 | if (offset == newOffset && text.length <= maxLength) return newValue; 67 | return newValue.copyWith( 68 | text: newValue.text.substring(0, newOffset), 69 | selection: TextSelection.collapsed(offset: newOffset)); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/app/modules/home/bindings/home_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | class HomeBinding extends Bindings { 4 | @override 5 | void dependencies() {} 6 | } 7 | -------------------------------------------------------------------------------- /lib/app/modules/home/controllers/home_controller.dart: -------------------------------------------------------------------------------- 1 | import '../index.dart'; 2 | import '/app/base/controller/base_controller.dart'; 3 | 4 | class HomeController extends BaseController { 5 | HomeController(HomeModel model) : super(model); 6 | } 7 | -------------------------------------------------------------------------------- /lib/app/modules/home/controllers/home_model.dart: -------------------------------------------------------------------------------- 1 | import '/app/base/base_model.dart'; 2 | 3 | class HomeModel extends BaseModel {} 4 | -------------------------------------------------------------------------------- /lib/app/modules/home/controllers/home_service_api.dart: -------------------------------------------------------------------------------- 1 | class HomeApiService { 2 | 3 | ///获取版本信息 4 | static const String appInfo = '/api/edition/info'; 5 | 6 | ///轮播图 7 | static const String homeSlide = '/api/home/slide'; 8 | 9 | ///砍价列表 10 | static const String homeBargain = '/api/home/bargain'; 11 | 12 | ///砍价活动列表 13 | static const String promotionBargain = '/api/promotion/bargain/list'; 14 | 15 | ///首页众筹列表 16 | static const String homeCrowd = '/api/home/crowd'; 17 | 18 | ///首页9.9包邮列表 19 | static const String freeShipping = '/api/home/free_shipping'; 20 | 21 | ///推荐商品 22 | static const String recommendList = '/api/search/recommend/goods'; 23 | 24 | ///领券中心 25 | static const String receiveCenter = '/api/coupon/receive_center'; 26 | 27 | ///领取优惠券 28 | static const String addCoupon = '/api/coupon/use_get_coupon'; 29 | 30 | ///我的砍价商品列表 31 | static const String myBargainList = '/api/promotion/bargain/goods/list'; 32 | 33 | 34 | } 35 | -------------------------------------------------------------------------------- /lib/app/modules/home/controllers/home_state.dart: -------------------------------------------------------------------------------- 1 | class HomeState {} 2 | -------------------------------------------------------------------------------- /lib/app/modules/home/index.dart: -------------------------------------------------------------------------------- 1 | library home; 2 | 3 | export './controllers/home_controller.dart'; 4 | export './controllers/home_state.dart'; 5 | export './controllers/home_model.dart'; 6 | export './bindings/home_binding.dart'; 7 | export './views/home_view.dart'; 8 | -------------------------------------------------------------------------------- /lib/app/modules/home/views/home_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '../index.dart'; 4 | 5 | class HomePage extends GetView { 6 | const HomePage({Key? key}) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Center( 11 | child: Text( 12 | 'home'.tr, 13 | ), 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/app/modules/login/bindings/login_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import '../index.dart'; 3 | 4 | class LoginBinding extends Bindings { 5 | @override 6 | void dependencies() { 7 | Get.lazyPut(() => LoginModel()); 8 | Get.lazyPut( 9 | () => LoginController(Get.find()), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/modules/login/controllers/login_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import '/global.dart'; 3 | import '/widget/widget.dart'; 4 | import '/app/base/controller/base_controller.dart'; 5 | import '/app/routes/app_pages.dart'; 6 | import 'login_model.dart'; 7 | import 'login_state.dart'; 8 | 9 | class LoginController extends BaseController { 10 | final loginState = LoginState(); 11 | 12 | LoginController(LoginModel model) : super(model); 13 | 14 | 15 | ///登录 16 | Future login() async { 17 | request( 18 | model.login(), 19 | (value) async { 20 | toastInfo(msg: 'login_success'.tr); 21 | await Global.saveUserToken('user_token'); 22 | await Global.saveUserId('user_id'); 23 | await Global.saveIsFirst(true); 24 | _goToHome(); 25 | }, 26 | (err) { 27 | toastInfo(msg: err.message); 28 | }, 29 | ); 30 | } 31 | 32 | ///跳转到主界面 33 | void _goToHome() { 34 | Get.offAllNamed(Routes.tab); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/app/modules/login/controllers/login_model.dart: -------------------------------------------------------------------------------- 1 | import '/app/base/base_model.dart'; 2 | import '/app/base/base_model_mixin.dart'; 3 | 4 | class LoginModel extends BaseModel with BaseModelMixin { 5 | Future login() async { 6 | final resp = await Future.delayed(const Duration(seconds: 3)); 7 | return true; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/app/modules/login/controllers/login_service_api.dart: -------------------------------------------------------------------------------- 1 | class LoginApiService { 2 | ///电话验证码登录 3 | static const String loginCaptcha = '/user/login/mobile_code'; 4 | } 5 | -------------------------------------------------------------------------------- /lib/app/modules/login/controllers/login_state.dart: -------------------------------------------------------------------------------- 1 | class LoginState {} 2 | -------------------------------------------------------------------------------- /lib/app/modules/login/index.dart: -------------------------------------------------------------------------------- 1 | library login; 2 | 3 | export './controllers/login_controller.dart'; 4 | export './controllers/login_model.dart'; 5 | export './bindings/login_binding.dart'; 6 | export './views/login_view.dart'; 7 | -------------------------------------------------------------------------------- /lib/app/modules/login/views/login_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:quiver/strings.dart'; 5 | import '/widget/widget.dart'; 6 | import '/common/utils/utils.dart'; 7 | import '../widget/sms_code_input_widget.dart'; 8 | import '../widget/third_partner_login_widget.dart'; 9 | import '../index.dart'; 10 | 11 | class LoginPage extends GetView { 12 | bool canBack = true; 13 | LoginPage({Key? key}) : super(key: key) { 14 | Map? arguments = Get.arguments; 15 | if (arguments != null && 16 | arguments.isNotEmpty && 17 | arguments.containsKey('canBack')) { 18 | canBack = arguments['canBack']; 19 | } 20 | } 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return UnFocusWrapper( 25 | CommonLayoutPage( 26 | _buildBody, 27 | margin: EdgeInsets.zero, 28 | backgroundColor: Colors.white, 29 | rootContext: context, 30 | canBack: canBack, 31 | resizeToAvoidBottomInset: false, 32 | ), 33 | ); 34 | } 35 | 36 | Widget _buildBody(BuildContext ctx) { 37 | return Column( 38 | mainAxisAlignment: MainAxisAlignment.start, 39 | children: [ 40 | _buildTitleWidget(), 41 | SizedBox( 42 | height: 55.h, 43 | ), 44 | _buildLoginInputWidget(), 45 | SizedBox( 46 | height: 10.h, 47 | ), 48 | SizedBox( 49 | height: 26.h, 50 | ), 51 | ThirdPartnerLoginWidget( 52 | thirdPartnerLoginCallback: (ThirdPlatform platform, {String? token}) { 53 | if (isBlank(token)) { 54 | toastInfo(msg: '${platform.name}${'login_failed'.tr}'); 55 | return; 56 | } 57 | }, 58 | ), 59 | ], 60 | ); 61 | } 62 | 63 | Widget _buildLoginInputWidget() { 64 | return SmsCodeInputWidget( 65 | checkGraphicCode: (phone, code, imageId) async { 66 | return true; 67 | }, 68 | onSendTap: (phone, code, imageId) async {}, 69 | onSubmit: (phone, code) async { 70 | controller.login(); 71 | }, 72 | ); 73 | } 74 | 75 | Widget _buildTitleWidget() { 76 | return Container( 77 | width: ScreenUtil().screenWidth, 78 | padding: EdgeInsets.symmetric(horizontal: 20.w), 79 | child: Text( 80 | 'login'.tr, 81 | style: TextStyle( 82 | color: const Color(0xFF373737), 83 | fontSize: 24.sp, 84 | fontWeight: FontWeight.bold, 85 | ), 86 | ), 87 | ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib/app/modules/personal/bindings/personal_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/instance_manager.dart'; 2 | 3 | class PersonalBinding extends Bindings { 4 | @override 5 | void dependencies() {} 6 | } 7 | -------------------------------------------------------------------------------- /lib/app/modules/personal/controllers/personal_controller.dart: -------------------------------------------------------------------------------- 1 | import '../index.dart'; 2 | import '/app/base/controller/base_controller.dart'; 3 | 4 | class PersonalController extends BaseController { 5 | PersonalController(PersonalMode model) : super(model); 6 | final PersonalState state = PersonalState(); 7 | } 8 | -------------------------------------------------------------------------------- /lib/app/modules/personal/controllers/personal_model.dart: -------------------------------------------------------------------------------- 1 | import '/app/base/base_model.dart'; 2 | 3 | class PersonalMode extends BaseModel {} 4 | -------------------------------------------------------------------------------- /lib/app/modules/personal/controllers/personal_service_api.dart: -------------------------------------------------------------------------------- 1 | class PersonalApiService { 2 | ///获取用户信息 3 | static const String userInfo = '/api/user/info'; 4 | 5 | ///修改用户信息 6 | static const String updateUserInfo = '/api/user/update_info'; 7 | 8 | ///退出登录 9 | static const String userExitLogin = '/user/login/logout'; 10 | 11 | ///注销 12 | static const String userReg = '/user/login/logout'; 13 | 14 | ///获取用户通知设置信息 15 | static const String userGetNotice = '/api/user/get_notice'; 16 | 17 | ///修改用户通知设置状态 18 | static const String userUpdateNotice = '/api/user/update_notice'; 19 | 20 | ///获取用户帮助中心列表 21 | static const String userGetHelp = '/api/user/get_help'; 22 | 23 | ///用户是否实名认证 24 | static const String userIsReal = '/api/user/if_real'; 25 | 26 | ///用户提交实名认证申请 27 | static const String userAddRealName = '/api/user/add_real_name'; 28 | 29 | ///获取用户实名认证信息 30 | static const String userRealNameDate = '/api/user/real_name_data'; 31 | 32 | ///修改、重新提交,用户实名认证信息 33 | static const String editUserRealName = '/api/user/edit_real_name'; 34 | 35 | ///订单类型数量 36 | static const String getOrderTypeNum = '/api/order/get_type_num'; 37 | 38 | ///查询分销商申请状态 39 | static const String checkDistributor = '/api/distributor/check'; 40 | 41 | } 42 | -------------------------------------------------------------------------------- /lib/app/modules/personal/controllers/personal_state.dart: -------------------------------------------------------------------------------- 1 | class PersonalState {} 2 | -------------------------------------------------------------------------------- /lib/app/modules/personal/index.dart: -------------------------------------------------------------------------------- 1 | library personal; 2 | 3 | export './controllers/personal_controller.dart'; 4 | export './controllers/personal_model.dart'; 5 | export './controllers/personal_state.dart'; 6 | export './bindings/personal_binding.dart'; 7 | export './views/personal_view.dart'; 8 | -------------------------------------------------------------------------------- /lib/app/modules/personal/views/personal_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '../index.dart'; 4 | 5 | class PersonalPage extends GetView { 6 | const PersonalPage({Key? key}) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Center( 11 | child: Text( 12 | 'personal'.tr, 13 | ), 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/app/modules/tab/bindings/tab_home_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import '/app/modules/classify/index.dart'; 3 | import '/app/modules/global/index.dart'; 4 | import '/app/modules/home/index.dart'; 5 | import '/app/modules/personal/index.dart'; 6 | import '../index.dart'; 7 | 8 | class TabHomeBinding extends Bindings { 9 | @override 10 | void dependencies() { 11 | Get.lazyPut( 12 | () => TabHomeController(), 13 | ); 14 | Get.lazyPut(() => HomeModel()); 15 | Get.lazyPut(() => HomeController(Get.find())); 16 | Get.lazyPut(() => GlobalOrderModel()); 17 | Get.lazyPut(() => GlobalPurchasingController(Get.find())); 18 | Get.lazyPut(() => ClassifyModel()); 19 | Get.lazyPut(() => ClassifyController(Get.find())); 20 | Get.lazyPut(() => PersonalMode()); 21 | Get.lazyPut(() => PersonalController(Get.find())); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/app/modules/tab/controllers/tab_home_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '/app/routes/app_pages.dart'; 3 | import '/global.dart'; 4 | import 'package:get/get.dart'; 5 | import '../index.dart'; 6 | 7 | class TabHomeController extends GetxController { 8 | final state = TabState(); 9 | 10 | PageController? pageController; 11 | 12 | handleNavBarTap(int index) { 13 | if (index == state.page) return; 14 | if (Global.isOfflineLogin && index != 0) { 15 | Get.toNamed(Routes.login('tab')); 16 | return; 17 | } 18 | pageController!.jumpToPage(index); 19 | } 20 | 21 | handlePageChanged(int page) { 22 | state.page = page; 23 | } 24 | 25 | @override 26 | void onInit() { 27 | super.onInit(); 28 | pageController = PageController(initialPage: state.page); 29 | } 30 | 31 | @override 32 | void dispose() { 33 | pageController!.dispose(); 34 | super.dispose(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/app/modules/tab/controllers/tab_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | class TabState { 4 | // 当前 tab 页码 5 | final _page = 0.obs; 6 | set page(value) => _page.value = value; 7 | get page => _page.value; 8 | } 9 | -------------------------------------------------------------------------------- /lib/app/modules/tab/index.dart: -------------------------------------------------------------------------------- 1 | library tab; 2 | 3 | export './controllers/tab_home_controller.dart'; 4 | export './controllers/tab_state.dart'; 5 | export './bindings/tab_home_binding.dart'; 6 | export './views/tab_home_view.dart'; 7 | -------------------------------------------------------------------------------- /lib/app/modules/webview/bindings/custom_webview_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import '../index.dart'; 3 | 4 | class CustomWebViewBinding extends Bindings { 5 | @override 6 | void dependencies() { 7 | Get.lazyPut(() => JsBridgeModel()); 8 | Get.lazyPut( 9 | () { 10 | return JsBridgeController(Get.find()); 11 | }, 12 | fenix: true, 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/app/modules/webview/controllers/js_bridge_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/global.dart'; 4 | import '/app/base/controller/base_controller.dart'; 5 | import 'js_bridge_model.dart'; 6 | import '../widget/webview_jsbridge.dart'; 7 | 8 | class JsBridgeController extends BaseController { 9 | final jsBridge = WebViewJSBridge(); 10 | 11 | final RxInt _progressBar = 0.obs; 12 | 13 | JsBridgeController(JsBridgeModel model) : super(model); 14 | get progress => _progressBar.value; 15 | set progress(value) => _progressBar.value = value; 16 | 17 | void initRegister() { 18 | registerToken(); 19 | registerDeviceId(); 20 | registerLogout(); 21 | } 22 | 23 | void registerToken() { 24 | jsBridge.registerHandler("getToken", (data) async { 25 | debugPrint('getToken'); 26 | String token = Global.getUserToken(); 27 | debugPrint('getToken $token'); 28 | await jsBridge.callHandler('getToken', data: token); 29 | return token; 30 | }); 31 | } 32 | 33 | void registerDeviceId() { 34 | jsBridge.registerHandler("getDeviceId", (data) async { 35 | debugPrint('getDeviceId'); 36 | String deviceId = Global.androidId ?? ''; 37 | debugPrint('deviceId $deviceId'); 38 | await jsBridge.callHandler('getDeviceId', data: deviceId); 39 | return deviceId; 40 | }); 41 | } 42 | 43 | void registerLogout() { 44 | jsBridge.registerHandler("logout", (data) async { 45 | debugPrint('logout $data'); 46 | Global.logout(); 47 | }); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/app/modules/webview/controllers/js_bridge_model.dart: -------------------------------------------------------------------------------- 1 | import '/app/base/base_model.dart'; 2 | 3 | class JsBridgeModel extends BaseModel {} 4 | -------------------------------------------------------------------------------- /lib/app/modules/webview/index.dart: -------------------------------------------------------------------------------- 1 | library webview; 2 | 3 | export './bindings/custom_webview_binding.dart'; 4 | export './controllers/js_bridge_controller.dart'; 5 | export './controllers/js_bridge_model.dart'; 6 | export './views/custom_webview.dart'; 7 | export './widget/custom_webview_widget.dart'; 8 | -------------------------------------------------------------------------------- /lib/app/modules/webview/views/custom_webview.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/widget/widget.dart'; 4 | import '../index.dart'; 5 | 6 | class CustomWebPage extends GetView { 7 | CustomWebPage({Key? key}) : super(key: key); 8 | late String url; 9 | String? title; 10 | bool? isCanBack; 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | Map urlMap = Get.arguments; 15 | url = urlMap["url"] as String; 16 | if (urlMap.containsKey("title")) { 17 | title = urlMap["title"] as String; 18 | } 19 | if (urlMap.containsKey("isCanBack")) { 20 | isCanBack = urlMap["isCanBack"] as bool; 21 | } 22 | return CommonLayoutPage( 23 | _buildBody, 24 | title: title ?? '', 25 | canBack: isCanBack ?? true, 26 | onLeaveConfirm: (bool didPop, Object? result) async { 27 | if (isCanBack == null) { 28 | if ((await controller.jsBridge.controller?.canGoBack()) ?? false) { 29 | controller.jsBridge.controller?.goBack(); 30 | return; 31 | } 32 | Get.back(); 33 | } else { 34 | if (isCanBack ?? false) { 35 | Get.back(); 36 | } 37 | } 38 | }, 39 | appBarBackgroundColor: Colors.white, 40 | backgroundColor: const Color(0xFFF8F5F8), 41 | removeBottom: false, 42 | ); 43 | } 44 | 45 | Widget _buildBody(BuildContext ctx) { 46 | return CustomWebWidget(url); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/app/modules/webview/widget/custom_webview_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:webview_flutter/webview_flutter.dart'; 5 | import '../index.dart'; 6 | 7 | typedef WebViewCallback = void Function(); 8 | 9 | class CustomWebWidget extends GetWidget { 10 | final String url; 11 | final WebViewCallback? webViewControllerCallback; 12 | final bool showProgress; 13 | 14 | const CustomWebWidget( 15 | this.url, { 16 | Key? key, 17 | this.showProgress = true, 18 | this.webViewControllerCallback, 19 | }) : super(key: key); 20 | 21 | WebView _buildWebView(BuildContext context) { 22 | WebView _webView = WebView( 23 | javascriptChannels: controller.jsBridge.jsChannels, 24 | javascriptMode: JavascriptMode.unrestricted, 25 | debuggingEnabled: kDebugMode, 26 | onProgress: (progress) { 27 | if (showProgress) { 28 | controller.progress = progress; 29 | } 30 | }, 31 | onWebViewCreated: (webController) { 32 | debugPrint('CustomWebWidget._buildWebView onWebViewCreated'); 33 | controller.jsBridge.controller = webController; 34 | controller.initRegister(); 35 | if (webViewControllerCallback != null) { 36 | webViewControllerCallback!(); 37 | } 38 | }, 39 | navigationDelegate: (NavigationRequest navigation) { 40 | debugPrint('navigationDelegate ${navigation.url}'); 41 | if (navigation.url.contains('__bridge_loaded__')) { 42 | controller.jsBridge.injectJsBridge(); 43 | return NavigationDecision.prevent; 44 | } else if (_isPreventNavigation(navigation.url)) { 45 | return NavigationDecision.prevent; 46 | } 47 | return NavigationDecision.navigate; 48 | }, 49 | onWebResourceError: (WebResourceError error) { 50 | debugPrint('CustomWebWidget._buildWebView onWebResourceError $error'); 51 | }, 52 | onPageFinished: (String url) { 53 | debugPrint('CustomWebWidget._buildWebView onPageFinished'); 54 | controller.jsBridge.injectJsBridge(); 55 | }, 56 | initialUrl: url, 57 | ); 58 | return _webView; 59 | } 60 | 61 | _progressBar(BuildContext context) { 62 | return Obx(() { 63 | double progress = controller.progress / 100; 64 | debugPrint('_progressBar $progress'); 65 | return LinearProgressIndicator( 66 | backgroundColor: Colors.white70.withOpacity(0), 67 | value: progress == 1.0 ? 0 : progress, 68 | valueColor: const AlwaysStoppedAnimation(Colors.red), 69 | ); 70 | }); 71 | } 72 | 73 | @override 74 | Widget build(BuildContext context) { 75 | return showProgress 76 | ? Stack( 77 | children: [ 78 | _buildWebView(context), 79 | Positioned( 80 | left: 0, top: 0, right: 0, child: _progressBar(context)) 81 | ], 82 | ) 83 | : _buildWebView(context); 84 | } 85 | 86 | ///是否包含打开第三方app scheme链接 87 | bool _isPreventNavigation(String url) { 88 | return url.contains('tbopen://m.taobao.com/') || //淘宝 89 | url.contains('openapp.jdmobile:') || //京东 90 | url.contains('vmallPullUpApp?launchExtra') || //华为 91 | url.contains('yanxuan://homepage') || 92 | url.contains('mishop://m.mi.com/p'); //严选 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lib/app/modules/welcome/bindings/welcome_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import '../index.dart'; 3 | 4 | class WelcomeBinding extends Bindings { 5 | @override 6 | void dependencies() { 7 | Get.put(WelcomeModel()); 8 | Get.put(WelcomeController(Get.find())); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/app/modules/welcome/controllers/welcome_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import '/global.dart'; 3 | import '/app/routes/app_pages.dart'; 4 | import '/app/base/controller/base_controller.dart'; 5 | import '../index.dart'; 6 | 7 | class WelcomeController extends BaseController { 8 | WelcomeController(WelcomeModel model) : super(model); 9 | 10 | @override 11 | Future onReady() async { 12 | super.onReady(); 13 | Future.delayed(const Duration(milliseconds: 1000), () async { 14 | Global.saveAlreadyOpen(); 15 | Get.offAndToNamed( 16 | Routes.login('welcome'), 17 | arguments: {'canBack': false}, 18 | ); 19 | }); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/app/modules/welcome/controllers/welcome_model.dart: -------------------------------------------------------------------------------- 1 | import '/app/base/base_model.dart'; 2 | 3 | class WelcomeModel extends BaseModel {} 4 | -------------------------------------------------------------------------------- /lib/app/modules/welcome/controllers/welcome_service_api.dart: -------------------------------------------------------------------------------- 1 | class WelcomeApiService { 2 | ///设备登陆 3 | static const String deviceLogin = '/device/login'; 4 | 5 | ///token登陆 6 | static const String tokenLogin = '/user/login/token'; 7 | 8 | ///更新推送token 9 | static const String updatePushToken = '/device/update_push_token'; 10 | } 11 | -------------------------------------------------------------------------------- /lib/app/modules/welcome/index.dart: -------------------------------------------------------------------------------- 1 | library welcome; 2 | 3 | export './controllers/welcome_controller.dart'; 4 | export './controllers/welcome_model.dart'; 5 | export './bindings/welcome_binding.dart'; 6 | export './views/welcome_view.dart'; 7 | -------------------------------------------------------------------------------- /lib/app/modules/welcome/views/welcome_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 4 | import '../index.dart'; 5 | 6 | class WelcomePage extends GetView { 7 | const WelcomePage({Key? key}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Container( 12 | color: const Color(0xFFFF584F), 13 | child: Stack( 14 | children: [ 15 | Center( 16 | child: _buildImage(), 17 | ), 18 | ], 19 | ), 20 | ); 21 | } 22 | 23 | Widget _buildImage() { 24 | return Icon( 25 | Icons.settings_applications_outlined, 26 | size: 48.w, 27 | color: Colors.white54, 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/app/routes/app_pages.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../middleware/router_login.dart'; 4 | import '../modules/login/index.dart'; 5 | import '../modules/tab/index.dart'; 6 | import '../modules/webview/index.dart'; 7 | import '../modules/welcome/index.dart'; 8 | part 'app_routes.dart'; 9 | 10 | class AppPages { 11 | static const initial = Routes.initial; 12 | 13 | static final List routes = [ 14 | GetPage( 15 | name: _Paths.initial, 16 | page: () => const WelcomePage(), 17 | binding: WelcomeBinding(), 18 | ), 19 | GetPage( 20 | name: _Paths.login, 21 | page: () => LoginPage(), 22 | binding: LoginBinding(), 23 | middlewares: [ 24 | RouteLoginMiddleware(priority: 0), 25 | ], 26 | ), 27 | GetPage( 28 | name: _Paths.tab, 29 | page: () => const TabHomePage(), 30 | binding: TabHomeBinding(), 31 | ), 32 | GetPage( 33 | name: _Paths.customWebView, 34 | page: () => CustomWebPage(), 35 | binding: CustomWebViewBinding(), 36 | ), 37 | ]; 38 | } 39 | -------------------------------------------------------------------------------- /lib/app/routes/app_routes.dart: -------------------------------------------------------------------------------- 1 | part of 'app_pages.dart'; 2 | 3 | abstract class Routes { 4 | static const initial = _Paths.initial; 5 | static login(String from) => '${_Paths.login}/$from'; 6 | static const tab = _Paths.tab; 7 | static const customWebView = _Paths.customWebView; 8 | } 9 | 10 | abstract class _Paths { 11 | static const initial = '/'; 12 | static const login = '/login'; 13 | static const tab = '/tab'; 14 | static const customWebView = '/customWebView'; 15 | } 16 | -------------------------------------------------------------------------------- /lib/common/langs/en_us.dart: -------------------------------------------------------------------------------- 1 | const enUS = { 2 | 'login': 'Log in', 3 | 'login_by_wechat': 'Continue with Wechat', 4 | 'login_by_apple': 'Continue with Apple', 5 | 'login_by_facebook': 'Continue with Facebook', 6 | 'login_by_google': 'Continue with Google', 7 | 'login_name_hint': 'Mobile Number', 8 | 'login_get_sms': 'Get Verification Code', 9 | 'login_no_get_sms': 'Re-acquisition after %s', 10 | 'login_input_sms_hint': 'Input Code', 11 | 'linear_gradient_or': 'OR', 12 | 'login_success': 'login successful', 13 | 'tab_home': 'Home', 14 | 'tab_global_purchasing': 'CrossBorder', 15 | 'tab_cash': 'Cash', 16 | 'tab_personal': 'Account', 17 | 'camera_per_dialog': 18 | 'You need to enable the camera permission, do you want to enable it?', 19 | 'photo_cut': 'Crop', 20 | 'photo_shot': 'Shoot', 21 | 'photo_select': 'Choose from album', 22 | 'local_save_dialog': 23 | 'You need to enable storage permission, do you want to enable it?', 24 | 'camera_confirm': 'Send', 25 | 'camera_shooting_tips': 'Tap to take photo.', 26 | 'camera_shooting_with_recording_tips': 27 | 'Tap to take photo. Long press to record video.', 28 | 'camera_shooting_only_recording_tips': 'Long press to record video.', 29 | 'camera_shooting_tap_recording_tips': 'Tap to record video.', 30 | 'camera_load_failed': 'Load failed', 31 | 'camera_action_manually_focus_hint': 'manually focus', 32 | 'camera_action_preview_hint': 'preview', 33 | 'camera_action_record_hint': 'record', 34 | 'camera_action_shoot_hint': 'take picture', 35 | 'camera_action_shooting_button_tooltip': 'shooting button', 36 | 'camera_action_stop_recording_hint': 'stop recording', 37 | }; 38 | -------------------------------------------------------------------------------- /lib/common/langs/translation_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '../utils/local_util.dart'; 4 | import 'en_us.dart'; 5 | import 'zh_cn.dart'; 6 | 7 | class TranslationService extends Translations { 8 | static Locale? get locale => LocaleUtil.getLocalLocale(); 9 | static const fallbackLocale = Locale('en', 'US'); 10 | 11 | @override 12 | Map> get keys => { 13 | 'en_US': enUS, 14 | 'zh_CN': zhCN, 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /lib/common/langs/zh_cn.dart: -------------------------------------------------------------------------------- 1 | const zhCN = { 2 | 'login': '登入', 3 | 'login_by_wechat': '使用Wechat继续', 4 | 'login_by_apple': '使用Apple继续', 5 | 'login_by_facebook': '使用Facebook继续', 6 | 'login_by_google': '使用Google继续', 7 | 'login_name_hint': '手机号码', 8 | 'login_get_sms': '发送验证码', 9 | 'login_no_get_sms': '%s内不能重新获取', 10 | 'login_input_sms_hint': '输入验证码', 11 | 'linear_gradient_or': '或', 12 | 'login_success': '登录成功', 13 | 'tab_home': 'HOME', 14 | 'tab_global_purchasing': '全球代购', 15 | 'tab_cash': '领现金', 16 | 'tab_personal': '个人中心', 17 | 'camera_per_dialog': '您需要开启摄相机权限,是否去开启?', 18 | 'photo_cut': '裁剪', 19 | 'photo_shot': '拍摄', 20 | 'photo_select': '从相册中选择', 21 | 'local_save_dialog': '您需要开启存储权限,是否去开启?', 22 | 'camera_confirm': '发送', 23 | 'camera_shooting_tips': '轻触拍照', 24 | 'camera_shooting_with_recording_tips': '轻触拍照,长按摄像', 25 | 'camera_shooting_only_recording_tips': '长按摄像', 26 | 'camera_shooting_tap_recording_tips': '轻触摄像', 27 | 'camera_load_failed': '加载失败', 28 | 'camera_action_manually_focus_hint': '手动聚焦', 29 | 'camera_action_preview_hint': '预览', 30 | 'camera_action_record_hint': '录像', 31 | 'camera_action_shoot_hint': '拍照', 32 | 'camera_action_shooting_button_tooltip': '拍照按钮', 33 | 'camera_action_stop_recording_hint': '停止录像', 34 | }; 35 | -------------------------------------------------------------------------------- /lib/common/utils/ali_push_kit.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:quiver/strings.dart'; 5 | import 'package:rammus/rammus.dart' as rammus; 6 | 7 | ///阿里云推送工具类 8 | class AliPushKit { 9 | static late final AliPushKit _instance = AliPushKit._internal(); 10 | factory AliPushKit() => _instance; 11 | AliPushKit._internal(); 12 | 13 | ///初始化 14 | void initAliPush() { 15 | rammus.register(); 16 | setupAndroidNotificationChannels(); 17 | setupIOSNotificationPresentationOption(); 18 | onNotificationListener(); 19 | } 20 | 21 | ///设置通知渠道(针对Android 8.0以上) 22 | void setupAndroidNotificationChannels() { 23 | List channels = []; 24 | channels.add(const rammus.NotificationChannel( 25 | 'chat_message_notice', 26 | '消息提醒', 27 | '消息提醒', 28 | importance: rammus.AndroidNotificationImportance.MAX, 29 | )); 30 | rammus.setupNotificationManager(channels); 31 | } 32 | 33 | ///设置iOS通知显示方式 34 | void setupIOSNotificationPresentationOption() { 35 | if (Platform.isIOS) { 36 | rammus.configureNotificationPresentationOption(); 37 | } 38 | } 39 | 40 | ///获取设备id 41 | Future getAliPushDeviceId() { 42 | return rammus.deviceId; 43 | } 44 | 45 | void onNotificationListener() { 46 | rammus.initCloudChannelResult.listen((data) { 47 | debugPrint( 48 | "----------->init successful ${data.isSuccessful} ${data.errorCode} ${data.errorMessage}"); 49 | }); 50 | rammus.onNotification.listen((data) { 51 | debugPrint( 52 | 'AliPushKit.onNotificationListener onNotification ${data.summary} ${data.extras} ${data.title}'); 53 | _onNotification(json.encode(data.extras)); 54 | }); 55 | rammus.onNotificationOpened.listen((data) { 56 | debugPrint( 57 | 'AliPushKit.onNotificationListener onNotificationOpened ${data.summary} ${data.extras} 被点了'); 58 | _onNotificationOpened(data.extras ?? ''); 59 | }); 60 | 61 | rammus.onNotificationRemoved.listen((data) { 62 | debugPrint( 63 | 'AliPushKit.onNotificationListener onNotificationRemoved $data 被删除了'); 64 | }); 65 | 66 | rammus.onNotificationReceivedInApp.listen((data) { 67 | debugPrint( 68 | 'AliPushKit.onNotificationListener onNotificationReceivedInApp ${data.summary} In app'); 69 | }); 70 | 71 | rammus.onNotificationClickedWithNoAction.listen((data) { 72 | debugPrint( 73 | 'AliPushKit.onNotificationListener onNotificationClickedWithNoAction "${data.summary} no action'); 74 | }); 75 | 76 | rammus.onMessageArrived.listen((data) { 77 | debugPrint( 78 | 'AliPushKit.onNotificationListener onMessageArrived -> ${data.content}'); 79 | }); 80 | } 81 | 82 | void _onNotification(String extras) { 83 | if (isBlank(extras)) return; 84 | } 85 | 86 | void _onNotificationOpened(String extras) { 87 | if (isBlank(extras)) return; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib/common/utils/alipay_kit.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:tobias/tobias.dart'; 3 | 4 | ///支付宝支付工具类 5 | class AliPayKit { 6 | static const String success = '9000'; //支付成功 7 | static late final AliPayKit _instance = AliPayKit._internal(); 8 | factory AliPayKit() => _instance; 9 | late Tobias? _tobias; 10 | AliPayKit._internal(){ 11 | _tobias = Tobias(); 12 | } 13 | 14 | ///判断支付宝是否安装 15 | Future get isAliPayInstalled async { 16 | return await _tobias?.isAliPayInstalled; 17 | } 18 | 19 | ///调起支付宝支付 20 | Future payByAliPay(String orderInfo) async { 21 | Map? resMap = await _tobias?.pay(orderInfo); 22 | return resMap?['resultStatus'] == success; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/common/utils/app_kit.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | 5 | class AppKit { 6 | static void exitApp(BuildContext context) { 7 | if (Platform.isIOS) { 8 | if (Navigator.canPop(context)) { 9 | Navigator.pop(context); 10 | } else { 11 | exit(0); 12 | } 13 | } else { 14 | SystemChannels.platform.invokeMethod('SystemNavigator.pop'); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/common/utils/app_upgrade_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:kooboo_flutter_app_upgrade/flutter_app_upgrade.dart'; 2 | 3 | ///应用内升级 4 | class AppUpgradeKit { 5 | ///下载并安装apk(仅适用于Android) 6 | static downloadApkInstall(String downloadUrl, String versionName) async { 7 | return FlutterAppUpgrade.downloadApkInstall(downloadUrl, versionName); 8 | } 9 | 10 | ///跳转AppStore(仅适用于iOS) 11 | static goToAppStore(String id) { 12 | return FlutterAppUpgrade.goToAppStore(id); 13 | } 14 | 15 | ///跳转Google应用市场(仅适用于Android) 16 | static goToGoogleMarket() { 17 | return FlutterAppUpgrade.goToGoogleMarket(); 18 | } 19 | 20 | ///监听下载进度 21 | static void onListenStreamData(Function onEvent, {Function? onError}) { 22 | FlutterAppUpgrade.onListenStreamData( 23 | (event) { 24 | onEvent.call(event); 25 | }, 26 | onError: onError, 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/common/utils/clipboard_kit.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | 3 | ///剪切板工具类 4 | class ClipboardKit { 5 | ///复制文本到剪切板 6 | static Future setData(String content) async { 7 | if (content.isEmpty) return; 8 | return Clipboard.setData(ClipboardData(text: content)); 9 | } 10 | 11 | ///从剪切板读取文本 12 | static Future getData() { 13 | return Clipboard.getData(Clipboard.kTextPlain); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/common/utils/local_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'storage.dart'; 4 | import '../values/values.dart'; 5 | 6 | ///多语言设置 7 | class LocaleUtil { 8 | ///切换语言 9 | static Future saveAppLocale(Locale locale) { 10 | Get.updateLocale(locale); 11 | return StorageUtil().putJSON(storageLangKey, getAppLang(locale)); 12 | } 13 | 14 | static String? getAppLocale() { 15 | return StorageUtil().getJSON(storageLangKey); 16 | } 17 | 18 | static String getAppLang(Locale locale) { 19 | String code = locale.languageCode; 20 | if (code == 'zh') { 21 | return 'zh_CN'; 22 | } 23 | if (code == 'en') { 24 | return 'en_US'; 25 | } 26 | return 'en_US'; 27 | } 28 | 29 | static Locale getLocalLocale() { 30 | String? lang = getAppLocale(); 31 | if (lang == 'zh_CN') { 32 | return const Locale('zh', 'CN'); 33 | } 34 | return const Locale('en', 'US'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/common/utils/lodash.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:math'; 3 | 4 | List> chunk(List list, int size) { 5 | return List.generate( 6 | (list.length / size).ceil(), 7 | (i) => list.sublist( 8 | i * size, 9 | min(i * size + size, list.length), 10 | ), 11 | ); 12 | } 13 | 14 | Map> groupBy(Iterable list, TKey Function(T) fn) { 15 | return Map.fromIterable( 16 | list.map(fn).toSet(), 17 | value: (i) => list.where((v) => fn(v) == i).toList(), 18 | ); 19 | } 20 | 21 | List map(List list, T1 Function(int ix, T2 it) fn) { 22 | List result = []; 23 | for (var i = 0; i < list.length; i++) { 24 | result.add(fn(i, list[i])); 25 | } 26 | 27 | return result; 28 | } 29 | 30 | List mapJoin( 31 | List list, 32 | Function(int ix, T2 it) fn, 33 | T1 Function(int ix, T2 it) separatorFn, 34 | ) { 35 | List result = []; 36 | for (var i = 0; i < list.length; i++) { 37 | result.add(fn(i, list[i])); 38 | if (i < list.length - 1) { 39 | result.add(separatorFn(i, list[i])); 40 | } 41 | } 42 | 43 | return result; 44 | } 45 | 46 | Function()? debounce( 47 | Function func, [ 48 | Duration delay = const Duration(milliseconds: 500), 49 | ]) { 50 | Timer? timer; 51 | target() { 52 | if (timer?.isActive ?? false) { 53 | timer?.cancel(); 54 | } 55 | timer = Timer(delay, () { 56 | func.call(); 57 | }); 58 | } 59 | 60 | return target; 61 | } 62 | 63 | Function throttle( 64 | Future Function()? func, 65 | ) { 66 | if (func == null) { 67 | return func!; 68 | } 69 | bool enable = true; 70 | target() { 71 | if (enable == true) { 72 | enable = false; 73 | func().then((_) { 74 | enable = true; 75 | }); 76 | } 77 | } 78 | 79 | return target; 80 | } 81 | 82 | /// Examples 83 | /// mapList([1, 2, 3], (a) => a * a); // [{1: 1, 2: 4, 3: 9}] 84 | Map mapList(Iterable itr, Y Function(T) fn) { 85 | return {for (var i in itr) i: fn(i)}; 86 | } 87 | 88 | /// Examples 89 | /// all([4, 2, 3], (x) => x > 1); // true 90 | bool all(Iterable itr, bool Function(T) fn) { 91 | return itr.every(fn); 92 | } 93 | 94 | /// Examples 95 | /// some([0, 1, 2, 0], (x) => x >= 2); // true 96 | bool some(Iterable itr, bool Function(T) fn) { 97 | return itr.any(fn); 98 | } 99 | 100 | List> mapToList(Map data) { 101 | List> list = []; 102 | data.forEach((key, value) { 103 | list.add(MapEntry(key, value)); 104 | }); 105 | return list; 106 | } 107 | 108 | int parseInt(dynamic data) { 109 | if (data is int) { 110 | return data; 111 | } 112 | 113 | if (data is String) { 114 | return int.parse(data); 115 | } 116 | 117 | if (data is bool) { 118 | return data ? 1 : 0; 119 | } 120 | 121 | if (data is double) { 122 | return data.toInt(); 123 | } 124 | 125 | return data; 126 | } 127 | 128 | dynamic getKeyByValue(Map map, dynamic value) { 129 | if (!map.values.contains(value)) return null; 130 | List keys = List.from(map.keys.toList()); 131 | int targetIndex = keys.indexWhere((element) => map[element] == value); 132 | if (targetIndex == -1) return null; 133 | return keys[targetIndex]; 134 | } 135 | -------------------------------------------------------------------------------- /lib/common/utils/logger.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Logger { 4 | // Sample of abstract logging function 5 | static void write(String text, {bool isError = false}) { 6 | Future.microtask(() => debugPrint('** $text. isError: [$isError]')); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lib/common/utils/regexp_kit.dart: -------------------------------------------------------------------------------- 1 | ///正则匹配工具类 2 | class RegExpKit { 3 | ///手机号验证(只验证前三位号段) 4 | static bool checkMobile(String str) { 5 | return RegExp(r"^1[3456789]\d{9}$").hasMatch(str); 6 | } 7 | 8 | ///马来西亚手机号验证(只验证前三位号段) 9 | static bool checkMSMobile(String str) { 10 | return RegExp(r"^(601|01|1)[145]1\d{7}$").hasMatch(str); 11 | } 12 | 13 | ///邮箱验证 14 | static bool checkEmail(String str) { 15 | return RegExp(r"^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$") 16 | .hasMatch(str); 17 | } 18 | 19 | ///验证URL 20 | static bool isUrl(String value) { 21 | return RegExp(r"^((https|http|ftp|rtsp|mms)?:\/\/)[^\s]+").hasMatch(value); 22 | } 23 | 24 | ///验证身份证 25 | static bool isIdCard(String value) { 26 | return RegExp(r"\d{17}[\d|x]|\d{15}").hasMatch(value); 27 | } 28 | 29 | ///验证中文 30 | static bool containChinese(String value) { 31 | return RegExp(r".*[\u4e00-\u9fa5].*").hasMatch(value); 32 | } 33 | 34 | ///数字验证 35 | static bool checkNumber(String value) { 36 | return RegExp(r"^[0-9]\d*\.?\d*$").hasMatch(value); 37 | } 38 | 39 | ///数字加字母 40 | static bool checkNumberAndLetter(String value) { 41 | return RegExp(r"^[a-zA-Z0-9]*$").hasMatch(value); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/common/utils/sina_kit.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:typed_data'; 3 | import 'package:weibo_kit/weibo_kit.dart'; 4 | 5 | ///微博分享工具类 6 | class SinaKit { 7 | static late final SinaKit _instance = SinaKit._internal(); 8 | factory SinaKit() => _instance; 9 | 10 | SinaKit._internal(); 11 | 12 | ///判断微博是否安装 13 | Future get isWeiboInstalledKit async { 14 | return Weibo.instance.isInstalled(); 15 | } 16 | 17 | ///注册QQ AppId 18 | Future registerApp({ 19 | required String appKey, 20 | required String? universalLink, 21 | }) { 22 | return Weibo.instance.registerApp( 23 | appKey: appKey, 24 | universalLink: universalLink, 25 | scope: [WeiboScope.ALL], 26 | ); 27 | } 28 | 29 | ///调起微博分享--图片 30 | Future shareImageToSina({String? text, Uint8List? imageData}) { 31 | return Weibo.instance.shareImage( 32 | text: text, 33 | imageData: imageData, 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/common/utils/statusbar_kit.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | 5 | ///状态栏设置工具类 6 | class StatusBarKit { 7 | ///设置沉浸式状态栏 8 | static void setStatusBarDark({bool dark = false, Color? darkColor}) { 9 | if (Platform.isAndroid) { 10 | SystemChrome.setSystemUIOverlayStyle( 11 | SystemUiOverlayStyle( 12 | statusBarColor: dark ? darkColor : Colors.transparent, 13 | systemNavigationBarIconBrightness: 14 | dark ? Brightness.light : Brightness.dark, 15 | statusBarIconBrightness: dark ? Brightness.light : Brightness.dark, 16 | statusBarBrightness: dark ? Brightness.light : Brightness.dark, 17 | ), 18 | ); 19 | } 20 | } 21 | 22 | ///设置竖屏 23 | static Future setPortrait() { 24 | return SystemChrome.setPreferredOrientations([ 25 | DeviceOrientation.portraitDown, 26 | DeviceOrientation.portraitUp, 27 | ]); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/common/utils/storage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:shared_preferences/shared_preferences.dart'; 3 | 4 | /// 本地存储 5 | class StorageUtil { 6 | static late final StorageUtil _singleton = StorageUtil._internal(); 7 | late SharedPreferences _prefs; 8 | 9 | factory StorageUtil() => _singleton; 10 | 11 | StorageUtil._internal(); 12 | 13 | Future init() async { 14 | _prefs = await SharedPreferences.getInstance(); 15 | } 16 | 17 | Future putJSON(String key, dynamic jsonVal) { 18 | String jsonString = jsonEncode(jsonVal); 19 | return _prefs.setString(key, jsonString); 20 | } 21 | 22 | dynamic getJSON(String key) { 23 | String? jsonString = _prefs.getString(key); 24 | return jsonString == null ? null : jsonDecode(jsonString); 25 | } 26 | 27 | String getString(String key, {String defValue = ''}) { 28 | if (!_prefs.containsKey(key)) return defValue; 29 | return _prefs.getString(key) ?? defValue; 30 | } 31 | 32 | Future? putString(String key, String value) { 33 | return _prefs.setString(key, value); 34 | } 35 | 36 | bool getBool(String key, {bool defValue = false}) { 37 | if (!_prefs.containsKey(key)) return defValue; 38 | return _prefs.getBool(key) ?? defValue; 39 | } 40 | 41 | Future? putBool(String key, bool value) { 42 | return _prefs.setBool(key, value); 43 | } 44 | 45 | int getInt(String key, {int defValue = 0}) { 46 | if (!_prefs.containsKey(key)) return defValue; 47 | return _prefs.getInt(key) ?? defValue; 48 | } 49 | 50 | Future? putInt(String key, int value) { 51 | return _prefs.setInt(key, value); 52 | } 53 | 54 | bool? haveKey(String key) { 55 | return getKeys().contains(key); 56 | } 57 | 58 | Set getKeys() { 59 | return _prefs.getKeys(); 60 | } 61 | 62 | Future? remove(String key) { 63 | return _prefs.remove(key); 64 | } 65 | 66 | Future? clear() { 67 | return _prefs.clear(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/common/utils/sys_badge_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_app_badger/flutter_app_badger.dart'; 2 | import 'dart:math' as math; 3 | 4 | class UpdateBSysBadgeState { 5 | UpdateBSysBadgeState(); 6 | late int unReadCount; 7 | 8 | late bool supported; 9 | int _cacheIndex = 0; 10 | bool _inited = false; 11 | 12 | Future init(int count) async { 13 | unReadCount = count - _cacheIndex; 14 | supported = await FlutterAppBadger.isAppBadgeSupported(); 15 | _inited = true; 16 | _update(); 17 | } 18 | 19 | int get _clampCount => math.max(0, unReadCount); 20 | 21 | void increase() { 22 | if (!_inited) { 23 | _cacheIndex++; 24 | return; 25 | } 26 | if (supported) { 27 | unReadCount++; 28 | _update(); 29 | } 30 | } 31 | 32 | void _update() { 33 | if (_clampCount == 0) { 34 | FlutterAppBadger.removeBadge(); 35 | } 36 | FlutterAppBadger.updateBadgeCount(_clampCount); 37 | } 38 | 39 | void decrease() { 40 | if (!_inited) { 41 | _cacheIndex--; 42 | return; 43 | } 44 | if (supported) { 45 | unReadCount--; 46 | _update(); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/common/utils/tencent_kit.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'package:tencent_kit/tencent_kit.dart'; 4 | 5 | ///QQ分享工具类 6 | class TencentKit { 7 | static late final TencentKit _instance = TencentKit._internal(); 8 | factory TencentKit() => _instance; 9 | 10 | TencentKit._internal(); 11 | 12 | ///授权获取设备信息/同意隐私协议 13 | Future setIsPermissionGranted() { 14 | return Tencent.instance.setIsPermissionGranted(granted: true); 15 | } 16 | 17 | ///判断QQ或者TIM是否安装 18 | Future get isQQInstalledKit async { 19 | return await Tencent.instance.isQQInstalled() || 20 | await Tencent.instance.isTIMInstalled(); 21 | } 22 | 23 | ///注册QQ AppId 24 | Future registerApp(String appId) { 25 | return Tencent.instance.registerApp(appId: appId); 26 | } 27 | 28 | ///调起QQ分享--图片(只可以分享本地图片) 29 | Future shareImageToQQ(File file) { 30 | return Tencent.instance.shareImage( 31 | scene: TencentScene.SCENE_QQ, 32 | imageUri: Uri.file(file.path), 33 | ); 34 | } 35 | 36 | ///调起QQ分享--图文 37 | Future shareWebpage( 38 | String urlImage, { 39 | required String targetUrl, 40 | required String title, 41 | String? description, 42 | }) { 43 | return Tencent.instance.shareWebpage( 44 | scene: TencentScene.SCENE_QQ, 45 | title: title, 46 | targetUrl: targetUrl, 47 | imageUri: Uri.parse(urlImage), 48 | summary: description, 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/common/utils/utils.dart: -------------------------------------------------------------------------------- 1 | library utils; 2 | 3 | export 'app_kit.dart'; 4 | export 'ali_push_kit.dart'; 5 | export 'alipay_kit.dart'; 6 | export 'app_upgrade_util.dart'; 7 | export 'clipboard_kit.dart'; 8 | export 'date_util.dart'; 9 | export 'local_util.dart'; 10 | export 'lodash.dart'; 11 | export 'logger.dart'; 12 | export 'permission.dart'; 13 | export 'photo_camera_kit.dart'; 14 | export 'regexp_kit.dart'; 15 | export 'sina_kit.dart'; 16 | export 'statusbar_kit.dart'; 17 | export 'storage.dart'; 18 | export 'tencent_kit.dart'; 19 | export 'third_plateform_login.dart'; 20 | export 'wechat_kit.dart'; 21 | export 'widget_util.dart'; 22 | -------------------------------------------------------------------------------- /lib/common/utils/widget_util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/scheduler.dart'; 4 | 5 | class WidgetUtil { 6 | static bool isEmpty(Object? value) { 7 | if (value == null) return true; 8 | if (value is String && value.isEmpty) { 9 | return true; 10 | } 11 | return false; 12 | } 13 | 14 | /// 获取图片宽高,加载错误情况返回 Rect.zero.(单位 px) 15 | static Future getImageWH({ 16 | Image? image, 17 | String? url, 18 | String? localUrl, 19 | String? package, 20 | }) { 21 | if (isEmpty(image) && isEmpty(url) && isEmpty(localUrl)) { 22 | return Future.value(Rect.zero); 23 | } 24 | Completer completer = Completer(); 25 | Image img = image ?? 26 | ((url != null && url.isNotEmpty) 27 | ? Image.network(url) 28 | : Image.asset(localUrl!, package: package)); 29 | late ImageStreamListener listener; 30 | final stream = img.image.resolve(const ImageConfiguration()); 31 | stream.addListener(listener = ImageStreamListener( 32 | (ImageInfo info, bool _) { 33 | completer.complete(Rect.fromLTWH( 34 | 0, 0, info.image.width.toDouble(), info.image.height.toDouble())); 35 | SchedulerBinding.instance.addPostFrameCallback((timeStamp) { 36 | stream.removeListener(listener); 37 | // info.dispose(); 38 | }); 39 | }, 40 | onError: (Object exception, StackTrace? stackTrace) { 41 | completer.completeError(exception, stackTrace); 42 | stream.removeListener(listener); 43 | }, 44 | )); 45 | return completer.future; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/common/values/border.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | 4 | BorderRadius get borderRadius1 => BorderRadius.circular(1.r); 5 | BorderRadius get borderRadius2 => BorderRadius.circular(2.r); 6 | BorderRadius get borderRadius3 => BorderRadius.circular(3.r); 7 | BorderRadius get borderRadius4 => BorderRadius.circular(4.r); 8 | BorderRadius get borderRadius5 => BorderRadius.circular(5.r); 9 | BorderRadius get borderRadius6 => BorderRadius.circular(6.r); 10 | BorderRadius get borderRadius7 => BorderRadius.circular(7.r); 11 | BorderRadius get borderRadius8 => BorderRadius.circular(8.r); 12 | BorderRadius get borderRadius9 => BorderRadius.circular(9.r); 13 | BorderRadius get borderRadius10 => BorderRadius.circular(10.r); 14 | BorderRadius get borderRadius12 => BorderRadius.circular(12.r); 15 | BorderRadius get borderRadius15 => BorderRadius.circular(15.r); 16 | BorderRadius get borderRadius16 => BorderRadius.circular(16.r); 17 | BorderRadius get borderRadius17 => BorderRadius.circular(17.r); 18 | BorderRadius get borderRadius19 => BorderRadius.circular(19.r); 19 | BorderRadius get borderRadius20 => BorderRadius.circular(20.r); 20 | BorderRadius get borderRadius24 => BorderRadius.circular(24.r); 21 | BorderRadius get borderRadius22 => BorderRadius.circular(22.r); 22 | BorderRadius get borderRadius26 => BorderRadius.circular(26.r); 23 | BorderRadius get borderRadius50 => BorderRadius.circular(50.r); 24 | BorderRadius get borderRadius54 => BorderRadius.circular(54.r); 25 | BorderRadius get borderRadius39 => BorderRadius.circular(39.r); 26 | BorderRadius get borderRadius40 => BorderRadius.circular(40.r); 27 | BorderRadius get borderRadius48 => BorderRadius.circular(48.r); 28 | BorderRadius get borderRadius100 => BorderRadius.circular(100.r); 29 | -------------------------------------------------------------------------------- /lib/common/values/font_size.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:flutter/material.dart'; 3 | 4 | FontWeight boldFont = FontWeight.w600; 5 | FontWeight iOSBoldFont = Platform.isIOS ? FontWeight.w500 : FontWeight.normal; 6 | 7 | const double fontSize10 = 10; 8 | const double fontSize12 = 12; 9 | const double fontSize13 = 13; 10 | const double fontSize14 = 14; 11 | const double fontSize15 = 15; 12 | const double fontSize16 = 16; 13 | const double fontSize18 = 18; 14 | const double fontSize20 = 20; 15 | const double fontSize22 = 22; 16 | const double fontSize26 = 26; 17 | const double fontSize28 = 28; 18 | const double fontSize30 = 30; 19 | const double fontSize40 = 40; 20 | const double fontSize54 = 54; 21 | -------------------------------------------------------------------------------- /lib/common/values/gradient.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | Gradient buildTopToBottomLinearGradient({ 4 | required List colors, 5 | List stops = const [0.0, 1.0], 6 | }) { 7 | return LinearGradient( 8 | begin: Alignment.topCenter, //渐变开始位置 9 | end: Alignment.bottomCenter, //渐变结束位置 10 | stops: stops, //[渐变起始点, 渐变结束点] 11 | colors: colors, //渐变颜色[始点颜色, 结束颜色] 12 | ); 13 | } 14 | 15 | Gradient buildLeftToRightLinearGradient({ 16 | required List colors, 17 | List stops = const [0.0, 1.0], 18 | }) { 19 | return LinearGradient( 20 | begin: Alignment.centerLeft, 21 | end: Alignment.centerRight, 22 | stops: stops, 23 | colors: colors, 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /lib/common/values/server.dart: -------------------------------------------------------------------------------- 1 | const serverApiUrl = 'https://api1.buygogo.com'; 2 | -------------------------------------------------------------------------------- /lib/common/values/storage.dart: -------------------------------------------------------------------------------- 1 | /// TOKEN 2 | const String storageTokenKey = 'token'; 3 | 4 | /// userId 5 | const String storageUserIdKey = 'userId'; 6 | 7 | /// 设备是否第一次打开 8 | const String storageDeviceFirstOpenKey = 'device_first_open'; 9 | 10 | /// 设备是否第一次登录 11 | const String storageDeviceFirstLoginKey = 'device_first_login'; 12 | 13 | /// 是否同意使用协议 14 | const String storageDeviceAgreeKey = 'device_agree'; 15 | 16 | const String storageDevicePolicyEntryKey = 'device_policy_entryKey'; 17 | 18 | /// 设备的id 19 | const String iid = 'iid'; 20 | 21 | /// aliPushDeviceId 22 | const String aliPushDeviceIdKey = 'aliPushDeviceId'; 23 | 24 | /// 设备的语言 25 | const String storageLangKey = 'lang'; 26 | 27 | /// 通知权限提醒日期 28 | const String notificationDateTime = 'notification_date_time'; 29 | -------------------------------------------------------------------------------- /lib/common/values/values.dart: -------------------------------------------------------------------------------- 1 | library values; 2 | 3 | export 'border.dart'; 4 | export 'font_size.dart'; 5 | export 'gradient.dart'; 6 | export 'server.dart'; 7 | export 'storage.dart'; 8 | -------------------------------------------------------------------------------- /lib/http/entity/base_resp_entity.dart: -------------------------------------------------------------------------------- 1 | import '../net_work.dart'; 2 | 3 | class BaseRespEntity { 4 | BaseRespEntity({ 5 | this.code = 0, 6 | this.message = '', 7 | this.data, 8 | }); 9 | 10 | int code; 11 | String message; 12 | dynamic data; 13 | 14 | bool get succeed => code == RequestClient.httpSucceed; 15 | 16 | factory BaseRespEntity.fromJson(Map json) => BaseRespEntity( 17 | code: json["code"], 18 | message: json["message"], 19 | data: json["data"], 20 | ); 21 | 22 | Map toJson() => { 23 | "code": code, 24 | "message": message, 25 | "data": data, 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /lib/http/http_error.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import '/global.dart'; 3 | import '../widget/widget.dart'; 4 | import 'net_exception.dart'; 5 | 6 | class HttpError { 7 | ///HTTP 状态码 8 | static const int unAuthorized = 401; 9 | static const int forbidden = 403; 10 | static const int notFound = 404; 11 | static const int requestTimeout = 408; 12 | static const int internalServerError = 500; 13 | static const int badGateWay = 502; 14 | static const int serviceUnAvailable = 503; 15 | static const int gateWayTimeout = 504; 16 | 17 | ///token 无效 18 | static const int tokenInValid = 20004; 19 | 20 | ///token 过期 21 | static const int tokenPastDue = 20007; 22 | 23 | ///未知错误 24 | static const String unknown = "UNKNOWN"; 25 | 26 | ///解析错误 27 | static const String parseError = "PARSE_ERROR"; 28 | 29 | ///网络错误 30 | static const String networkError = "NETWORK_ERROR"; 31 | 32 | ///协议错误 33 | static const String httpError = "HTTP_ERROR"; 34 | 35 | ///证书错误 36 | static const String sslError = "SSL_ERROR"; 37 | 38 | ///连接超时 39 | static const String connectTimeout = "CONNECT_TIMEOUT"; 40 | 41 | ///响应超时 42 | static const String receiveTimeout = "RECEIVE_TIMEOUT"; 43 | 44 | ///发送超时 45 | static const String sendTimeout = "SEND_TIMEOUT"; 46 | 47 | ///网络请求取消 48 | static const String cancel = "CANCEL"; 49 | 50 | // static const String netException = '无网络,请检查网络'; 51 | static const String netException = 'NETWORK ERROR'; 52 | 53 | ///定义调用原生aop代码 54 | static const String androidAop = "habit"; 55 | static const String androidAopLoginMethod = "go2Login"; 56 | 57 | String code = ''; 58 | 59 | String message = ''; 60 | 61 | HttpError(this.code, this.message); 62 | 63 | HttpError.checkNetError(dynamic error) { 64 | if (error is DioError) { 65 | message = error.message; 66 | switch (error.type) { 67 | case DioErrorType.connectTimeout: 68 | code = connectTimeout; 69 | message = netException; 70 | break; 71 | case DioErrorType.receiveTimeout: 72 | code = receiveTimeout; 73 | message = netException; 74 | break; 75 | case DioErrorType.sendTimeout: 76 | code = sendTimeout; 77 | message = netException; 78 | break; 79 | case DioErrorType.response: 80 | var statusCode = error.response?.statusCode; 81 | code = statusCode.toString(); 82 | message = netException; 83 | break; 84 | case DioErrorType.cancel: 85 | code = cancel; 86 | break; 87 | case DioErrorType.other: 88 | code = unknown; 89 | message = netException; 90 | break; 91 | } 92 | } else if (error is ResponseException) { 93 | code = error.code.toString(); 94 | message = error.msg ?? ''; 95 | } else { 96 | code = unknown; 97 | message = error.toString(); 98 | } 99 | } 100 | 101 | Future handleError() async { 102 | if (code == tokenInValid.toString()) { 103 | toastInfo(msg: message); 104 | Global.logout(); 105 | } else if (code == tokenPastDue.toString()) { 106 | } else { 107 | // toastInfo(msg: message); 108 | } 109 | } 110 | 111 | @override 112 | String toString() { 113 | return 'HttpError{code: $code, message: $message}'; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /lib/http/interceptor/token_interceptor.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:dio/dio.dart'; 3 | import 'package:flutter/material.dart'; 4 | import '/global.dart'; 5 | import '../http_error.dart'; 6 | import '../net_work.dart'; 7 | 8 | ///Token拦截器 9 | class TokenInterceptor extends Interceptor { 10 | @override 11 | void onRequest( 12 | RequestOptions options, RequestInterceptorHandler handler) async { 13 | ///todo token添加 14 | Map _authorization = { 15 | 'usertoken': '', 16 | }; 17 | debugPrint('TokenInterceptor.onRequest $_authorization'); 18 | options.headers.addAll(_authorization); 19 | return handler.next(options); 20 | } 21 | 22 | @override 23 | void onResponse(Response response, ResponseInterceptorHandler handler) { 24 | RequestOptions origin = response.requestOptions; 25 | if (response.statusCode == HttpStatus.ok) { 26 | var baseResult = response.data; 27 | if (baseResult['code'] == HttpError.tokenPastDue) { 28 | var data = baseResult['data']; 29 | Global.saveUserToken(data ?? ''); 30 | RequestClient.instance.client! 31 | .fetch(origin) 32 | .then(handler.resolve) 33 | .catchError((e) => handler.reject(e)); 34 | } else { 35 | handler.next(response); 36 | } 37 | } else { 38 | handler.next(response); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/http/net_cancel_scope.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | 3 | class CancelScope { 4 | String? key; 5 | 6 | static Map mCancelTokenOfTags = 7 | {}; 8 | 9 | bool mCleared = false; 10 | 11 | CancelToken get() { 12 | CancelToken? cancel = getTag(key!); 13 | if (cancel != null) { 14 | return cancel; 15 | } 16 | return setTagIfAbsent(key!, CancelToken()); 17 | } 18 | 19 | clear() { 20 | mCleared = true; 21 | mCancelTokenOfTags.forEach((key, value) { 22 | close(value); 23 | }); 24 | } 25 | 26 | CancelToken? getTag(String tag) { 27 | return mCancelTokenOfTags[tag]; 28 | } 29 | 30 | CancelToken setTagIfAbsent(String tag, CancelToken cancelToken) { 31 | CancelToken? previous = getTag(tag); 32 | if (previous == null) { 33 | mCancelTokenOfTags[tag] = cancelToken; 34 | } 35 | CancelToken result = previous ?? cancelToken; 36 | if (mCleared) { 37 | close(result); 38 | } 39 | return result; 40 | } 41 | 42 | void cancel(String key){ 43 | CancelToken? cancelToken = getTag(key); 44 | close(cancelToken!); 45 | } 46 | 47 | void close(CancelToken cancelToken) { 48 | if (cancelToken.isCancelled) { 49 | return; 50 | } 51 | cancelToken.cancel(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/http/net_exception.dart: -------------------------------------------------------------------------------- 1 | class ResponseException implements Exception { 2 | final int? code; 3 | final String? msg; 4 | 5 | ResponseException({this.code, this.msg}); 6 | 7 | @override 8 | String toString() { 9 | if (msg == null) return "Exception"; 10 | return "$msg"; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/initial_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | class InitialBinding implements Bindings { 4 | @override 5 | void dependencies() {} 6 | } 7 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 4 | import 'package:flutter_easyloading/flutter_easyloading.dart'; 5 | import 'package:flutter_localizations/flutter_localizations.dart'; 6 | import 'app/routes/app_pages.dart'; 7 | import 'common/langs/translation_service.dart'; 8 | import 'common/utils/utils.dart'; 9 | import 'global.dart'; 10 | import 'initial_binding.dart'; 11 | 12 | Future main() async { 13 | WidgetsFlutterBinding.ensureInitialized(); 14 | await initServices(); 15 | //强制竖屏 16 | StatusBarKit.setPortrait().then((_) { 17 | runApp(const MyApp()); 18 | }); 19 | } 20 | 21 | Future initServices() async { 22 | debugPrint('starting services ...'); 23 | await Global.init(); 24 | debugPrint('All services started...'); 25 | } 26 | 27 | class MyApp extends StatelessWidget { 28 | const MyApp({Key? key}) : super(key: key); 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | return ScreenUtilInit( 33 | designSize: const Size(390, 844), // iPhone 13尺寸 34 | builder: ([BuildContext? _, __]) { 35 | return GetMaterialApp( 36 | debugShowCheckedModeBanner: false, 37 | defaultTransition: Transition.cupertino, 38 | 39 | // 日志 40 | enableLog: true, 41 | logWriterCallback: Logger.write, 42 | // 路由 43 | getPages: AppPages.routes, 44 | navigatorObservers: [Global.routeObserver], 45 | // 启动页面 46 | initialRoute: AppPages.initial, 47 | initialBinding: InitialBinding(), 48 | // 多语言设置 49 | locale: TranslationService.locale, //刚进入App时,默认显示语言 50 | fallbackLocale: TranslationService.fallbackLocale, //语言选择无效时,备用语言 51 | translations: TranslationService(), //配置显示国际化内容 52 | builder: (context, widget) { 53 | return MediaQuery( 54 | ///设置文字大小不随系统设置改变 55 | data: MediaQuery.of(context).copyWith(textScaler: const TextScaler.linear(1.0)), 56 | child: FlutterEasyLoading(child: widget), 57 | ); 58 | }, 59 | localizationsDelegates: const [ 60 | GlobalMaterialLocalizations.delegate, 61 | GlobalWidgetsLocalizations.delegate, 62 | GlobalCupertinoLocalizations.delegate, 63 | //一定要配置,否则iphone手机长按编辑框有白屏卡着的bug出现 64 | ], 65 | supportedLocales: const [ 66 | Locale('en', 'US'), 67 | Locale('zh', 'CN'), 68 | ], 69 | ); 70 | }, 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/widget/async_image_banner_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '/common/utils/utils.dart'; 3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 4 | import 'package:carousel_pro_nullsafety/carousel_pro_nullsafety.dart' 5 | as my_carousel_comp; 6 | 7 | class AsyncImageBannerWidget extends StatefulWidget { 8 | const AsyncImageBannerWidget({ 9 | Key? key, 10 | required this.children, 11 | required this.defaultImage, 12 | this.padding, 13 | this.onTap, 14 | }) : super(key: key); 15 | final List children; 16 | final void Function(int)? onTap; 17 | final String defaultImage; 18 | 19 | final EdgeInsets? padding; 20 | @override 21 | State createState() => _AsyncImageBannerState(); 22 | } 23 | 24 | class _AsyncImageBannerState extends State { 25 | @override 26 | Widget build(BuildContext context) { 27 | if (widget.children.isEmpty) return const SizedBox(); 28 | return renderAsyncItem(widget.defaultImage); 29 | } 30 | 31 | Future loadAsync(String url) async { 32 | var baseR = await WidgetUtil.getImageWH(url: url); 33 | Size baseSize = baseR.size; 34 | return baseSize; 35 | } 36 | 37 | Widget renderAsyncItem(String imageUrl) { 38 | final padding = widget.padding ?? 39 | EdgeInsets.symmetric(vertical: 10.h, horizontal: 10.w); 40 | final horizontalSpace = padding.horizontal; 41 | 42 | return FutureBuilder( 43 | future: loadAsync(imageUrl), 44 | builder: ((context, snapshot) { 45 | if (snapshot.connectionState == ConnectionState.done && 46 | snapshot.hasData) { 47 | final size = snapshot.data!; 48 | final imageHeight = size.height; 49 | final imageWidth = size.width; 50 | return LayoutBuilder(builder: (context, constraints) { 51 | var maxWidth = constraints.maxWidth - horizontalSpace; 52 | final fac = imageWidth / maxWidth; 53 | var realHeight = imageHeight / fac; 54 | 55 | return Container( 56 | width: maxWidth, 57 | height: realHeight, 58 | margin: padding, 59 | child: my_carousel_comp.Carousel( 60 | images: widget.children, 61 | indicatorBgPadding: 8, 62 | radius: const Radius.circular(0), 63 | animationCurve: Curves.easeOutQuart, 64 | onImageChange: (a, b) {}, 65 | onImageTap: widget.onTap, 66 | dotSize: 4.w, 67 | dotSpacing: 10, 68 | showIndicator: widget.children.length > 1, 69 | dotColor: Colors.white.withOpacity(0.4), 70 | dotIncreasedColor: Colors.white, 71 | dotBgColor: Colors.transparent, 72 | ), 73 | ); 74 | }); 75 | } 76 | return _placeholder(); 77 | }), 78 | ); 79 | } 80 | 81 | Widget _placeholder() { 82 | return SizedBox(width: ScreenUtil().screenWidth, height: 120.h); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/widget/auto_scroll_to_item_widget.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | class ScrollToItemBaseObject { 6 | final GlobalKey globalKey = GlobalKey(); 7 | } 8 | 9 | class AutoScrollToItemWidget extends StatefulWidget { 10 | final IndexedWidgetBuilder itemBuilder; 11 | final List list; 12 | final Duration? duration; 13 | final double topDistance; 14 | 15 | const AutoScrollToItemWidget( 16 | {Key? key, 17 | required this.list, 18 | this.duration, 19 | this.topDistance = 0, 20 | required this.itemBuilder}) 21 | : super(key: key); 22 | 23 | @override 24 | _ScrollToItemState createState() => _ScrollToItemState(); 25 | } 26 | 27 | class _ScrollToItemState extends State { 28 | final ScrollController _scrollController = ScrollController(); 29 | final GlobalKey _scrollKey = GlobalKey(); 30 | Timer? _timer; 31 | int currentIndex = 0; 32 | 33 | @override 34 | void initState() { 35 | super.initState(); 36 | currentIndex = 0; 37 | _startTimer(); 38 | } 39 | 40 | _startTimer() { 41 | _timer = Timer.periodic(const Duration(seconds: 3), (timer) { 42 | if (mounted) { 43 | if (widget.list.isEmpty) { 44 | return; 45 | } 46 | if (currentIndex == widget.list.length - 1) { 47 | currentIndex = 0; 48 | } else { 49 | currentIndex = currentIndex + 2; 50 | if (currentIndex >= widget.list.length - 1) { 51 | currentIndex = widget.list.length - 1; 52 | } 53 | } 54 | scrollToIndex(currentIndex); 55 | } else { 56 | _timer?.cancel(); 57 | } 58 | }); 59 | } 60 | 61 | _cancelTimer() { 62 | _timer?.cancel(); 63 | } 64 | 65 | @override 66 | Widget build(BuildContext context) { 67 | return SingleChildScrollView( 68 | key: _scrollKey, 69 | controller: _scrollController, 70 | child: ListView.builder( 71 | itemBuilder: widget.itemBuilder, 72 | itemCount: widget.list.length, 73 | shrinkWrap: true, 74 | physics: const NeverScrollableScrollPhysics(), 75 | ), 76 | ); 77 | } 78 | 79 | @override 80 | void didUpdateWidget(covariant AutoScrollToItemWidget oldWidget) { 81 | super.didUpdateWidget(oldWidget); 82 | } 83 | 84 | // 滑动到指定下标的item 85 | void scrollToIndex(int index) { 86 | if (index > widget.list.length) { 87 | return; 88 | } 89 | ScrollToItemBaseObject item = widget.list[index]; 90 | if (item.globalKey.currentContext != null) { 91 | RenderBox renderBox = 92 | item.globalKey.currentContext!.findRenderObject() as RenderBox; 93 | double dy = renderBox 94 | .localToGlobal(Offset.zero, 95 | ancestor: _scrollKey.currentContext!.findRenderObject()) 96 | .dy; 97 | var offset = dy + _scrollController.offset; 98 | // double stateTopHei = MediaQueryData.fromWindow(window).padding.top; 99 | _scrollController.animateTo(offset - widget.topDistance, 100 | duration: widget.duration ?? const Duration(milliseconds: 500), 101 | curve: Curves.linear); 102 | } else { 103 | debugPrint("Please bind the key to the widget"); 104 | } 105 | } 106 | 107 | @override 108 | void dispose() { 109 | super.dispose(); 110 | _scrollController.dispose(); 111 | _cancelTimer(); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /lib/widget/badge.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomBadge extends StatelessWidget { 4 | const CustomBadge({ 5 | Key? key, 6 | this.alignment = Alignment.topRight, 7 | this.childPadding = EdgeInsets.zero, 8 | required this.child, 9 | this.badgeContent, 10 | this.elevation = 2, 11 | this.shape, 12 | this.badgeBackground, 13 | this.borderRadius, 14 | this.badgePadding = const EdgeInsets.all(3), 15 | this.badgeMargin, 16 | }) : super(key: key); 17 | final EdgeInsetsGeometry? childPadding; 18 | final EdgeInsetsGeometry? badgePadding; 19 | final EdgeInsetsGeometry? badgeMargin; 20 | final Alignment alignment; 21 | final Widget child; 22 | final Widget? badgeContent; 23 | final double elevation; 24 | final ShapeBorder? shape; 25 | final Color? badgeBackground; 26 | final BorderRadiusGeometry? borderRadius; 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return Stack( 31 | fit: StackFit.loose, 32 | children: [ 33 | Container(padding: childPadding, child: child), 34 | Positioned.fill( 35 | child: Container( 36 | alignment: alignment, 37 | margin: badgeMargin, 38 | child: Material( 39 | shape: borderRadius != null ? null : shape, 40 | borderRadius: borderRadius, 41 | color: badgeBackground, 42 | child: Container( 43 | padding: badgePadding, 44 | child: IntrinsicHeight( 45 | child: IntrinsicWidth(child: badgeContent), 46 | ), 47 | ), 48 | ), 49 | ), 50 | ), 51 | ], 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/widget/badge_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'badge.dart'; 4 | 5 | ///圆点数字/红点提醒 6 | class BadgeWidget extends StatelessWidget { 7 | final int count; 8 | final bool isDot; 9 | final double elevation; 10 | final bool toAnimate; 11 | final BorderRadiusGeometry? borderRadius; 12 | final EdgeInsetsGeometry? padding; 13 | final Color badgeColor; 14 | // final BorderSide? borderSide; 15 | final TextStyle textStyle; 16 | final EdgeInsetsGeometry? badgePadding; 17 | final EdgeInsetsGeometry? badgeMargain; 18 | final EdgeInsetsGeometry? childPadding; 19 | final Widget child; 20 | final ShapeBorder? shape; 21 | 22 | const BadgeWidget({ 23 | Key? key, 24 | required this.child, 25 | this.count = 0, 26 | this.isDot = false, 27 | this.elevation = 2, 28 | this.toAnimate = false, 29 | this.borderRadius, 30 | // this.borderSide, 31 | this.shape = const CircleBorder(), 32 | this.padding, 33 | this.badgePadding, 34 | this.badgeMargain, 35 | this.childPadding, 36 | this.badgeColor = Colors.red, 37 | // this.position, 38 | this.textStyle = const TextStyle( 39 | fontSize: 10, 40 | color: Colors.white, 41 | ), 42 | }) : super(key: key); 43 | @override 44 | Widget build(BuildContext context) { 45 | String _num = count > 99 ? '99+' : count.toString(); 46 | return isDot || count != 0 47 | ? CustomBadge( 48 | shape: shape, 49 | childPadding: childPadding, 50 | borderRadius: borderRadius, 51 | badgeMargin: badgeMargain, 52 | // ?? BorderRadius.circular(9.r), 53 | // borderSide: borderSide ?? BorderSide.none, 54 | elevation: elevation, 55 | // toAnimate: toAnimate, 56 | child: child, 57 | badgeBackground: badgeColor, 58 | badgePadding: badgePadding ?? const EdgeInsets.all(3), 59 | badgeContent: isDot 60 | ? null 61 | : Container( 62 | // height: 16.w, 63 | alignment: Alignment.center, 64 | // padding: badgePadding ?? EdgeInsets.zero, 65 | child: Text( 66 | _num, 67 | style: textStyle, 68 | ), 69 | ), 70 | ) 71 | : Container(padding: childPadding, child: child); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/widget/bottom_height.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | extension BottomHeight on GetInterface { 5 | double get bottomHeight { 6 | return bottomBarHeight / pixelRatio; 7 | } 8 | } 9 | 10 | extension MediaRemoveBottom on BuildContext { 11 | Widget removeBottomPadding([Widget? child]) { 12 | final bottom = MediaQuery.of(this).padding.bottom; 13 | return MediaQuery.removePadding( 14 | context: this, 15 | child: Container(padding: EdgeInsets.only(bottom: bottom), child: child), 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/widget/check_box.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | 4 | class CheckBoxWidget extends StatefulWidget { 5 | final bool initValue; 6 | final bool isRound; //是否圆形 7 | final double size; 8 | final BorderSide? side; 9 | final Function(bool)? onChanged; 10 | final Widget Function(Widget child)? builder; 11 | 12 | const CheckBoxWidget({ 13 | Key? key, 14 | required this.initValue, 15 | this.isRound = false, 16 | this.size = 16, 17 | this.side, 18 | this.onChanged, 19 | this.builder, 20 | }) : super(key: key); 21 | 22 | @override 23 | createState() => _CheckBoxWidget(); 24 | } 25 | 26 | class _CheckBoxWidget extends State { 27 | late bool _initValue; 28 | @override 29 | void initState() { 30 | super.initState(); 31 | _initValue = widget.initValue; 32 | } 33 | 34 | @override 35 | void didUpdateWidget(covariant CheckBoxWidget oldWidget) { 36 | super.didUpdateWidget(oldWidget); 37 | if (widget.initValue != oldWidget.initValue) { 38 | _initValue = widget.initValue; 39 | } 40 | } 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | Widget child = SizedBox( 45 | width: widget.size.w, 46 | height: widget.size.w, 47 | child: Checkbox( 48 | value: _initValue, 49 | activeColor: const Color(0xFFDC593B), 50 | shape: widget.isRound ? const CircleBorder() : null, 51 | side: widget.side, 52 | materialTapTargetSize: MaterialTapTargetSize.padded, 53 | onChanged: (value) { 54 | setState(() { 55 | _initValue = !_initValue; 56 | }); 57 | widget.onChanged?.call(_initValue); 58 | }, 59 | ), 60 | ); 61 | 62 | if (widget.builder != null) { 63 | child = widget.builder!(child); 64 | } 65 | 66 | return GestureDetector( 67 | onTap: () { 68 | setState(() { 69 | _initValue = !_initValue; 70 | }); 71 | widget.onChanged?.call(_initValue); 72 | }, 73 | child: child, 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/widget/city_picker/city_picker.dart: -------------------------------------------------------------------------------- 1 | library city_picker; 2 | 3 | export 'src/city_picker.dart'; 4 | -------------------------------------------------------------------------------- /lib/widget/city_picker/src/check.dart: -------------------------------------------------------------------------------- 1 | class PicketUtil { 2 | /// 字符串不为空 3 | static bool strNoEmpty(String? value) { 4 | if (value == null) return false; 5 | 6 | return value.trim().isNotEmpty; 7 | } 8 | 9 | /// 字符串为空 10 | static bool strEmpty(String? value) { 11 | if (value == null) return true; 12 | 13 | return value.trim().isEmpty; 14 | } 15 | 16 | /// MAp不为空 17 | static bool mapNoEmpty(Map? value) { 18 | if (value == null) return false; 19 | return value.isNotEmpty; 20 | } 21 | 22 | /// MAp为空 23 | static bool mapEmpty(Map? value) { 24 | if (value == null) return true; 25 | return value.isEmpty; 26 | } 27 | 28 | ///判断List是否为空 29 | static bool listNoEmpty(List? list) { 30 | if (list == null) return false; 31 | 32 | if (list.isEmpty) return false; 33 | 34 | return true; 35 | } 36 | 37 | ///判断List是否为空 38 | static bool listEmpty(List? list) { 39 | if (list == null) return true; 40 | 41 | if (list.isEmpty) return true; 42 | 43 | return false; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/widget/copy_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 4 | import '../common/utils/utils.dart'; 5 | import '../common/values/values.dart'; 6 | import 'toast.dart'; 7 | 8 | class CopyButtonWidget extends StatelessWidget { 9 | final String content; 10 | final Decoration? decoration; 11 | final Color? color; 12 | final double fontSize; 13 | final EdgeInsetsGeometry? padding; 14 | 15 | const CopyButtonWidget({ 16 | Key? key, 17 | required this.content, 18 | this.color = const Color(0xFF58595B), 19 | this.decoration, 20 | this.fontSize = 13, 21 | this.padding, 22 | }) : super(key: key); 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return InkWell( 27 | onTap: () async { 28 | if (content.isEmpty) return; 29 | await ClipboardKit.setData(content); 30 | toastInfo(msg: 'copy_success'.tr); 31 | }, 32 | child: Container( 33 | padding: 34 | padding ?? EdgeInsets.symmetric(horizontal: 8.w, vertical: 3.h), 35 | alignment: Alignment.center, 36 | decoration: decoration ?? 37 | BoxDecoration( 38 | borderRadius: borderRadius3, 39 | border: Border.all(width: 1.w, color: const Color(0xFF9C9C9C)), 40 | ), 41 | child: Text( 42 | 'copy'.tr, 43 | style: TextStyle( 44 | color: color, 45 | fontSize: fontSize.sp, 46 | ), 47 | ), 48 | ), 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/widget/count_down.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 4 | 5 | typedef FormatDurationFunction = String Function(Duration timeDiff); 6 | 7 | class CountDown extends StatefulWidget { 8 | @override 9 | _CountDownState createState() => _CountDownState(); 10 | final DateTime endTime; 11 | final String labelPrefixStart; 12 | final String labelPrefix; 13 | final String labelPrefixEnd; 14 | final FormatDurationFunction formatter; 15 | final TextStyle labelStyle; 16 | final VoidCallback? timeoutCallback; 17 | final Function? timeCallback; 18 | final Function? timeFinishCallback; 19 | 20 | const CountDown(this.endTime, this.labelStyle, this.formatter, 21 | {Key? key, 22 | this.labelPrefix = '', 23 | this.labelPrefixStart = '', 24 | this.labelPrefixEnd = '', 25 | this.timeoutCallback, 26 | this.timeCallback, 27 | this.timeFinishCallback}) 28 | : super(key: key); 29 | } 30 | 31 | class _CountDownState extends State { 32 | Duration timeDiff = Duration.zero; 33 | late Timer _timer; 34 | 35 | @override 36 | void initState() { 37 | super.initState(); 38 | if (mounted) { 39 | setState(() { 40 | timeDiff = widget.endTime.difference(DateTime.now()); 41 | }); 42 | } 43 | _timer = Timer.periodic(const Duration(seconds: 1), (timer) async { 44 | final diff = widget.endTime.difference(DateTime.now()); 45 | if (mounted) { 46 | setState(() { 47 | timeDiff = diff; 48 | }); 49 | } 50 | if (widget.timeCallback != null) widget.timeCallback!(_timer.tick); 51 | if (diff <= Duration.zero) { 52 | timer.cancel(); 53 | if (widget.timeFinishCallback != null) widget.timeFinishCallback!(true); 54 | widget.timeoutCallback?.call(); 55 | } 56 | }); 57 | } 58 | 59 | @override 60 | void dispose() { 61 | _timer.cancel(); 62 | super.dispose(); 63 | } 64 | 65 | @override 66 | Widget build(BuildContext context) { 67 | if (timeDiff <= Duration.zero) return const SizedBox(); 68 | final _label = widget.formatter.call(timeDiff); 69 | final left = widget.labelPrefix.isEmpty ? '' : '${widget.labelPrefix} '; 70 | return Row( 71 | mainAxisSize: MainAxisSize.min, 72 | mainAxisAlignment: MainAxisAlignment.start, 73 | children: [ 74 | Text.rich( 75 | TextSpan( 76 | text: widget.labelPrefixStart, 77 | style: TextStyle( 78 | color: const Color(0xFF84858A), 79 | fontSize: 10.sp, 80 | ), 81 | children: [ 82 | TextSpan( 83 | text: '$left$_label', 84 | style: widget.labelStyle, 85 | ), 86 | TextSpan( 87 | text: widget.labelPrefixEnd, 88 | style: TextStyle( 89 | color: const Color(0xFF84858A), 90 | fontSize: 10.sp, 91 | ), 92 | ), 93 | ], 94 | ), 95 | ) 96 | ], 97 | ); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lib/widget/custom_list_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:quiver/strings.dart'; 3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 4 | import '../common/values/values.dart'; 5 | import 'badge_widget.dart'; 6 | 7 | ///自定义ListTile 8 | class CustomListTile extends StatelessWidget { 9 | final Widget? leadingIcon; //左边图标 10 | final String title; //文字 11 | final String subTitle; //右边文字 12 | final Widget? trailingIcon; //右边图标 13 | final TextStyle titleStyle; //左边文字格式 14 | final TextStyle subTitleStyle; //右边文字格式 15 | final EdgeInsetsGeometry leftMargin; //左边图标和文字间距 16 | final EdgeInsetsGeometry rightMargin; //右边文字和图标间距 17 | final Widget? subWidget; //右边控件:icon... 18 | final bool showNotification; //是否显示小红点 19 | final GestureTapCallback? onTap; //点击事件 20 | final bool needUnderLine; //是否需要下划线 21 | const CustomListTile( 22 | this.title, { 23 | Key? key, 24 | this.leadingIcon, 25 | this.subTitle = '', 26 | this.trailingIcon, 27 | this.titleStyle = const TextStyle( 28 | fontSize: fontSize14, 29 | color: Color(0xFF1A1A1A), 30 | ), 31 | this.subTitleStyle = const TextStyle( 32 | fontSize: fontSize14, 33 | color: Color(0xFF1A1A1A), 34 | ), 35 | this.leftMargin = const EdgeInsets.only(left: 8), 36 | this.rightMargin = const EdgeInsets.only(right: 8), 37 | this.subWidget, 38 | this.showNotification = false, 39 | this.onTap, 40 | this.needUnderLine = true, 41 | }) : super(key: key); 42 | @override 43 | Widget build(BuildContext context) { 44 | return InkWell( 45 | onTap: onTap, 46 | child: Container( 47 | width: ScreenUtil().screenWidth, 48 | padding: EdgeInsets.only(top: 12.h), 49 | child: Column( 50 | children: [ 51 | Row( 52 | mainAxisSize: MainAxisSize.min, 53 | children: [ 54 | leadingIcon ?? const SizedBox(), 55 | Container( 56 | margin: leftMargin, 57 | child: Text( 58 | title, 59 | style: titleStyle, 60 | ), 61 | ), 62 | const Spacer(), 63 | isBlank(subTitle) 64 | ? const SizedBox() 65 | : Container( 66 | margin: rightMargin, 67 | child: Text( 68 | subTitle, 69 | style: subTitleStyle, 70 | ), 71 | ), 72 | subWidget ?? const SizedBox(), 73 | showNotification 74 | ? BadgeWidget( 75 | isDot: true, 76 | borderRadius: borderRadius5, 77 | child: const SizedBox(), 78 | ) 79 | : const SizedBox(), 80 | trailingIcon ?? const SizedBox(), 81 | ], 82 | ), 83 | SizedBox( 84 | height: needUnderLine ? 11.h : 12.h, 85 | ), 86 | needUnderLine 87 | ? Container( 88 | color: const Color(0xFFEFEFEF), 89 | height: 1.h, 90 | ) 91 | : const SizedBox() 92 | ], 93 | ), 94 | ), 95 | ); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lib/widget/dashed_rect.dart: -------------------------------------------------------------------------------- 1 | //虚线 2 | import 'package:flutter/material.dart'; 3 | import 'dart:math' as math; 4 | 5 | class DashedRect extends StatelessWidget { 6 | final Color color; 7 | final double strokeWidth; //虚线高度 8 | final double gap; //点之间的间隔 9 | 10 | const DashedRect( 11 | {Key? key, 12 | this.color = Colors.black, 13 | this.strokeWidth = 1.0, 14 | this.gap = 5.0}) 15 | : super(key: key); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return Padding( 20 | padding: EdgeInsets.all(strokeWidth / 2), 21 | child: CustomPaint( 22 | painter: 23 | DashRectPainter(color: color, strokeWidth: strokeWidth, gap: gap), 24 | ), 25 | ); 26 | } 27 | } 28 | 29 | class DashRectPainter extends CustomPainter { 30 | double strokeWidth; 31 | Color color; 32 | double gap; 33 | 34 | DashRectPainter( 35 | {this.strokeWidth = 5.0, this.color = Colors.red, this.gap = 5.0}); 36 | 37 | @override 38 | void paint(Canvas canvas, Size size) { 39 | Paint dashedPaint = Paint() 40 | ..color = color 41 | ..strokeWidth = strokeWidth 42 | ..style = PaintingStyle.stroke; 43 | 44 | double x = size.width; 45 | double y = size.height; 46 | 47 | Path _topPath = getDashedPath( 48 | a: const math.Point(0, 0), 49 | b: math.Point(x, 0), 50 | gap: gap, 51 | ); 52 | 53 | Path _rightPath = getDashedPath( 54 | a: math.Point(x, 0), 55 | b: math.Point(x, y), 56 | gap: gap, 57 | ); 58 | 59 | Path _bottomPath = getDashedPath( 60 | a: math.Point(0, y), 61 | b: math.Point(x, y), 62 | gap: gap, 63 | ); 64 | 65 | Path _leftPath = getDashedPath( 66 | a: const math.Point(0, 0), 67 | b: math.Point(0.001, y), 68 | gap: gap, 69 | ); 70 | 71 | canvas.drawPath(_topPath, dashedPaint); 72 | canvas.drawPath(_rightPath, dashedPaint); 73 | canvas.drawPath(_bottomPath, dashedPaint); 74 | canvas.drawPath(_leftPath, dashedPaint); 75 | } 76 | 77 | Path getDashedPath({ 78 | required math.Point a, 79 | required math.Point b, 80 | @required gap, 81 | }) { 82 | Size size = Size(b.x - a.x, b.y - a.y); 83 | Path path = Path(); 84 | path.moveTo(a.x, a.y); 85 | bool shouldDraw = true; 86 | math.Point currentPoint = math.Point(a.x, a.y); 87 | 88 | num radians = math.atan(size.height / size.width); 89 | 90 | num dx = math.cos(radians) * gap < 0 91 | ? math.cos(radians) * gap * -1 92 | : math.cos(radians) * gap; 93 | 94 | num dy = math.sin(radians) * gap < 0 95 | ? math.sin(radians) * gap * -1 96 | : math.sin(radians) * gap; 97 | 98 | while (currentPoint.x <= b.x && currentPoint.y <= b.y) { 99 | shouldDraw 100 | ? path.lineTo(currentPoint.x.toDouble(), currentPoint.y.toDouble()) 101 | : path.moveTo(currentPoint.x.toDouble(), currentPoint.y.toDouble()); 102 | shouldDraw = !shouldDraw; 103 | currentPoint = math.Point( 104 | currentPoint.x + dx, 105 | currentPoint.y + dy, 106 | ); 107 | } 108 | return path; 109 | } 110 | 111 | @override 112 | bool shouldRepaint(CustomPainter oldDelegate) { 113 | return true; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /lib/widget/easy_refresh/easy_refresh.dart: -------------------------------------------------------------------------------- 1 | library esay_refresh; 2 | 3 | export 'src/empty_result_widget.dart'; 4 | export 'src/no_more_result.dart'; 5 | export 'src/paged_list_view.dart'; 6 | -------------------------------------------------------------------------------- /lib/widget/easy_refresh/src/empty_result_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | import 'package:get/get.dart'; 4 | import '/widget/widget.dart'; 5 | 6 | class EmptyResultWidget extends StatelessWidget { 7 | final String icon; 8 | final String text; 9 | final double? iconWidth; 10 | final double? iconHeight; 11 | final bool showReload; 12 | final VoidCallback? onReload; 13 | 14 | const EmptyResultWidget({ 15 | Key? key, 16 | required this.icon, 17 | this.text = '', 18 | this.iconWidth = 142, 19 | this.iconHeight = 110, 20 | this.showReload = false, 21 | this.onReload, 22 | }) : super(key: key); 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return Center( 27 | child: SizedBox( 28 | width: ScreenUtil().screenWidth, 29 | child: Column( 30 | crossAxisAlignment: CrossAxisAlignment.center, 31 | mainAxisSize: MainAxisSize.min, 32 | children: [ 33 | getIconPngWithSize( 34 | icon, 35 | width: iconWidth?.w, 36 | height: iconHeight?.h, 37 | fit: BoxFit.contain, 38 | ), 39 | SizedBox( 40 | height: 30.5.h, 41 | ), 42 | Text( 43 | showReload ? 'reload_hint'.tr : text, 44 | style: TextStyle( 45 | color: const Color(0xFF999999), 46 | fontSize: 16.sp, 47 | ), 48 | ), 49 | SizedBox( 50 | height: 20.h, 51 | ), 52 | showReload 53 | ? SizedBox( 54 | width: 124.w, 55 | height: 44.h, 56 | child: SecondaryButton( 57 | 'reload'.tr, 58 | onReload, 59 | padding: EdgeInsets.zero, 60 | ), 61 | ) 62 | : const SizedBox() 63 | ], 64 | ), 65 | ), 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/widget/easy_refresh/src/no_more_result.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | import 'package:get/get.dart'; 4 | 5 | ///无数据提醒小尾巴组件 6 | class NoMoreResult extends StatelessWidget { 7 | const NoMoreResult({ 8 | Key? key, 9 | this.text, 10 | }) : super(key: key); 11 | 12 | final String? text; 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Container( 17 | margin: const EdgeInsets.all(12), 18 | child: Text( 19 | text ?? 'no_more'.tr, 20 | textAlign: TextAlign.center, 21 | style: TextStyle( 22 | fontSize: 12.sp, 23 | color: const Color(0xFFCDCDD0), 24 | ), 25 | ), 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/widget/element.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | 4 | import 'image/image.dart'; 5 | 6 | Widget buildGoToIconWidget({ 7 | double? iconSize, 8 | Color iconColor = const Color(0xFFA3A3A3), 9 | }) { 10 | return Icon( 11 | Icons.arrow_forward_ios_rounded, 12 | size: iconSize ?? 16.w, 13 | color: iconColor, 14 | ); 15 | } 16 | 17 | Widget buildDialogClosedWidget(BuildContext context, 18 | {BorderRadius? borderRadius}) { 19 | return InkWell( 20 | borderRadius: borderRadius, 21 | onTap: () { 22 | Navigator.of(context).pop(false); 23 | }, 24 | child: getIconPng( 25 | 'ic_dialog_grey', 26 | iconSize: 54.w, 27 | ), 28 | ); 29 | } 30 | 31 | Widget buildLoading({double iconSize = 64}) { 32 | return Image.asset( 33 | 'assets/images/ic_loading.gif', 34 | width: iconSize.w, 35 | height: iconSize.w, 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /lib/widget/empty_card_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '/common/values/values.dart'; 3 | 4 | ///卡片式布局 5 | class EmptyCardWidget extends StatelessWidget { 6 | final Widget child; 7 | final double? width; //宽 8 | final double? height; //高 9 | final Color color; //背景颜色 10 | final double elevation; //阴影 11 | final EdgeInsetsGeometry? padding; 12 | final EdgeInsetsGeometry? margin; 13 | final BorderSide side; //边框 14 | final BorderRadiusGeometry? borderRadius; // 圆角值 15 | 16 | const EmptyCardWidget({ 17 | Key? key, 18 | required this.child, 19 | this.color = Colors.white, 20 | this.elevation = 0, 21 | this.side = BorderSide.none, 22 | this.width, 23 | this.height, 24 | this.borderRadius, 25 | this.padding, 26 | this.margin, 27 | }) : super(key: key); 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return Card( 32 | child: Container( 33 | width: width, 34 | height: height, 35 | padding: padding, 36 | child: child, 37 | ), 38 | color: color, 39 | elevation: elevation, 40 | margin: margin ?? EdgeInsets.zero, 41 | shape: RoundedRectangleBorder( 42 | side: side, 43 | borderRadius: borderRadius ?? borderRadius15, 44 | ), 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/widget/first_animation_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class FirstAnimationWidget extends StatefulWidget { 4 | const FirstAnimationWidget({ 5 | Key? key, 6 | required this.child, 7 | required this.callback, 8 | }) : super(key: key); 9 | final Widget child; 10 | final VoidCallback callback; 11 | 12 | @override 13 | State createState() => _FirstAnimationWidgetState(); 14 | } 15 | 16 | class _FirstAnimationWidgetState extends State 17 | with SingleTickerProviderStateMixin { 18 | late AnimationController controller; 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | controller = AnimationController( 24 | vsync: this, duration: const Duration(milliseconds: 300)); 25 | _startAnimated(); 26 | } 27 | 28 | void _startAnimated() { 29 | controller.forward(); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | final opacity = CurveTween(curve: Curves.easeInOut).animate(controller); 35 | return PopScope( 36 | canPop: false, 37 | onPopInvokedWithResult: (bool didPop, Object? result) async { 38 | widget.callback(); 39 | return; 40 | }, 41 | child: FadeTransition( 42 | opacity: opacity, 43 | child: GestureDetector( 44 | onTap: widget.callback, 45 | child: Material( 46 | color: Colors.black54, 47 | child: SizedBox.expand( 48 | child: GestureDetector(onTap: () {}, child: widget.child)), 49 | ), 50 | ), 51 | ), 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/widget/html_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_html/flutter_html.dart'; 3 | 4 | class HtmlWidget extends StatelessWidget { 5 | const HtmlWidget({ 6 | Key? key, 7 | required this.data, 8 | this.style = const {}, 9 | }) : super(key: key); 10 | final String data; 11 | final Map style; 12 | @override 13 | Widget build(BuildContext context) { 14 | return Html( 15 | key: ValueKey(data), 16 | data: data, 17 | style: style, 18 | shrinkWrap: true, 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/widget/icon_stack_flow_delegate.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'dart:math' as math; 3 | 4 | /// FlowDelegate 5 | /// 6 | /// 当前实现:只支持单行布局,如果children超过布局数量的话会忽略掉后面的的[Widget] 7 | class IconStackFlowDelegate extends FlowDelegate { 8 | IconStackFlowDelegate({ 9 | required this.maxExtent, 10 | required this.itemExtent, 11 | this.axisDirection = AxisDirection.left, 12 | this.overlaps = 0.0, 13 | }); 14 | 15 | // 交叉轴的高度 16 | final double maxExtent; 17 | // 单个 18 | final Size itemExtent; 19 | 20 | // 只支持左右布局 21 | final AxisDirection axisDirection; 22 | 23 | // 重叠的部分 24 | final double overlaps; 25 | 26 | @override 27 | Size getSize(BoxConstraints constraints) { 28 | return Size(constraints.maxWidth, constraints.constrainHeight(maxExtent)); 29 | } 30 | 31 | @override 32 | void paintChildren(FlowPaintingContext context) { 33 | final size = context.size; 34 | final childCount = context.childCount; 35 | if (childCount <= 0) return; 36 | 37 | final childSize = context.getChildSize(0); 38 | // final extent = math.min(childSize!.width, itemExtent.width); 39 | final extent = childSize!.width; 40 | 41 | // 除了第一个child的总容量 42 | final otherExtent = size.width - extent; 43 | // 44 | final extentWithOverlaps = extent - overlaps; 45 | 46 | // 反方向的布局会有额外的宽度 47 | var extraWidth = 0.0; 48 | 49 | if (extentWithOverlaps > 0 && otherExtent > 0) { 50 | final realCount = otherExtent ~/ extentWithOverlaps + 1; 51 | var index = math.min(childCount, realCount) - 1; 52 | 53 | if (isRevese) { 54 | extraWidth = size.width - index * extentWithOverlaps - childSize.width; 55 | } 56 | while (index > 0) { 57 | final transform = Matrix4.translationValues( 58 | extraWidth + index * extentWithOverlaps, 0, 0); 59 | context.paintChild(index, transform: transform); 60 | index--; 61 | } 62 | } else { 63 | if (isRevese) { 64 | extraWidth = size.width - childSize.width; 65 | } 66 | } 67 | context.paintChild(0, 68 | transform: Matrix4.translationValues(extraWidth, 0, 0)); 69 | } 70 | 71 | bool get isRevese => 72 | axisDirection == AxisDirection.right || 73 | axisDirection == AxisDirection.down; 74 | 75 | @override 76 | BoxConstraints getConstraintsForChild(int i, BoxConstraints constraints) { 77 | return constraints.tighten( 78 | width: itemExtent.width, height: itemExtent.height); 79 | } 80 | 81 | @override 82 | bool shouldRepaint(covariant IconStackFlowDelegate oldDelegate) { 83 | return maxExtent != oldDelegate.maxExtent || 84 | itemExtent != oldDelegate.itemExtent || 85 | overlaps != oldDelegate.overlaps || 86 | axisDirection != oldDelegate.axisDirection; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /lib/widget/image/image.dart: -------------------------------------------------------------------------------- 1 | library image; 2 | 3 | export 'src/image.dart'; 4 | export 'src/image_holder.dart'; 5 | export 'src/big_image_widget.dart'; 6 | export 'src/big_image_list_widget.dart'; 7 | -------------------------------------------------------------------------------- /lib/widget/image/src/big_image_widget.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | import 'package:flutter_app_template/common/utils/utils.dart'; 5 | import 'package:flutter_cache_manager/flutter_cache_manager.dart'; 6 | import 'package:cached_network_image/cached_network_image.dart'; 7 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 8 | import 'package:get/get.dart'; 9 | import 'package:photo_view/photo_view.dart'; 10 | import '/widget/loading_widget.dart'; 11 | 12 | void showBigImageDialog(BuildContext context, {String imageUrl = ''}) { 13 | Get.to( 14 | AnnotatedRegion( 15 | value: const SystemUiOverlayStyle( 16 | statusBarColor: Colors.transparent, 17 | statusBarBrightness: Brightness.light, 18 | statusBarIconBrightness: Brightness.light, 19 | ), 20 | child: BigImageWidget(imageUrl: imageUrl), 21 | ), 22 | ); 23 | } 24 | 25 | ///大图预览 26 | class BigImageWidget extends StatelessWidget { 27 | final String imageUrl; //图片地址 28 | BigImageWidget({ 29 | Key? key, 30 | required this.imageUrl, 31 | }) : super(key: key) { 32 | _isLocalFile = PhotoCameraKit.isLocalFilePath(imageUrl); 33 | _localImageProvider = FileImage(File(imageUrl)); 34 | _networkImageProvider = CachedNetworkImageProvider( 35 | imageUrl, 36 | cacheKey: imageUrl, 37 | cacheManager: DefaultCacheManager(), 38 | ); 39 | } 40 | late bool _isLocalFile; 41 | late ImageProvider _localImageProvider; 42 | late ImageProvider _networkImageProvider; 43 | @override 44 | Widget build(BuildContext context) { 45 | return Container( 46 | color: Colors.black, 47 | child: Stack( 48 | children: [ 49 | PhotoView( 50 | imageProvider: 51 | _isLocalFile ? _localImageProvider : _networkImageProvider, 52 | errorBuilder: (context, error, stackTrace) { 53 | return const Center( 54 | child: Icon( 55 | Icons.error, 56 | color: Colors.white, 57 | size: 42, 58 | ), 59 | ); 60 | }, 61 | loadingBuilder: (context, progress) { 62 | return const Center( 63 | child: LoadingWidget( 64 | stop: false, 65 | ), 66 | ); 67 | }, 68 | ), 69 | Positioned( 70 | top: 38.h, 71 | left: 10.w, 72 | child: _buildLeadingWidget(), 73 | ), 74 | ], 75 | ), 76 | ); 77 | } 78 | 79 | Widget _buildLeadingWidget() { 80 | return Container( 81 | alignment: Alignment.center, 82 | height: 44.h, 83 | width: 44.w, 84 | child: TextButton( 85 | child: Icon( 86 | Icons.arrow_back_ios_rounded, 87 | size: 24.w, 88 | color: Colors.white, 89 | ), 90 | onPressed: () { 91 | Get.back(); 92 | }, 93 | ), 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /lib/widget/image/src/image.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_cache_manager/flutter_cache_manager.dart'; 4 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 5 | import 'package:quiver/strings.dart'; 6 | 7 | ///图片加载工具类 8 | Widget loadImage( 9 | String url, { 10 | double? width = 64, 11 | double? height = 64, 12 | BoxFit fit = BoxFit.cover, 13 | Color? imageColor, 14 | BlendMode? colorBlendMode, 15 | Alignment? alignment, 16 | String placeholder = 'icon_place_logo', 17 | }) { 18 | Widget placeHolder = getDefaultPlaceHolderWidget( 19 | width: width ?? 64, 20 | height: height ?? 64, 21 | placeholder: placeholder, 22 | ); 23 | if (isBlank(url)) { 24 | return placeHolder; 25 | } 26 | return CachedNetworkImage( 27 | imageUrl: url, 28 | width: width, 29 | height: height, 30 | fit: fit, 31 | color: imageColor, 32 | colorBlendMode: colorBlendMode, 33 | cacheKey: url, 34 | alignment: alignment ?? Alignment.center, 35 | cacheManager: DefaultCacheManager(), 36 | placeholder: (context, url) { 37 | return placeHolder; 38 | }, 39 | errorWidget: (context, url, error) { 40 | return placeHolder; 41 | }, 42 | ); 43 | } 44 | 45 | Widget getDefaultPlaceHolderWidget({ 46 | double width = 64, 47 | double height = 64, 48 | String placeholder = 'icon_place_logo', 49 | }) { 50 | return Container( 51 | width: width, 52 | height: height, 53 | color: const Color(0xFFE0E2E6), 54 | alignment: Alignment.center, 55 | child: Image.asset( 56 | 'assets/images/$placeholder.png', 57 | ), 58 | ); 59 | } 60 | 61 | Widget getIconByPackageName( 62 | url, { 63 | double width = 64, 64 | double height = 64, 65 | String suffix = 'png', 66 | String? packageName, 67 | }) { 68 | return Image.asset( 69 | 'assets/images/$url.$suffix', 70 | width: width.w, 71 | height: height.h, 72 | fit: BoxFit.cover, 73 | package: packageName, 74 | ); 75 | } 76 | 77 | CachedNetworkImageProvider getCacheNetworkImageProvider(String imageUrl) => 78 | CachedNetworkImageProvider( 79 | imageUrl, 80 | cacheKey: imageUrl, 81 | cacheManager: DefaultCacheManager(), 82 | ); 83 | 84 | Widget getIconPng( 85 | String url, { 86 | double iconSize = 64.0, 87 | }) { 88 | return getIcon(url, "png", iconSize: iconSize.w); 89 | } 90 | 91 | Widget getIconJpg(String url, {double iconSize = 64.0}) { 92 | return getIcon(url, "jpg", iconSize: iconSize.w); 93 | } 94 | 95 | Widget getIcon(String url, String suffix, {double iconSize = 64.0}) { 96 | return Image.asset( 97 | 'assets/images/$url.$suffix', 98 | width: iconSize, 99 | height: iconSize, 100 | // fit: BoxFit.cover, 101 | ); 102 | } 103 | 104 | Widget getIconPngWithSize(String url, 105 | {double? width = 64, double? height = 64, BoxFit? fit}) { 106 | return getIconWithSize(url, 'png', width: width, height: height, fit: fit); 107 | } 108 | 109 | Widget getIconWithSize(String url, String suffix, 110 | {double? width = 64, double? height = 64, BoxFit? fit}) { 111 | return Image.asset( 112 | 'assets/images/$url.$suffix', 113 | width: width, 114 | height: height, 115 | fit: fit, 116 | // fit: BoxFit.cover, 117 | ); 118 | } 119 | 120 | AssetImage getAssetImage( 121 | String url, { 122 | String suffix = 'png', 123 | }) { 124 | return AssetImage('assets/images/$url.$suffix'); 125 | } 126 | -------------------------------------------------------------------------------- /lib/widget/image/src/image_holder.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flutter/material.dart'; 3 | 4 | /// 外部持有;保证图片一直存活 5 | class ImageHolder { 6 | ImageHolder({required this.image}); 7 | ImageHolder.asset(String name) : image = AssetImage(name); 8 | 9 | final ImageProvider image; 10 | 11 | ImageStream? _imageStream; 12 | ImageStreamListener? _imageStreamListener; 13 | 14 | Completer? _completer; 15 | bool _completed = false; 16 | Future? get future { 17 | if (_completed) return null; 18 | _completer ??= Completer(); 19 | return _completer!.future; 20 | } 21 | 22 | void _complete() { 23 | if (_completer != null) { 24 | _completer!.complete(); 25 | _completer = null; 26 | } 27 | _completed = true; 28 | } 29 | 30 | void init(BuildContext context) { 31 | if (_imageStreamListener != null) return; 32 | _imageStreamListener ??= ImageStreamListener(_listen, onError: _onError); 33 | 34 | _imageStream = image.resolve(createLocalImageConfiguration(context)); 35 | _imageStream!.addListener(_imageStreamListener!); 36 | } 37 | 38 | ImageInfo? _info; 39 | void _listen(ImageInfo? info, bool sync) { 40 | _info?.dispose(); 41 | _info = info; 42 | _complete(); 43 | } 44 | 45 | void _onError(e, s) { 46 | debugPrint('error: $e, $s'); 47 | } 48 | 49 | void dispose() { 50 | _info?.dispose(); 51 | if (_imageStreamListener != null) { 52 | _imageStream?.removeListener(_imageStreamListener!); 53 | } 54 | _info = null; 55 | _imageStream = null; 56 | _imageStreamListener = null; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/widget/label.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 4 | 5 | ///文本带标签(水平方向) 6 | class Label extends StatelessWidget { 7 | final String? label; 8 | final double? iconSize; 9 | final String? img; 10 | final Axis direction; 11 | final Color textColor; 12 | final double fontSize; 13 | final double space; //标签和文本的间距 14 | 15 | const Label( 16 | this.label, 17 | this.img, { 18 | Key? key, 19 | this.direction = Axis.horizontal, 20 | this.iconSize, 21 | this.textColor = Colors.white, 22 | this.fontSize = 11, 23 | this.space = 0, 24 | }) : super(key: key); 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return Text.rich( 29 | TextSpan( 30 | children: [ 31 | WidgetSpan( 32 | child: SizedBox( 33 | width: iconSize ?? 15.w, 34 | height: iconSize ?? 15.w, 35 | child: Image.asset( 36 | "assets/images/$img.png", 37 | ), 38 | ), 39 | ), 40 | WidgetSpan( 41 | child: SizedBox( 42 | width: direction == Axis.horizontal ? space : 0, 43 | height: direction == Axis.horizontal ? 0 : space, 44 | ), 45 | ), 46 | TextSpan( 47 | text: direction == Axis.horizontal ? label : '\n$label', 48 | style: TextStyle(color: textColor, fontSize: fontSize), 49 | ) 50 | ], 51 | ), 52 | textAlign: TextAlign.center, 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/widget/layout/layout.dart: -------------------------------------------------------------------------------- 1 | library layout; 2 | 3 | export 'src/common/common_layout.dart'; 4 | export 'src/common/app_bar.dart'; 5 | export 'src/tab/bottom_tab_bar_view.dart'; 6 | export 'src/un_focus_wrapper.dart'; -------------------------------------------------------------------------------- /lib/widget/layout/src/common/app_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 5 | import '/common/values/values.dart'; 6 | 7 | PreferredSize buildAppBar( 8 | BuildContext context, 9 | String text, { 10 | double fontSize = 18.0, 11 | double elevation = 0, 12 | Widget? subTitle, 13 | Widget? leading, 14 | PreferredSizeWidget? bottom, 15 | Widget? titleIcon, 16 | Color backgroundColor = Colors.white, 17 | Color titleColor = const Color(0xFF373737), 18 | bool centerTitle = true, 19 | bool canBack = true, 20 | double? toolbarHeight, 21 | List? actions, 22 | VoidCallback? onBackClick, 23 | ShapeBorder? shape, 24 | double preferredHeight = 60, 25 | SystemUiOverlayStyle? systemOverlayStyle, 26 | }) { 27 | return PreferredSize( 28 | child: AppBar( 29 | elevation: elevation, 30 | // 阴影 31 | centerTitle: centerTitle, 32 | toolbarHeight: toolbarHeight, 33 | titleSpacing: 0, 34 | shape: shape, 35 | automaticallyImplyLeading: false, 36 | systemOverlayStyle: systemOverlayStyle, 37 | bottom: bottom, 38 | title: Row( 39 | mainAxisSize: MainAxisSize.min, 40 | mainAxisAlignment: MainAxisAlignment.center, 41 | children: [ 42 | titleIcon ?? const SizedBox(), 43 | Flexible( 44 | child: Padding( 45 | padding: titleIcon == null 46 | ? EdgeInsets.zero 47 | : EdgeInsets.only(left: 16.w), 48 | child: Column( 49 | mainAxisSize: MainAxisSize.min, 50 | children: [ 51 | Text( 52 | text, 53 | style: TextStyle( 54 | fontSize: fontSize, 55 | color: titleColor, 56 | fontWeight: boldFont, 57 | ), 58 | ), 59 | subTitle ?? const SizedBox(), 60 | ], 61 | ), 62 | ), 63 | ), 64 | ], 65 | ), 66 | leading: canBack 67 | ? leading ?? 68 | DefaultAppBarLeadingWidget( 69 | leadingIconColor: titleColor, 70 | onBackClick: onBackClick, 71 | ) 72 | : null, 73 | actions: actions, 74 | backgroundColor: backgroundColor, 75 | ), 76 | preferredSize: Size.fromHeight(preferredHeight.h), 77 | ); 78 | } 79 | 80 | class DefaultAppBarLeadingWidget extends StatelessWidget { 81 | final Color? leadingIconColor; 82 | final VoidCallback? onBackClick; 83 | 84 | const DefaultAppBarLeadingWidget({ 85 | Key? key, 86 | this.leadingIconColor, 87 | this.onBackClick, 88 | }) : super(key: key); 89 | 90 | @override 91 | Widget build(BuildContext context) { 92 | return Container( 93 | alignment: Alignment.center, 94 | height: 44.h, 95 | width: 44.w, 96 | child: TextButton( 97 | child: Icon( 98 | Icons.arrow_back_ios_rounded, 99 | color: leadingIconColor ?? const Color(0xFF373737), 100 | size: 20.w, 101 | ), 102 | onPressed: () { 103 | onBackClick != null ? onBackClick!() : Get.back(); 104 | }, 105 | ), 106 | ); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /lib/widget/layout/src/tab/round_underline_tab_indicator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | ///自定义的tabView 4 | class RoundUnderlineTabIndicator extends Decoration { 5 | /// Create an underline style selected tab indicator. 6 | /// 7 | /// The [borderSide] and [insets] arguments must not be null. 8 | const RoundUnderlineTabIndicator({ 9 | this.borderSide = const BorderSide(width: 2.0, color: Colors.white), 10 | this.indicatorPaddingTop = 9, 11 | this.indicatorWidth = 0, 12 | this.insets = EdgeInsets.zero, 13 | }); 14 | 15 | /// The color and weight of the horizontal line drawn below the selected tab. 16 | final BorderSide borderSide; 17 | 18 | final double indicatorPaddingTop; 19 | 20 | /// Locates the selected tab's underline relative to the tab's boundary. 21 | /// 22 | /// The [TabBar.indicatorSize] property can be used to define the 23 | /// tab indicator's bounds in terms of its (centered) tab widget with 24 | /// [TabIndicatorSize.label], or the entire tab with [TabIndicatorSize.tab]. 25 | final EdgeInsetsGeometry insets; 26 | 27 | final double indicatorWidth; 28 | 29 | @override 30 | Decoration? lerpFrom(Decoration? a, double t) { 31 | if (a is UnderlineTabIndicator) { 32 | return UnderlineTabIndicator( 33 | borderSide: BorderSide.lerp(a.borderSide, borderSide, t), 34 | insets: EdgeInsetsGeometry.lerp(a.insets, insets, t) ?? EdgeInsets.zero, 35 | ); 36 | } 37 | return super.lerpFrom(a, t); 38 | } 39 | 40 | @override 41 | Decoration? lerpTo(Decoration? b, double t) { 42 | if (b is UnderlineTabIndicator) { 43 | return UnderlineTabIndicator( 44 | borderSide: BorderSide.lerp(borderSide, b.borderSide, t), 45 | insets: EdgeInsetsGeometry.lerp(b.insets, insets, t) ?? EdgeInsets.zero, 46 | ); 47 | } 48 | return super.lerpTo(b, t); 49 | } 50 | 51 | @override 52 | _UnderlinePainter createBoxPainter([ VoidCallback? onChanged ]) { 53 | return _UnderlinePainter(this,onChanged); 54 | } 55 | } 56 | 57 | class _UnderlinePainter extends BoxPainter { 58 | _UnderlinePainter(this.decoration,VoidCallback? onChanged) 59 | : super(onChanged); 60 | 61 | final RoundUnderlineTabIndicator decoration; 62 | 63 | BorderSide get borderSide => decoration.borderSide; 64 | EdgeInsetsGeometry get insets => decoration.insets; 65 | double get indicatorPaddingTop => decoration.indicatorPaddingTop; 66 | 67 | Rect _indicatorRectFor(Rect rect, TextDirection textDirection) { 68 | final indicator = insets.resolve(textDirection).deflateRect(rect); 69 | //希望的宽度 70 | double wantWidth = decoration.indicatorWidth == 0 71 | ? ((indicator.right - indicator.left) * 2 / 3) 72 | : decoration.indicatorWidth; 73 | //取中间坐标 74 | double cw = (indicator.left + indicator.right) / 2; 75 | return Rect.fromLTWH( 76 | cw - wantWidth / 2, 77 | indicator.bottom - indicatorPaddingTop * 2, 78 | wantWidth, 79 | borderSide.width); 80 | } 81 | 82 | @override 83 | void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) { 84 | assert(configuration.size != null); 85 | final rect = offset & configuration.size!; 86 | final textDirection = configuration.textDirection!; 87 | final indicator = _indicatorRectFor(rect, textDirection).deflate(borderSide.width / 2.0); 88 | // final Paint paint = borderSide.toPaint()..strokeCap = StrokeCap.square; 89 | // 改为圆角 90 | final paint = borderSide.toPaint()..strokeCap = StrokeCap.round; 91 | canvas.drawLine(indicator.bottomLeft, indicator.bottomRight, paint); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /lib/widget/layout/src/un_focus_wrapper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | ///焦点获取页面 4 | class UnFocusWrapper extends StatelessWidget { 5 | final Widget child; 6 | const UnFocusWrapper(this.child, {Key? key}) : super(key: key); 7 | @override 8 | Widget build(BuildContext context) { 9 | return GestureDetector( 10 | onPanDown: (e) { 11 | final scope = FocusScope.of(context); 12 | if (scope.hasFocus) { 13 | scope.unfocus(); 14 | } 15 | }, 16 | behavior: HitTestBehavior.translucent, 17 | child: child, 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/widget/loading_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | import 'package:get/get.dart'; 4 | 5 | import '../common/values/values.dart'; 6 | 7 | void showLoadingDialog({ 8 | bool barrierDismissible = false, 9 | Color barrierColor = Colors.transparent, 10 | }) { 11 | Get.dialog( 12 | const LoadingDialog(), 13 | barrierDismissible: barrierDismissible, 14 | barrierColor: barrierColor, 15 | ); 16 | } 17 | 18 | class LoadingDialog extends StatefulWidget { 19 | const LoadingDialog({Key? key}) : super(key: key); 20 | 21 | @override 22 | createState() => LoadingDialogState(); 23 | } 24 | 25 | class LoadingDialogState extends State { 26 | @override 27 | Widget build(BuildContext context) { 28 | return Align( 29 | alignment: Alignment.center, 30 | child: Container( 31 | width: 100.w, 32 | height: 100.w, 33 | alignment: Alignment.center, 34 | decoration: BoxDecoration( 35 | color: const Color(0xFF2F3B46), 36 | borderRadius: borderRadius8, 37 | ), 38 | child: Image.asset( 39 | 'assets/images/ic_loading_dialog.gif', 40 | width: 32.w, 41 | height: 32.w, 42 | ), 43 | ), 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/widget/loading_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | 4 | import 'circle_indicator.dart'; 5 | 6 | class LoadingWidget extends StatelessWidget { 7 | const LoadingWidget({ 8 | Key? key, 9 | required this.stop, 10 | this.progress = 0, 11 | }) : super(key: key); 12 | final bool stop; 13 | final double progress; 14 | @override 15 | Widget build(BuildContext context) { 16 | return RepaintBoundary( 17 | child: SizedBox( 18 | width: ScreenUtil().screenWidth, 19 | child: CircleIndicator( 20 | stop: stop, 21 | radius: 18, 22 | childRadius: 5, 23 | progress: progress, 24 | duration: const Duration(seconds: 1), 25 | colors: [ 26 | Colors.grey.shade100, 27 | Colors.grey.shade200, 28 | Colors.grey.shade300, 29 | Colors.grey.shade400, 30 | Colors.grey.shade500, 31 | Colors.grey.shade600, 32 | Colors.grey.shade700, 33 | Colors.grey.shade800, 34 | Colors.grey.shade900, 35 | ], 36 | ), 37 | ), 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/widget/lock_mixin.dart: -------------------------------------------------------------------------------- 1 | /// 锁定一个异步任务,避免多次调用引起的数据问题 2 | mixin LockMixin { 3 | Object? _lock; 4 | 5 | Object genNewLock() { 6 | return _lock = Object(); 7 | } 8 | 9 | bool isCurrentLock(Object key) => _lock == key; 10 | 11 | bool isNotCurrent(Object key) => _lock != key; 12 | 13 | void resetLock() { 14 | _lock = null; 15 | } 16 | } 17 | 18 | /// 以对象存在 19 | class Lock with LockMixin {} 20 | -------------------------------------------------------------------------------- /lib/widget/preference.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | 4 | import 'image/image.dart'; 5 | 6 | class Preference extends StatelessWidget { 7 | final double verticalPadding; 8 | final String title; 9 | final String? subTitle; 10 | final Color subTitleColor; 11 | final double subFontSize; 12 | final bool showArrow; 13 | final bool required; 14 | final Widget? trailing; 15 | final void Function()? onTap; 16 | final bool trailingExpaned; 17 | const Preference( 18 | this.title, { 19 | Key? key, 20 | this.subTitle, 21 | this.subTitleColor = const Color(0XFF828488), 22 | this.subFontSize = 12, 23 | this.trailing, 24 | this.showArrow = true, 25 | this.required = false, 26 | this.onTap, 27 | this.trailingExpaned = false, 28 | this.verticalPadding = 16, 29 | }) : super(key: key); 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return InkWell( 34 | onTap: onTap, 35 | child: Container( 36 | color: Colors.white, 37 | padding: 38 | EdgeInsets.symmetric(horizontal: 12.w, vertical: verticalPadding.h), 39 | child: Center( 40 | child: Row( 41 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 42 | children: [ 43 | Text( 44 | title, 45 | style: TextStyle( 46 | color: const Color(0XFF40454F), 47 | fontSize: 13.sp, 48 | ), 49 | ), 50 | if (required) 51 | Text( 52 | '*', 53 | style: TextStyle( 54 | color: const Color(0XFFF14D2F), 55 | fontSize: 13.sp, 56 | ), 57 | ), 58 | // if (!trailingExpaned) const Spacer(), 59 | if (subTitle != null) 60 | Expanded( 61 | child: Container( 62 | alignment: Alignment.centerRight, 63 | padding: EdgeInsets.only(left: 4.w), 64 | child: Text( 65 | subTitle ?? '', 66 | style: TextStyle( 67 | color: subTitleColor, 68 | fontSize: subFontSize.sp, 69 | ), 70 | ))), 71 | SizedBox(width: 7.w), 72 | if (trailing != null) _buildTrailing(), 73 | if (showArrow) getIconPng('ic_arrow', iconSize: 18.w) 74 | ], 75 | ), 76 | ), 77 | ), 78 | ); 79 | } 80 | 81 | Widget _buildTrailing() { 82 | Widget child = Container( 83 | margin: EdgeInsets.only(right: 7.w), 84 | alignment: Alignment.centerRight, 85 | child: trailing, 86 | ); 87 | if (trailingExpaned) { 88 | child = Expanded(child: child); 89 | } 90 | return child; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /lib/widget/rating_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_rating_bar/flutter_rating_bar.dart'; 3 | 4 | class RatingBarWidget extends StatefulWidget { 5 | final double itemPadding; 6 | final double itemSize; 7 | final Color itemColor; 8 | final bool showRatingText; 9 | final double initialRating; 10 | final ValueChanged? onRatingUpdate; 11 | 12 | const RatingBarWidget({ 13 | Key? key, 14 | this.itemPadding = 5, 15 | this.itemSize = 22, 16 | this.itemColor = Colors.amber, 17 | this.showRatingText = true, 18 | this.onRatingUpdate, 19 | this.initialRating = 5, 20 | }) : super(key: key); 21 | 22 | @override 23 | State createState() => _RatingBarWidgetState(); 24 | } 25 | 26 | class _RatingBarWidgetState extends State { 27 | @override 28 | Widget build(BuildContext context) { 29 | return Row( 30 | children: [ 31 | RatingBar.builder( 32 | initialRating: widget.initialRating, 33 | minRating: 1, 34 | direction: Axis.horizontal, 35 | allowHalfRating: true, 36 | unratedColor: Colors.amber.withAlpha(50), 37 | itemCount: 5, 38 | itemSize: widget.itemSize, 39 | itemPadding: EdgeInsets.symmetric(horizontal: widget.itemPadding), 40 | itemBuilder: (context, _) => Icon( 41 | Icons.star, 42 | color: widget.itemColor, 43 | ), 44 | updateOnDrag: false, 45 | onRatingUpdate: (double value) { 46 | if (widget.onRatingUpdate != null) { 47 | widget.onRatingUpdate!(value); 48 | } 49 | }, 50 | ), 51 | ], 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/widget/second_tap_exit_app.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:get/get.dart'; 4 | import 'toast.dart'; 5 | 6 | /// 再按一次退出程序 7 | class SecondTapExitAppWidget extends StatefulWidget { 8 | final Widget child; 9 | 10 | const SecondTapExitAppWidget({ 11 | Key? key, 12 | required this.child, 13 | }) : super(key: key); 14 | 15 | @override 16 | _SecondTapExitAppWidgetState createState() => _SecondTapExitAppWidgetState(); 17 | } 18 | 19 | class _SecondTapExitAppWidgetState extends State { 20 | DateTime? _lastPopTime; //上次点击时间 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | if (!Platform.isAndroid) { 25 | return widget.child; 26 | } 27 | return PopScope( 28 | canPop: false, 29 | onPopInvokedWithResult: (bool didPop, Object? result) async { 30 | if (_lastPopTime == null || 31 | DateTime.now().difference(_lastPopTime!) > 32 | const Duration(seconds: 2)) { 33 | //两次点击间隔超过2秒则重新计时 34 | _lastPopTime = DateTime.now(); 35 | toastInfo(msg: "exit_app".tr); 36 | return; 37 | } 38 | Get.back(); 39 | }, 40 | child: widget.child, 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/widget/shake_button/shake_animation_controller.dart: -------------------------------------------------------------------------------- 1 | 2 | ///抖动监听 3 | typedef ShakeAnimationListener = void Function(bool isOpen, int shakeCount); 4 | 5 | ///抖动动画控制器 6 | class ShakeAnimationController { 7 | ///当前抖动动画的状态 8 | bool animationRunging = false; 9 | 10 | ///监听 11 | ShakeAnimationListener? _shakeAnimationListener; 12 | 13 | ///控制器中添加监听 14 | setShakeListener(ShakeAnimationListener listener) { 15 | _shakeAnimationListener = listener; 16 | } 17 | 18 | ///打开 19 | void start({int shakeCount = 1}) { 20 | if (_shakeAnimationListener != null) { 21 | animationRunging = true; 22 | _shakeAnimationListener!(true, shakeCount); 23 | } 24 | } 25 | 26 | ///关闭 27 | void stop() { 28 | if (_shakeAnimationListener != null) { 29 | animationRunging = false; 30 | _shakeAnimationListener!(false, 0); 31 | } 32 | } 33 | 34 | ///移除监听 35 | void removeListener() { 36 | _shakeAnimationListener = null; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/widget/shake_button/shake_animation_type.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | ///抖动类型 4 | ///[ShakeAnimationType.leftRightShake]左右抖动 5 | ///[ShakeAnimationType.topBottomShake]上下抖动 6 | ///[ShakeAnimationType.skewShake]斜角抖动 7 | ///[ShakeAnimationType.rotateShake]旋转抖动 8 | ///[ShakeAnimationType.randomShake]随机抖动 9 | enum ShakeAnimationType { 10 | leftRightShake, 11 | topBottomShake, 12 | skewShake, 13 | rotateShake, 14 | randomShake, 15 | } 16 | -------------------------------------------------------------------------------- /lib/widget/switch_field.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | class SwitchFieldWidget extends StatefulWidget { 4 | const SwitchFieldWidget( 5 | this.selected, { 6 | Key? key, 7 | this.onChanged, 8 | }) : super(key: key); 9 | 10 | final ValueChanged? onChanged; 11 | final bool selected; 12 | 13 | @override 14 | _SwitchFieldWidgetState createState() => _SwitchFieldWidgetState(); 15 | } 16 | 17 | class _SwitchFieldWidgetState extends State { 18 | bool? _selected; 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | _selected = widget.selected; 24 | } 25 | 26 | @override 27 | void didUpdateWidget(covariant SwitchFieldWidget oldWidget) { 28 | super.didUpdateWidget(oldWidget); 29 | if (widget.selected != oldWidget.selected) { 30 | _selected = widget.selected; 31 | } 32 | } 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | return Transform.scale( 37 | scale: 0.8, 38 | child: CupertinoSwitch( 39 | activeColor: const Color(0xFF64C365), 40 | value: _selected ?? false, 41 | onChanged: (selected) { 42 | setState(() { 43 | _selected = selected; 44 | }); 45 | widget.onChanged?.call(selected); 46 | }, 47 | )); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/widget/toast.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_easyloading/flutter_easyloading.dart'; 2 | 3 | Future toastInfo({ 4 | required String msg, 5 | Duration? duration, 6 | EasyLoadingToastPosition? toastPosition, 7 | EasyLoadingMaskType? maskType, 8 | bool? dismissOnTap, 9 | }) async { 10 | EasyLoading.showToast( 11 | msg, 12 | duration: duration, 13 | toastPosition: toastPosition, 14 | maskType: maskType, 15 | dismissOnTap: dismissOnTap, 16 | ); 17 | } 18 | 19 | bool get isShow => EasyLoading.isShow; 20 | 21 | void dismiss({animation = true}) { 22 | EasyLoading.dismiss(animation: animation); 23 | } 24 | 25 | void show({String status = '加载中...'}) { 26 | EasyLoading.show(status: status); 27 | } 28 | -------------------------------------------------------------------------------- /lib/widget/verification_box/verification_box_cursor.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// 4 | /// des: 模拟光标 5 | /// 6 | class VerificationBoxCursor extends StatefulWidget { 7 | const VerificationBoxCursor({ 8 | Key? key, 9 | this.color, 10 | this.width, 11 | this.indent, 12 | this.endIndent, 13 | }) : super(key: key); 14 | 15 | /// 16 | /// 光标颜色 17 | /// 18 | final Color? color; 19 | 20 | /// 21 | /// 光标宽度 22 | /// 23 | final double? width; 24 | 25 | /// 26 | /// 光标距离顶部距离 27 | /// 28 | final double? indent; 29 | 30 | /// 31 | /// 光标距离底部距离 32 | /// 33 | final double? endIndent; 34 | 35 | @override 36 | State createState() => _VerificationBoxCursorState(); 37 | } 38 | 39 | class _VerificationBoxCursorState extends State 40 | with SingleTickerProviderStateMixin { 41 | late AnimationController _controller; 42 | 43 | @override 44 | void initState() { 45 | _controller = AnimationController( 46 | duration: const Duration(milliseconds: 500), vsync: this) 47 | ..addStatusListener((status) { 48 | if (status == AnimationStatus.completed) { 49 | _controller.reverse(); 50 | } else if (status == AnimationStatus.dismissed) { 51 | _controller.forward(); 52 | } 53 | }); 54 | _controller.forward(); 55 | 56 | super.initState(); 57 | } 58 | 59 | @override 60 | Widget build(BuildContext context) { 61 | return FadeTransition( 62 | opacity: _controller, 63 | child: VerticalDivider( 64 | thickness: widget.width, 65 | color: widget.color, 66 | indent: widget.indent, 67 | endIndent: widget.endIndent, 68 | ), 69 | ); 70 | } 71 | 72 | @override 73 | void dispose() { 74 | _controller.dispose(); 75 | super.dispose(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/widget/verification_box/verification_box_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'verification_box_cursor.dart'; 3 | 4 | /// 5 | /// 输入框样式 6 | /// 7 | enum VerificationBoxItemType { 8 | /// 9 | ///下划线 10 | /// 11 | underline, 12 | 13 | /// 14 | /// 盒子 15 | /// 16 | box, 17 | } 18 | 19 | /// 20 | /// 单个输入框 21 | /// 22 | class VerificationBoxItem extends StatelessWidget { 23 | const VerificationBoxItem( 24 | {Key? key, 25 | this.data = '', 26 | this.textStyle, 27 | this.type = VerificationBoxItemType.box, 28 | this.decoration, 29 | this.borderRadius = 5.0, 30 | this.borderWidth = 2.0, 31 | this.borderColor, 32 | this.showCursor = false, 33 | this.cursorColor, 34 | this.cursorWidth = 2, 35 | this.cursorIndent = 5, 36 | this.cursorEndIndent = 5}) 37 | : super(key: key); 38 | 39 | final String data; 40 | final VerificationBoxItemType type; 41 | final double borderWidth; 42 | final Color? borderColor; 43 | final double borderRadius; 44 | final TextStyle? textStyle; 45 | final Decoration? decoration; 46 | 47 | /// 48 | /// 是否显示光标 49 | /// 50 | final bool showCursor; 51 | 52 | /// 53 | /// 光标颜色 54 | /// 55 | final Color? cursorColor; 56 | 57 | /// 58 | /// 光标宽度 59 | /// 60 | final double cursorWidth; 61 | 62 | /// 63 | /// 光标距离顶部距离 64 | /// 65 | final double cursorIndent; 66 | 67 | /// 68 | /// 光标距离底部距离 69 | /// 70 | final double cursorEndIndent; 71 | 72 | @override 73 | Widget build(BuildContext context) { 74 | var borderColor = this.borderColor ?? Theme.of(context).dividerColor; 75 | var text = _buildText(); 76 | late Widget widget; 77 | if (type == VerificationBoxItemType.box) { 78 | widget = _buildBoxDecoration(text, borderColor); 79 | } else { 80 | widget = _buildUnderlineDecoration(text, borderColor); 81 | } 82 | 83 | return Stack( 84 | children: [ 85 | widget, 86 | showCursor 87 | ? Positioned.fill( 88 | child: VerificationBoxCursor( 89 | color: cursorColor, 90 | width: cursorWidth, 91 | indent: cursorIndent, 92 | endIndent: cursorEndIndent, 93 | ), 94 | ) 95 | : Container() 96 | ], 97 | ); 98 | } 99 | 100 | /// 101 | /// 绘制盒子类型 102 | /// 103 | _buildBoxDecoration(Widget child, Color borderColor) { 104 | return Container( 105 | alignment: Alignment.center, 106 | decoration: decoration ?? 107 | BoxDecoration( 108 | borderRadius: BorderRadius.circular(borderRadius), 109 | border: Border.all(color: borderColor, width: borderWidth)), 110 | child: child, 111 | ); 112 | } 113 | 114 | /// 115 | /// 绘制下划线类型 116 | /// 117 | _buildUnderlineDecoration(Widget child, Color borderColor) { 118 | return Container( 119 | alignment: Alignment.center, 120 | decoration: UnderlineTabIndicator( 121 | borderSide: BorderSide(width: borderWidth, color: borderColor)), 122 | child: child, 123 | ); 124 | } 125 | 126 | /// 127 | /// 文本 128 | /// 129 | _buildText() { 130 | return Text( 131 | data, 132 | style: textStyle, 133 | ); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /lib/widget/waterfall_flow_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:waterfall_flow/waterfall_flow.dart'; 4 | import 'widget.dart'; 5 | import '../app/base/model/base_page_list_resp.dart'; 6 | 7 | ///瀑布流布局列表(包含分页) 8 | class WaterFallFlowWidget extends StatefulWidget { 9 | final ScrollController? controller; 10 | final List items; 11 | final int? itemCount; 12 | final Function itemBuilder; 13 | final String emptyIcon; 14 | final String emptyText; 15 | final String emptyHint; 16 | final int crossAxisCount; 17 | final double crossAxisSpacing; 18 | final double mainAxisSpacing; 19 | final EdgeInsetsGeometry padding; 20 | final ScrollPhysics? physics; 21 | final bool shrinkWrap; 22 | final String noMoreText; 23 | final Function? onRefresh; 24 | final Function? onLoadMore; 25 | 26 | WaterFallFlowWidget({ 27 | Key? key, 28 | required this.items, 29 | required this.itemBuilder, 30 | required this.emptyIcon, 31 | required this.emptyText, 32 | this.padding = EdgeInsets.zero, 33 | this.shrinkWrap = false, 34 | this.crossAxisCount = 2, 35 | this.crossAxisSpacing = 7.0, 36 | this.mainAxisSpacing = 7.0, 37 | this.emptyHint = '', 38 | String? noMoreText, 39 | this.controller, 40 | this.itemCount, 41 | this.physics, 42 | this.onRefresh, 43 | this.onLoadMore, 44 | }) : noMoreText = noMoreText ?? 'wall_no_more_default'.tr, 45 | super(key: key); 46 | 47 | @override 48 | _WaterFallFlowWidgetState createState() => _WaterFallFlowWidgetState(); 49 | } 50 | 51 | class _WaterFallFlowWidgetState extends State { 52 | late ScrollController _controller; 53 | 54 | @override 55 | void initState() { 56 | super.initState(); 57 | _controller = widget.controller ?? ScrollController(); 58 | _controller.addListener(() { 59 | if (_controller.position.pixels == _controller.position.maxScrollExtent) { 60 | if (widget.onLoadMore == null) return; 61 | widget.onLoadMore!(); 62 | } 63 | }); 64 | } 65 | 66 | @override 67 | void dispose() { 68 | _controller.dispose(); 69 | super.dispose(); 70 | } 71 | 72 | @override 73 | Widget build(BuildContext context) { 74 | if ((widget.itemCount ?? widget.items.length) == 0) { 75 | return Center( 76 | child: EmptyResultWidget( 77 | icon: widget.emptyIcon, 78 | text: widget.emptyText, 79 | ), 80 | ); 81 | } 82 | return RefreshIndicator( 83 | onRefresh: () async { 84 | if (widget.onRefresh == null) return Future.value(); 85 | widget.onRefresh!(); 86 | }, 87 | child: WaterfallFlow.builder( 88 | padding: widget.padding, 89 | physics: widget.physics, 90 | shrinkWrap: widget.shrinkWrap, 91 | gridDelegate: SliverWaterfallFlowDelegateWithFixedCrossAxisCount( 92 | crossAxisCount: widget.crossAxisCount, 93 | crossAxisSpacing: widget.crossAxisSpacing, 94 | mainAxisSpacing: widget.mainAxisSpacing, 95 | ), 96 | itemCount: widget.items.length, 97 | itemBuilder: (BuildContext context, int index) { 98 | return widget.itemBuilder(index); 99 | }, 100 | ), 101 | ); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /lib/widget/widget.dart: -------------------------------------------------------------------------------- 1 | library widgets; 2 | 3 | export 'async_image_banner_widget.dart'; 4 | export 'auto_scroll_list_view.dart'; 5 | export 'auto_scroll_to_item_widget.dart'; 6 | export 'badge.dart'; 7 | export 'badge_widget.dart'; 8 | export 'bottom_height.dart'; 9 | export 'buttons.dart'; 10 | export 'circle_indicator.dart'; 11 | export 'city_picker/city_picker.dart'; 12 | export 'check_box.dart'; 13 | export 'count_down.dart'; 14 | export 'custom_list_tile.dart'; 15 | export 'dialog.dart'; 16 | export 'dashed_rect.dart'; 17 | export 'element.dart'; 18 | export 'empty_card_widget.dart'; 19 | export 'easy_refresh/easy_refresh.dart'; 20 | export 'first_animation_widget.dart'; 21 | export 'flow_animation_widget.dart'; 22 | export 'html_widget.dart'; 23 | export 'icon_stack_flow_delegate.dart'; 24 | export 'inner_scroll_widget.dart'; 25 | export 'image/image.dart'; 26 | export 'input.dart'; 27 | export 'layout/layout.dart'; 28 | export 'label.dart'; 29 | export 'loading_dialog.dart'; 30 | export 'loading_widget.dart'; 31 | export 'number_input.dart'; 32 | export 'preference.dart'; 33 | export 'rating_bar.dart'; 34 | export 'refresh_widget.dart'; 35 | export 'search_app_bar.dart'; 36 | export 'second_tap_exit_app.dart'; 37 | export 'switch_field.dart'; 38 | export 'toast.dart'; 39 | export 'uploader.dart'; 40 | export 'waterfall_flow_widget.dart'; 41 | export 'wrap_widget.dart'; 42 | -------------------------------------------------------------------------------- /lib/widget/wrap_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | 4 | ///自适应包裹组件 5 | class WrapWidget extends StatelessWidget { 6 | final List children; 7 | final double runSpacing; 8 | final WrapCrossAlignment crossAlignment; 9 | 10 | const WrapWidget({ 11 | Key? key, 12 | required this.children, 13 | this.runSpacing = 0, 14 | this.crossAlignment = WrapCrossAlignment.start, 15 | }) : super(key: key); 16 | @override 17 | Widget build(BuildContext context) { 18 | return Wrap( 19 | direction: Axis.horizontal, 20 | // 排列方向,默认水平方向排列 21 | alignment: WrapAlignment.start, 22 | // 子控件在主轴上的对齐方式 23 | spacing: 12.w, 24 | // 主轴上子控件中间的间距 25 | runAlignment: WrapAlignment.start, 26 | // 子控件在交叉轴上的对齐方式 27 | runSpacing: runSpacing, 28 | // 交叉轴上子控件之间的间距 29 | crossAxisAlignment: crossAlignment, 30 | // 交叉轴上子控件的对齐方式 31 | textDirection: TextDirection.ltr, 32 | // 水平方向上子控件的起始位置 33 | verticalDirection: VerticalDirection.down, 34 | // 垂直方向上子控件的起始位置 35 | children: children, 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_app_template 2 | description: Flutter template project. 3 | 4 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 5 | 6 | version: 1.0.0+1 7 | 8 | environment: 9 | sdk: ">=2.16.1 <3.0.0" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | flutter_localizations: 15 | sdk: flutter 16 | get: ^4.6.6 17 | dio: ^4.0.6 18 | quiver: ^3.1.0 19 | flutter_screenutil: ^5.9.3 20 | flutter_easyrefresh: ^2.2.1 21 | flutter_easyloading: ^3.0.5 22 | shared_preferences: ^2.1.1 23 | device_info_plus: ^10.1.2 24 | package_info_plus: ^8.0.2 25 | kooboo_flutter_app_upgrade: ^0.0.9 26 | google_sign_in: ^5.3.3 27 | sign_in_with_apple: ^6.1.2 28 | flutter_login_facebook: ^1.4.1 29 | permission_handler: ^10.2.0 30 | image_cropper: ^8.0.2 31 | video_compress: ^3.1.3 32 | wechat_assets_picker: ^8.7.0 33 | wechat_camera_picker: ^4.3.2 34 | flutter_image_compress: ^2.3.0 35 | cached_network_image: ^3.3.0 36 | waterfall_flow: ^3.0.2 37 | webview_flutter: ^3.0.4 38 | photo_view: ^0.14.0 39 | flutter_html: ^3.0.0-beta.2 40 | flutter_rating_bar: ^4.0.1 41 | tuple: ^2.0.0 42 | fluwx: ^4.5.5 43 | flutter_app_badger: ^1.4.0 44 | carousel_pro_nullsafety: ^2.0.0 45 | rammus: ^2.5.1 46 | image_gallery_saver_plus: ^3.0.5 47 | android_id: ^0.0.7 48 | package_by_walle: ^1.0.2 49 | weibo_kit: ^4.0.0 50 | tencent_kit: ^4.0.0 51 | tobias: ^4.0.0 52 | 53 | dev_dependencies: 54 | flutter_test: 55 | sdk: flutter 56 | flutter_lints: ^1.0.0 57 | 58 | flutter: 59 | uses-material-design: true 60 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:flutter_app_template/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(const MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ucoon/flutter_app_template/aa64ef3feb293616d0787208171c035a4252633a/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flutter_app_template", 3 | "short_name": "flutter_app_template", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "Flutter template project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | --------------------------------------------------------------------------------