├── .github └── FUNDING.yml ├── .gitignore ├── .metadata ├── LICENSE ├── README.md ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── xuexiang │ │ │ │ └── flutter_template │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── 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 │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets └── fonts │ └── iconfont.ttf ├── i18n ├── en-US.json └── zh-CN.json ├── i18nconfig.json ├── ios ├── .gitignore ├── Flutter │ ├── .last_build_id │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── Runner │ ├── AppDelegate.swift │ ├── 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 │ ├── Runner-Bridging-Header.h │ └── zh-Hans.lproj │ ├── LaunchScreen.strings │ └── Main.strings ├── lib ├── core │ ├── http │ │ └── http.dart │ ├── utils │ │ ├── click.dart │ │ ├── event.dart │ │ ├── locale.dart │ │ ├── path.dart │ │ ├── privacy.dart │ │ ├── toast.dart │ │ ├── utils.dart │ │ ├── xuifont.dart │ │ └── xupdate.dart │ └── widget │ │ ├── grid │ │ └── grid_item.dart │ │ ├── list │ │ ├── article_item.dart │ │ ├── list_item.dart │ │ └── sample_list_item.dart │ │ ├── loading_dialog.dart │ │ └── web_view_page.dart ├── generated │ └── i18n.dart ├── init │ ├── app_init.dart │ ├── default_app.dart │ └── splash.dart ├── main.dart ├── page │ ├── home │ │ └── tab_home.dart │ ├── index.dart │ └── menu │ │ ├── about.dart │ │ ├── language.dart │ │ ├── login.dart │ │ ├── menu_drawer.dart │ │ ├── register.dart │ │ ├── settings.dart │ │ ├── sponsor.dart │ │ └── theme_color.dart ├── router │ ├── route_map.dart │ └── router.dart └── utils │ ├── provider.dart │ └── sputils.dart ├── pubspec.yaml └── test └── widget_test.dart /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | custom: https://github.com/xuexiangjys/Resource/blob/master/doc/sponsor.md 4 | -------------------------------------------------------------------------------- /.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 | .dart_tool/ 26 | .flutter-plugins 27 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | /build/ 31 | pubspec.lock 32 | .flutter-plugins-dependencies 33 | 34 | # Android related 35 | **/android/**/gradle-wrapper.jar 36 | **/android/.gradle 37 | **/android/captures/ 38 | **/android/gradlew 39 | **/android/gradlew.bat 40 | **/android/local.properties 41 | **/android/**/GeneratedPluginRegistrant.java 42 | **/android/keystores/ 43 | 44 | # iOS/XCode related 45 | **/ios/**/*.mode1v3 46 | **/ios/**/*.mode2v3 47 | **/ios/**/*.moved-aside 48 | **/ios/**/*.pbxuser 49 | **/ios/**/*.perspectivev3 50 | **/ios/**/*sync/ 51 | **/ios/**/.sconsign.dblite 52 | **/ios/**/.tags* 53 | **/ios/**/.vagrant/ 54 | **/ios/**/DerivedData/ 55 | **/ios/**/Icon? 56 | **/ios/**/Pods/ 57 | **/ios/**/.symlinks/ 58 | **/ios/**/profile 59 | **/ios/**/xcuserdata 60 | **/ios/.generated/ 61 | **/ios/Flutter/App.framework 62 | **/ios/Flutter/Flutter.framework 63 | **/ios/Flutter/Generated.xcconfig 64 | **/ios/Flutter/app.flx 65 | **/ios/Flutter/app.zip 66 | **/ios/Flutter/flutter_assets/ 67 | **/ios/Flutter/flutter_export_environment.sh 68 | **/ios/ServiceDefinitions.json 69 | **/ios/Runner/GeneratedPluginRegistrant.* 70 | 71 | # Exceptions to above rules. 72 | !**/ios/**/default.mode1v3 73 | !**/ios/**/default.mode2v3 74 | !**/ios/**/default.pbxuser 75 | !**/ios/**/default.perspectivev3 76 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 77 | -------------------------------------------------------------------------------- /.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: fabeb2a16f1d008ab8230f450c49141d35669798 8 | channel: beta 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "{}" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright 2020 xuexiangjys 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flutter_template 2 | 3 | Flutter空壳模板工程,已搭建基础框架,实现国际化、主题换肤、登录注册等功能,可在此基础上简单修改实现自己的应用功能。 4 | 5 | ## 关于我 6 | 7 | | 公众号 | 掘金 | 知乎 | CSDN | 简书 | 思否 | 哔哩哔哩 | 今日头条 8 | |---------|---------|--------- |---------|---------|---------|---------|---------| 9 | | [我的Android开源之旅](https://s1.ax1x.com/2022/04/27/LbG8yt.png) | [点我](https://juejin.im/user/598feef55188257d592e56ed/posts) | [点我](https://www.zhihu.com/people/xuexiangjys/posts) | [点我](https://xuexiangjys.blog.csdn.net/) | [点我](https://www.jianshu.com/u/6bf605575337) | [点我](https://segmentfault.com/u/xuexiangjys) | [点我](https://space.bilibili.com/483850585) | [点我](https://img.rruu.net/image/5ff34ff7b02dd) 10 | 11 | ## 效果 12 | 13 | ![flutter_template.gif](https://s1.ax1x.com/2022/04/27/LbYodK.gif) 14 | 15 | ## Star趋势图 16 | 17 | [![Stargazers over time](https://starchart.cc/xuexiangjys/flutter_template.svg)](https://starchart.cc/xuexiangjys/flutter_template) 18 | 19 | ## 视频教程 20 | 21 | * [Flutter模板工程入门介绍](https://www.bilibili.com/video/BV1854y1W7hB) 22 | 23 | * [Flutter模板工程使用详解](https://www.bilibili.com/video/BV13N411d73X) 24 | 25 | * [Flutter系列视频教程](https://space.bilibili.com/483850585/channel/detail?cid=168279) 26 | 27 | ## 运行 28 | 29 | * 查看一下版本号是否正确, 要求flutter的版本是`2.x.x`的版本。 30 | 31 | ``` 32 | flutter --version 33 | ``` 34 | 35 | 这里推荐的flutter版本为`2.0.6`, 下载地址如下: 36 | 37 | * [windows_2.0.6-stable](https://storage.googleapis.com/flutter_infra_release/releases/stable/windows/flutter_windows_2.0.6-stable.zip) 38 | * [macos_2.0.6-stable](https://storage.googleapis.com/flutter_infra_release/releases/stable/macos/flutter_macos_2.0.6-stable.zip) 39 | 40 | **【注意】** 如果你的flutter版本是`1.x.x`版本,那么请将你的flutter版本进行升级,或者使用`flutter/1.0`的分支。 41 | 42 | * 运行以下命令查看是否需要安装其它依赖项来完成安装 43 | ``` 44 | flutter doctor 45 | ``` 46 | 47 | * 运行启动您的应用 48 | ``` 49 | flutter packages get 50 | flutter run 51 | ``` 52 | 53 | ## 项目集成介绍 54 | 55 | > 本项目精选了目前Flutter最实用的几个库,可大大提高开发的效率。 56 | 57 | * [flutter_i18n(国际化插件)](https://marketplace.visualstudio.com/items?itemName=esskar.vscode-flutter-i18n-json) 58 | * [GetX(路由、状态管理工具)](https://pub.dev/packages/get) 59 | * [cached_network_image (网络缓存图片)](https://pub.dev/packages/cached_network_image) 60 | * [dio (非常好用的网络请求库)](https://pub.dev/packages/dio) 61 | * [event_bus (事件工具)](https://pub.dev/packages/event_bus) 62 | * [flutter_easyrefresh (刷新组件)](https://pub.dev/packages/flutter_easyrefresh) 63 | * [flutter_webview_plugin (网页加载)](https://pub.dev/packages/flutter_webview_plugin) 64 | * [flutter_spinkit (loading加载动画)](https://pub.dev/packages/flutter_spinkit) 65 | * [flutter_swiper (轮播图组件)](https://pub.dev/packages/flutter_swiper) 66 | * [flutter_xupdate (应用版本更新)](https://pub.dev/packages/flutter_xupdate) 67 | * [oktoast](https://pub.dev/packages/oktoast) 68 | * [path_provider (路径)](https://pub.dev/packages/path_provider) 69 | * [package_info (应用包信息)](https://pub.dev/packages/url_launcher) 70 | * [permission_handler 权限申请](https://pub.dev/packages/permission_handler) 71 | * [provider (非常好用的数据共享工具)](https://pub.dev/packages/provider) 72 | * [share (分享)](https://pub.dev/packages/share) 73 | * [shared_preferences](https://pub.dev/packages/shared_preferences) 74 | * [url_launcher (链接处理)](https://pub.dev/packages/url_launcher) 75 | 76 | ## 使用指南 77 | 78 | 1.克隆项目 79 | 80 | ``` 81 | git clone https://github.com/xuexiangjys/flutter_template.git 82 | ``` 83 | 84 | 2.修改项目名(文件夹名),并删除目录下的.git文件夹(隐藏文件) 85 | 86 | 3.使用AS或者VSCode打开项目,然后分别修改flutter、Android、ios项目的包名、应用ID以及应用名等信息。 87 | 88 | 最简单的替换方法就是进行全局替换,搜索关键字`flutter_template`,然后替换你想要的项目包名,如下图所示: 89 | 90 | ![flutter_replace.png](https://s1.ax1x.com/2022/04/27/LbYhs1.png) 91 | 92 | ### Flutter目录修改 93 | 94 | * 修改项目根目录`pubspec.yaml`文件, 修改项目名、描述、版本等信息。 95 | 96 | ![flutter_1.png](https://s1.ax1x.com/2022/04/27/LbY2RJ.png) 97 | 98 | 【注意】这里修改完`pubspec.yaml`中的`name`属性后,flutter项目的包名将会修改,这里我推荐大家使用全局替换的方式修改比较快。例如我想要修改`name`为`flutter_app`,在VSCode中你可以选择`lib`文件夹之后右击,选择`在文件夹中寻找`, 进行全局替换: 99 | 100 | ![flutter_2.png](https://s1.ax1x.com/2022/04/27/LbYfMR.png) 101 | 102 | * 修改`lib/core/http/http.dart`中的网络请求配置,包括:服务器地址、超时、拦截器等设置 103 | 104 | * 修改`lib/core/utils/privacy.dart`中隐私服务政策地址 105 | 106 | * 修改`lib/core/utils/xupdate.dart`中版本更新检查的地址 107 | 108 | 109 | ### Android目录修改 110 | 111 | * 修改android目录下的包名。 112 | 113 | 在VSCode中你可以选择`android`文件夹之后右击,选择`在文件夹中寻找`, 进行全局替换。 114 | 115 | ![android_1.png](https://s1.ax1x.com/2022/04/27/LbYRz9.png) 116 | 117 | 【注意】修改包名之后,记住需要将存放`MainActivity.kt`类的文件夹名也一并修改,否则将会找不到类。 118 | 119 | * 修改应用ID。修改`android/app/build.gradle`文件中的`applicationId` 120 | 121 | * 修改应用名。修改`android/app/src/main/res/values/strings.xml`文件中的`app_name` 122 | 123 | ### IOS目录修改 124 | 125 | ios修改相对简单,直接使用XCode打开ios目录进行修改即可。如下图所示: 126 | 127 | ![ios_1.jpeg](https://s1.ax1x.com/2022/04/27/LbY4qx.jpg) 128 | 129 | ![ios_2.png](https://s1.ax1x.com/2022/04/27/LbYIZ6.png) 130 | 131 | 132 | ## 更新插件版本 133 | 134 | ``` 135 | flutter packages upgrade 136 | flutter pub outdated 137 | flutter pub upgrade --major-versions 138 | ``` 139 | 140 | --- 141 | 142 | ## 如果觉得项目还不错,可以考虑打赏一波 143 | 144 | > 你的打赏是我维护的动力,我将会列出所有打赏人员的清单在下方作为凭证,打赏前请留下打赏项目的备注! 145 | 146 | ![pay.png](https://s1.ax1x.com/2022/04/27/LbGQWd.png) 147 | 148 | ## 微信公众号 149 | 150 | > 更多资讯内容,欢迎扫描关注我的个人微信公众号:【我的Android开源之旅】 151 | 152 | ![gzh_weixin.jpg](https://s1.ax1x.com/2022/04/27/LbGMJH.jpg) 153 | 154 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /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 | android { 29 | compileSdkVersion 28 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.xuexiang.flutter_template" 42 | minSdkVersion 16 43 | targetSdkVersion 28 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 47 | } 48 | 49 | buildTypes { 50 | release { 51 | // TODO: Add your own signing config for the release build. 52 | // Signing with the debug keys for now, so `flutter run --release` works. 53 | signingConfig signingConfigs.debug 54 | } 55 | } 56 | } 57 | 58 | flutter { 59 | source '../..' 60 | } 61 | 62 | dependencies { 63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 64 | testImplementation 'junit:junit:4.12' 65 | androidTestImplementation 'androidx.test:runner:1.1.1' 66 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 67 | } 68 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 12 | 19 | 23 | 27 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 43 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/xuexiang/flutter_template/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.xuexiang.flutter_template 2 | 3 | import androidx.annotation.NonNull; 4 | import io.flutter.embedding.android.FlutterActivity 5 | import io.flutter.embedding.engine.FlutterEngine 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { 10 | GeneratedPluginRegistrant.registerWith(flutterEngine); 11 | } 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/xuexiangjys/flutter_template/85177b1ba88820161420725d69327cb931fb822a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/flutter_template/85177b1ba88820161420725d69327cb931fb822a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/flutter_template/85177b1ba88820161420725d69327cb931fb822a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/flutter_template/85177b1ba88820161420725d69327cb931fb822a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/flutter_template/85177b1ba88820161420725d69327cb931fb822a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | flutter_template 3 | 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google { url 'https://maven.aliyun.com/repository/google' } 5 | jcenter { url 'https://maven.aliyun.com/repository/jcenter' } 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google { url 'https://maven.aliyun.com/repository/google' } 17 | jcenter { url 'https://maven.aliyun.com/repository/jcenter' } 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /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\://services.gradle.org/distributions/gradle-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /assets/fonts/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/flutter_template/85177b1ba88820161420725d69327cb931fb822a/assets/fonts/iconfont.ttf -------------------------------------------------------------------------------- /i18n/en-US.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Flutter Template", 3 | "login": "Login", 4 | "logout": "Logout", 5 | "loginName": "LoginName", 6 | "loginNameHint": "Please enter your login name or email", 7 | "loginNameError": "LoginName cannot be empty!", 8 | "password": "Password", 9 | "passwordHint": "Please enter your password", 10 | "passwordError": "Password cannot be less than 6 digits!", 11 | "loginSuccess": "Login Success", 12 | "register": "Register", 13 | "repeatPassword": "Repeat Password", 14 | "registerSuccess": "Register Success", 15 | "settings": "Settings", 16 | "theme": "Theme", 17 | "language": "Language", 18 | "chinese": "Chinese", 19 | "english": "English", 20 | "auto": "Auto", 21 | "about": "About", 22 | "versionName": "Version", 23 | "author": "Author", 24 | "qqgroup": "QQ Group", 25 | "appupdate": "AppUpdate", 26 | "sponsor": "Sponsor", 27 | "sponsorDescription": "Your reward is the motivation for me to maintain. I will make a list of all reward staff on GitHub as a voucher.", 28 | "home": "Home", 29 | "category": "Category", 30 | "activity": "Activity", 31 | "message": "Message", 32 | "profile": "Profile", 33 | "reminder": "Reminder", 34 | "agree": "Agree", 35 | "disagree": "Disagree", 36 | "lookAgain": "Look Again", 37 | "stillDisagree": "Still Disagree", 38 | "thinkAboutItAgain": " Do you want to think about it again?", 39 | "privacyExplainAgain": " We attach great importance to the protection of your personal information and promise to protect and process your information in strict accordance with the 《{appName} privacy policy》. If you do not agree with the policy, we regret that we will not be able to provide you with services.", 40 | "exitApp": "Exit App", 41 | "privacyName": "《{appName} privacy policy》", 42 | "welcome": " Welcome to {appName}!", 43 | "welcome1": " We know the importance of personal information to you and thank you for your trust in us.", 44 | "welcome2": " In order to better protect your rights and interests and comply with the relevant regulatory requirements, we will explain to you through ", 45 | "welcome3": " how we will collect, store, protect, use and provide your information to the outside world, and explain your rights.", 46 | "welcome4": " For more details, please refer to", 47 | "welcome5": " the full text.", 48 | "agreePrivacy": "Privacy agreement agreed!", 49 | "darkTheme": "Dark Theme" 50 | } -------------------------------------------------------------------------------- /i18n/zh-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Flutter模版工程", 3 | "login": "登录", 4 | "logout": "登出", 5 | "loginName": "用户名", 6 | "loginNameHint": "请输入您的用户名或邮箱", 7 | "loginNameError": "用户名不能为空!", 8 | "password": "密码", 9 | "passwordHint": "请输入您的密码", 10 | "passwordError": "密码不能少于6位!", 11 | "loginSuccess": "登录成功", 12 | "register": "注册", 13 | "repeatPassword": "重复密码", 14 | "registerSuccess": "注册成功", 15 | "settings": "设置", 16 | "theme": "主题", 17 | "language": "语言", 18 | "chinese": "简体中文", 19 | "english": "英语", 20 | "auto": "系统默认", 21 | "about": "关于", 22 | "versionName": "版本号", 23 | "author": "作者", 24 | "qqgroup": "QQ群", 25 | "appupdate": "版本更新", 26 | "sponsor": "赞助", 27 | "sponsorDescription": "你的打赏是我维护的动力,我将会列出所有打赏人员的清单在Github上作为凭证.", 28 | "home": "主页", 29 | "category": "分类", 30 | "activity": "活动", 31 | "message": "消息", 32 | "profile": "我的", 33 | "reminder": "温馨提醒", 34 | "agree": "同意", 35 | "disagree": "不同意", 36 | "lookAgain": "再次查看", 37 | "stillDisagree": "仍不同意", 38 | "thinkAboutItAgain": " 要不要再想想?", 39 | "privacyExplainAgain": " 我们非常重视对你个人信息的保护,承诺严格按照《{appName}隐私权政策》保护及处理你的信息。如果你不同意该政策,很遗憾我们将无法为你提供服务。", 40 | "exitApp": "退出应用", 41 | "privacyName": "《{appName}隐私权政策》", 42 | "welcome": " 欢迎来到{appName}!", 43 | "welcome1": " 我们深知个人信息对你的重要性,也感谢你对我们的信任。", 44 | "welcome2": " 为了更好地保护你的权益,同时遵守相关监管的要求,我们将通过", 45 | "welcome3": "向你说明我们会如何收集、存储、保护、使用及对外提供你的信息,并说明你享有的权利。", 46 | "welcome4": " 更多详情,敬请查阅", 47 | "welcome5": "全文。", 48 | "agreePrivacy": "已同意隐私协议!", 49 | "darkTheme": "深色主题" 50 | } -------------------------------------------------------------------------------- /i18nconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultLocale": "en-US", 3 | "locales": [ 4 | "en-US", 5 | "zh-CN" 6 | ], 7 | "localePath": "i18n", 8 | "generatedPath": "lib/generated", 9 | "ltr": [ 10 | "en-US", 11 | "zh-CN" 12 | ], 13 | "rtl": [] 14 | } -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /ios/Flutter/.last_build_id: -------------------------------------------------------------------------------- 1 | 4d5518bed0b11145d1682b39e915be1d -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | 8.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, '9.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 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - flutter_webview_plugin (0.0.1): 4 | - Flutter 5 | - FMDB (2.7.5): 6 | - FMDB/standard (= 2.7.5) 7 | - FMDB/standard (2.7.5) 8 | - image_picker_saver (0.0.1): 9 | - Flutter 10 | - package_info (0.0.1): 11 | - Flutter 12 | - path_provider (0.0.1): 13 | - Flutter 14 | - "permission_handler (5.1.0+2)": 15 | - Flutter 16 | - share (0.0.1): 17 | - Flutter 18 | - shared_preferences (0.0.1): 19 | - Flutter 20 | - sqflite (0.0.2): 21 | - Flutter 22 | - FMDB (>= 2.7.5) 23 | - url_launcher (0.0.1): 24 | - Flutter 25 | 26 | DEPENDENCIES: 27 | - Flutter (from `Flutter`) 28 | - flutter_webview_plugin (from `.symlinks/plugins/flutter_webview_plugin/ios`) 29 | - image_picker_saver (from `.symlinks/plugins/image_picker_saver/ios`) 30 | - package_info (from `.symlinks/plugins/package_info/ios`) 31 | - path_provider (from `.symlinks/plugins/path_provider/ios`) 32 | - permission_handler (from `.symlinks/plugins/permission_handler/ios`) 33 | - share (from `.symlinks/plugins/share/ios`) 34 | - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) 35 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 36 | - url_launcher (from `.symlinks/plugins/url_launcher/ios`) 37 | 38 | SPEC REPOS: 39 | trunk: 40 | - FMDB 41 | 42 | EXTERNAL SOURCES: 43 | Flutter: 44 | :path: Flutter 45 | flutter_webview_plugin: 46 | :path: ".symlinks/plugins/flutter_webview_plugin/ios" 47 | image_picker_saver: 48 | :path: ".symlinks/plugins/image_picker_saver/ios" 49 | package_info: 50 | :path: ".symlinks/plugins/package_info/ios" 51 | path_provider: 52 | :path: ".symlinks/plugins/path_provider/ios" 53 | permission_handler: 54 | :path: ".symlinks/plugins/permission_handler/ios" 55 | share: 56 | :path: ".symlinks/plugins/share/ios" 57 | shared_preferences: 58 | :path: ".symlinks/plugins/shared_preferences/ios" 59 | sqflite: 60 | :path: ".symlinks/plugins/sqflite/ios" 61 | url_launcher: 62 | :path: ".symlinks/plugins/url_launcher/ios" 63 | 64 | SPEC CHECKSUMS: 65 | Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c 66 | flutter_webview_plugin: ed9e8a6a96baf0c867e90e1bce2673913eeac694 67 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 68 | image_picker_saver: 4f28bd70e1efdca68ad88beab0f11d22cffe04f6 69 | package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 70 | path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c 71 | permission_handler: ccb20a9fad0ee9b1314a52b70b76b473c5f8dab0 72 | share: 0b2c3e82132f5888bccca3351c504d0003b3b410 73 | shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d 74 | sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 75 | url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef 76 | 77 | PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c 78 | 79 | COCOAPODS: 1.10.1 80 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 13 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 16 | ABA1167A6F012F337F5B2670 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37E50CD4616E73039A0D71F5 /* Pods_Runner.framework */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXCopyFilesBuildPhase section */ 20 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 21 | isa = PBXCopyFilesBuildPhase; 22 | buildActionMask = 2147483647; 23 | dstPath = ""; 24 | dstSubfolderSpec = 10; 25 | files = ( 26 | ); 27 | name = "Embed Frameworks"; 28 | runOnlyForDeploymentPostprocessing = 0; 29 | }; 30 | /* End PBXCopyFilesBuildPhase section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 34 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 35 | 1C9CD4CEEBA91E1E15F22DCC /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 36 | 23038588C31982282424F233 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 37 | 37E50CD4616E73039A0D71F5 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 38 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 39 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 40 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 41 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 42 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 43 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 44 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 46 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 47 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 48 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 49 | F92BE4FF12110F13B1E1E08D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 50 | FDEADB8E2420E737006E9B22 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Main.strings"; sourceTree = ""; }; 51 | FDEADB8F2420E737006E9B22 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/LaunchScreen.strings"; sourceTree = ""; }; 52 | /* End PBXFileReference section */ 53 | 54 | /* Begin PBXFrameworksBuildPhase section */ 55 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 56 | isa = PBXFrameworksBuildPhase; 57 | buildActionMask = 2147483647; 58 | files = ( 59 | ABA1167A6F012F337F5B2670 /* Pods_Runner.framework in Frameworks */, 60 | ); 61 | runOnlyForDeploymentPostprocessing = 0; 62 | }; 63 | /* End PBXFrameworksBuildPhase section */ 64 | 65 | /* Begin PBXGroup section */ 66 | 76210EC4F3F37EED7873DD30 /* Pods */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | F92BE4FF12110F13B1E1E08D /* Pods-Runner.debug.xcconfig */, 70 | 23038588C31982282424F233 /* Pods-Runner.release.xcconfig */, 71 | 1C9CD4CEEBA91E1E15F22DCC /* Pods-Runner.profile.xcconfig */, 72 | ); 73 | path = Pods; 74 | sourceTree = ""; 75 | }; 76 | 8234B6B7ED03DCEB71FBC78B /* Frameworks */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | 37E50CD4616E73039A0D71F5 /* Pods_Runner.framework */, 80 | ); 81 | name = Frameworks; 82 | sourceTree = ""; 83 | }; 84 | 9740EEB11CF90186004384FC /* Flutter */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 88 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 89 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 90 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 91 | ); 92 | name = Flutter; 93 | sourceTree = ""; 94 | }; 95 | 97C146E51CF9000F007C117D = { 96 | isa = PBXGroup; 97 | children = ( 98 | 9740EEB11CF90186004384FC /* Flutter */, 99 | 97C146F01CF9000F007C117D /* Runner */, 100 | 97C146EF1CF9000F007C117D /* Products */, 101 | 76210EC4F3F37EED7873DD30 /* Pods */, 102 | 8234B6B7ED03DCEB71FBC78B /* Frameworks */, 103 | ); 104 | sourceTree = ""; 105 | }; 106 | 97C146EF1CF9000F007C117D /* Products */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | 97C146EE1CF9000F007C117D /* Runner.app */, 110 | ); 111 | name = Products; 112 | sourceTree = ""; 113 | }; 114 | 97C146F01CF9000F007C117D /* Runner */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 118 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 119 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 120 | 97C147021CF9000F007C117D /* Info.plist */, 121 | 97C146F11CF9000F007C117D /* Supporting Files */, 122 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 123 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 124 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 125 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 126 | ); 127 | path = Runner; 128 | sourceTree = ""; 129 | }; 130 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | ); 134 | name = "Supporting Files"; 135 | sourceTree = ""; 136 | }; 137 | /* End PBXGroup section */ 138 | 139 | /* Begin PBXNativeTarget section */ 140 | 97C146ED1CF9000F007C117D /* Runner */ = { 141 | isa = PBXNativeTarget; 142 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 143 | buildPhases = ( 144 | D6F07A58898EA60246BB2204 /* [CP] Check Pods Manifest.lock */, 145 | 9740EEB61CF901F6004384FC /* Run Script */, 146 | 97C146EA1CF9000F007C117D /* Sources */, 147 | 97C146EB1CF9000F007C117D /* Frameworks */, 148 | 97C146EC1CF9000F007C117D /* Resources */, 149 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 150 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 151 | 8D5FA44DA80BF1300BE59A81 /* [CP] Embed Pods Frameworks */, 152 | ); 153 | buildRules = ( 154 | ); 155 | dependencies = ( 156 | ); 157 | name = Runner; 158 | productName = Runner; 159 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 160 | productType = "com.apple.product-type.application"; 161 | }; 162 | /* End PBXNativeTarget section */ 163 | 164 | /* Begin PBXProject section */ 165 | 97C146E61CF9000F007C117D /* Project object */ = { 166 | isa = PBXProject; 167 | attributes = { 168 | LastUpgradeCheck = 1020; 169 | ORGANIZATIONNAME = ""; 170 | TargetAttributes = { 171 | 97C146ED1CF9000F007C117D = { 172 | CreatedOnToolsVersion = 7.3.1; 173 | LastSwiftMigration = 1100; 174 | }; 175 | }; 176 | }; 177 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 178 | compatibilityVersion = "Xcode 3.2"; 179 | developmentRegion = en; 180 | hasScannedForEncodings = 0; 181 | knownRegions = ( 182 | en, 183 | Base, 184 | "zh-Hans", 185 | ); 186 | mainGroup = 97C146E51CF9000F007C117D; 187 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 188 | projectDirPath = ""; 189 | projectRoot = ""; 190 | targets = ( 191 | 97C146ED1CF9000F007C117D /* Runner */, 192 | ); 193 | }; 194 | /* End PBXProject section */ 195 | 196 | /* Begin PBXResourcesBuildPhase section */ 197 | 97C146EC1CF9000F007C117D /* Resources */ = { 198 | isa = PBXResourcesBuildPhase; 199 | buildActionMask = 2147483647; 200 | files = ( 201 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 202 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 203 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 204 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 205 | ); 206 | runOnlyForDeploymentPostprocessing = 0; 207 | }; 208 | /* End PBXResourcesBuildPhase section */ 209 | 210 | /* Begin PBXShellScriptBuildPhase section */ 211 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 212 | isa = PBXShellScriptBuildPhase; 213 | buildActionMask = 2147483647; 214 | files = ( 215 | ); 216 | inputPaths = ( 217 | ); 218 | name = "Thin Binary"; 219 | outputPaths = ( 220 | ); 221 | runOnlyForDeploymentPostprocessing = 0; 222 | shellPath = /bin/sh; 223 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 224 | }; 225 | 8D5FA44DA80BF1300BE59A81 /* [CP] Embed Pods Frameworks */ = { 226 | isa = PBXShellScriptBuildPhase; 227 | buildActionMask = 2147483647; 228 | files = ( 229 | ); 230 | inputPaths = ( 231 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", 232 | "${BUILT_PRODUCTS_DIR}/FMDB/FMDB.framework", 233 | "${BUILT_PRODUCTS_DIR}/flutter_webview_plugin/flutter_webview_plugin.framework", 234 | "${BUILT_PRODUCTS_DIR}/image_picker_saver/image_picker_saver.framework", 235 | "${BUILT_PRODUCTS_DIR}/package_info/package_info.framework", 236 | "${BUILT_PRODUCTS_DIR}/path_provider/path_provider.framework", 237 | "${BUILT_PRODUCTS_DIR}/share/share.framework", 238 | "${BUILT_PRODUCTS_DIR}/shared_preferences/shared_preferences.framework", 239 | "${BUILT_PRODUCTS_DIR}/sqflite/sqflite.framework", 240 | "${BUILT_PRODUCTS_DIR}/url_launcher/url_launcher.framework", 241 | ); 242 | name = "[CP] Embed Pods Frameworks"; 243 | outputPaths = ( 244 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FMDB.framework", 245 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_webview_plugin.framework", 246 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_picker_saver.framework", 247 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info.framework", 248 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider.framework", 249 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/share.framework", 250 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences.framework", 251 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqflite.framework", 252 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher.framework", 253 | ); 254 | runOnlyForDeploymentPostprocessing = 0; 255 | shellPath = /bin/sh; 256 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 257 | showEnvVarsInLog = 0; 258 | }; 259 | 9740EEB61CF901F6004384FC /* Run Script */ = { 260 | isa = PBXShellScriptBuildPhase; 261 | buildActionMask = 2147483647; 262 | files = ( 263 | ); 264 | inputPaths = ( 265 | ); 266 | name = "Run Script"; 267 | outputPaths = ( 268 | ); 269 | runOnlyForDeploymentPostprocessing = 0; 270 | shellPath = /bin/sh; 271 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 272 | }; 273 | D6F07A58898EA60246BB2204 /* [CP] Check Pods Manifest.lock */ = { 274 | isa = PBXShellScriptBuildPhase; 275 | buildActionMask = 2147483647; 276 | files = ( 277 | ); 278 | inputFileListPaths = ( 279 | ); 280 | inputPaths = ( 281 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 282 | "${PODS_ROOT}/Manifest.lock", 283 | ); 284 | name = "[CP] Check Pods Manifest.lock"; 285 | outputFileListPaths = ( 286 | ); 287 | outputPaths = ( 288 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 289 | ); 290 | runOnlyForDeploymentPostprocessing = 0; 291 | shellPath = /bin/sh; 292 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 293 | showEnvVarsInLog = 0; 294 | }; 295 | /* End PBXShellScriptBuildPhase section */ 296 | 297 | /* Begin PBXSourcesBuildPhase section */ 298 | 97C146EA1CF9000F007C117D /* Sources */ = { 299 | isa = PBXSourcesBuildPhase; 300 | buildActionMask = 2147483647; 301 | files = ( 302 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 303 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 304 | ); 305 | runOnlyForDeploymentPostprocessing = 0; 306 | }; 307 | /* End PBXSourcesBuildPhase section */ 308 | 309 | /* Begin PBXVariantGroup section */ 310 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 311 | isa = PBXVariantGroup; 312 | children = ( 313 | 97C146FB1CF9000F007C117D /* Base */, 314 | FDEADB8E2420E737006E9B22 /* zh-Hans */, 315 | ); 316 | name = Main.storyboard; 317 | sourceTree = ""; 318 | }; 319 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 320 | isa = PBXVariantGroup; 321 | children = ( 322 | 97C147001CF9000F007C117D /* Base */, 323 | FDEADB8F2420E737006E9B22 /* zh-Hans */, 324 | ); 325 | name = LaunchScreen.storyboard; 326 | sourceTree = ""; 327 | }; 328 | /* End PBXVariantGroup section */ 329 | 330 | /* Begin XCBuildConfiguration section */ 331 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 332 | isa = XCBuildConfiguration; 333 | buildSettings = { 334 | ALWAYS_SEARCH_USER_PATHS = NO; 335 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 336 | CLANG_ANALYZER_NONNULL = YES; 337 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 338 | CLANG_CXX_LIBRARY = "libc++"; 339 | CLANG_ENABLE_MODULES = YES; 340 | CLANG_ENABLE_OBJC_ARC = YES; 341 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 342 | CLANG_WARN_BOOL_CONVERSION = YES; 343 | CLANG_WARN_COMMA = YES; 344 | CLANG_WARN_CONSTANT_CONVERSION = YES; 345 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 346 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 347 | CLANG_WARN_EMPTY_BODY = YES; 348 | CLANG_WARN_ENUM_CONVERSION = YES; 349 | CLANG_WARN_INFINITE_RECURSION = YES; 350 | CLANG_WARN_INT_CONVERSION = YES; 351 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 352 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 353 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 354 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 355 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 356 | CLANG_WARN_STRICT_PROTOTYPES = YES; 357 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 358 | CLANG_WARN_UNREACHABLE_CODE = YES; 359 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 360 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 361 | COPY_PHASE_STRIP = NO; 362 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 363 | ENABLE_NS_ASSERTIONS = NO; 364 | ENABLE_STRICT_OBJC_MSGSEND = YES; 365 | GCC_C_LANGUAGE_STANDARD = gnu99; 366 | GCC_NO_COMMON_BLOCKS = YES; 367 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 368 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 369 | GCC_WARN_UNDECLARED_SELECTOR = YES; 370 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 371 | GCC_WARN_UNUSED_FUNCTION = YES; 372 | GCC_WARN_UNUSED_VARIABLE = YES; 373 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 374 | MTL_ENABLE_DEBUG_INFO = NO; 375 | SDKROOT = iphoneos; 376 | SUPPORTED_PLATFORMS = iphoneos; 377 | TARGETED_DEVICE_FAMILY = "1,2"; 378 | VALIDATE_PRODUCT = YES; 379 | }; 380 | name = Profile; 381 | }; 382 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 383 | isa = XCBuildConfiguration; 384 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 385 | buildSettings = { 386 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 387 | CLANG_ENABLE_MODULES = YES; 388 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 389 | ENABLE_BITCODE = NO; 390 | FRAMEWORK_SEARCH_PATHS = ( 391 | "$(inherited)", 392 | "$(PROJECT_DIR)/Flutter", 393 | ); 394 | INFOPLIST_FILE = Runner/Info.plist; 395 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 396 | LIBRARY_SEARCH_PATHS = ( 397 | "$(inherited)", 398 | "$(PROJECT_DIR)/Flutter", 399 | ); 400 | PRODUCT_BUNDLE_IDENTIFIER = com.xuexiang.flutter_template; 401 | PRODUCT_NAME = "$(TARGET_NAME)"; 402 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 403 | SWIFT_VERSION = 5.0; 404 | VERSIONING_SYSTEM = "apple-generic"; 405 | }; 406 | name = Profile; 407 | }; 408 | 97C147031CF9000F007C117D /* Debug */ = { 409 | isa = XCBuildConfiguration; 410 | buildSettings = { 411 | ALWAYS_SEARCH_USER_PATHS = NO; 412 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 413 | CLANG_ANALYZER_NONNULL = YES; 414 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 415 | CLANG_CXX_LIBRARY = "libc++"; 416 | CLANG_ENABLE_MODULES = YES; 417 | CLANG_ENABLE_OBJC_ARC = YES; 418 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 419 | CLANG_WARN_BOOL_CONVERSION = YES; 420 | CLANG_WARN_COMMA = YES; 421 | CLANG_WARN_CONSTANT_CONVERSION = YES; 422 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 423 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 424 | CLANG_WARN_EMPTY_BODY = YES; 425 | CLANG_WARN_ENUM_CONVERSION = YES; 426 | CLANG_WARN_INFINITE_RECURSION = YES; 427 | CLANG_WARN_INT_CONVERSION = YES; 428 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 429 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 430 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 431 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 432 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 433 | CLANG_WARN_STRICT_PROTOTYPES = YES; 434 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 435 | CLANG_WARN_UNREACHABLE_CODE = YES; 436 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 437 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 438 | COPY_PHASE_STRIP = NO; 439 | DEBUG_INFORMATION_FORMAT = dwarf; 440 | ENABLE_STRICT_OBJC_MSGSEND = YES; 441 | ENABLE_TESTABILITY = YES; 442 | GCC_C_LANGUAGE_STANDARD = gnu99; 443 | GCC_DYNAMIC_NO_PIC = NO; 444 | GCC_NO_COMMON_BLOCKS = YES; 445 | GCC_OPTIMIZATION_LEVEL = 0; 446 | GCC_PREPROCESSOR_DEFINITIONS = ( 447 | "DEBUG=1", 448 | "$(inherited)", 449 | ); 450 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 451 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 452 | GCC_WARN_UNDECLARED_SELECTOR = YES; 453 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 454 | GCC_WARN_UNUSED_FUNCTION = YES; 455 | GCC_WARN_UNUSED_VARIABLE = YES; 456 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 457 | MTL_ENABLE_DEBUG_INFO = YES; 458 | ONLY_ACTIVE_ARCH = YES; 459 | SDKROOT = iphoneos; 460 | TARGETED_DEVICE_FAMILY = "1,2"; 461 | }; 462 | name = Debug; 463 | }; 464 | 97C147041CF9000F007C117D /* Release */ = { 465 | isa = XCBuildConfiguration; 466 | buildSettings = { 467 | ALWAYS_SEARCH_USER_PATHS = NO; 468 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 469 | CLANG_ANALYZER_NONNULL = YES; 470 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 471 | CLANG_CXX_LIBRARY = "libc++"; 472 | CLANG_ENABLE_MODULES = YES; 473 | CLANG_ENABLE_OBJC_ARC = YES; 474 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 475 | CLANG_WARN_BOOL_CONVERSION = YES; 476 | CLANG_WARN_COMMA = YES; 477 | CLANG_WARN_CONSTANT_CONVERSION = YES; 478 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 479 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 480 | CLANG_WARN_EMPTY_BODY = YES; 481 | CLANG_WARN_ENUM_CONVERSION = YES; 482 | CLANG_WARN_INFINITE_RECURSION = YES; 483 | CLANG_WARN_INT_CONVERSION = YES; 484 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 485 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 486 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 487 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 488 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 489 | CLANG_WARN_STRICT_PROTOTYPES = YES; 490 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 491 | CLANG_WARN_UNREACHABLE_CODE = YES; 492 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 493 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 494 | COPY_PHASE_STRIP = NO; 495 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 496 | ENABLE_NS_ASSERTIONS = NO; 497 | ENABLE_STRICT_OBJC_MSGSEND = YES; 498 | GCC_C_LANGUAGE_STANDARD = gnu99; 499 | GCC_NO_COMMON_BLOCKS = YES; 500 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 501 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 502 | GCC_WARN_UNDECLARED_SELECTOR = YES; 503 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 504 | GCC_WARN_UNUSED_FUNCTION = YES; 505 | GCC_WARN_UNUSED_VARIABLE = YES; 506 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 507 | MTL_ENABLE_DEBUG_INFO = NO; 508 | SDKROOT = iphoneos; 509 | SUPPORTED_PLATFORMS = iphoneos; 510 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 511 | TARGETED_DEVICE_FAMILY = "1,2"; 512 | VALIDATE_PRODUCT = YES; 513 | }; 514 | name = Release; 515 | }; 516 | 97C147061CF9000F007C117D /* Debug */ = { 517 | isa = XCBuildConfiguration; 518 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 519 | buildSettings = { 520 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 521 | CLANG_ENABLE_MODULES = YES; 522 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 523 | ENABLE_BITCODE = NO; 524 | FRAMEWORK_SEARCH_PATHS = ( 525 | "$(inherited)", 526 | "$(PROJECT_DIR)/Flutter", 527 | ); 528 | INFOPLIST_FILE = Runner/Info.plist; 529 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 530 | LIBRARY_SEARCH_PATHS = ( 531 | "$(inherited)", 532 | "$(PROJECT_DIR)/Flutter", 533 | ); 534 | PRODUCT_BUNDLE_IDENTIFIER = com.xuexiang.flutter_template; 535 | PRODUCT_NAME = "$(TARGET_NAME)"; 536 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 537 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 538 | SWIFT_VERSION = 5.0; 539 | VERSIONING_SYSTEM = "apple-generic"; 540 | }; 541 | name = Debug; 542 | }; 543 | 97C147071CF9000F007C117D /* Release */ = { 544 | isa = XCBuildConfiguration; 545 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 546 | buildSettings = { 547 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 548 | CLANG_ENABLE_MODULES = YES; 549 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 550 | ENABLE_BITCODE = NO; 551 | FRAMEWORK_SEARCH_PATHS = ( 552 | "$(inherited)", 553 | "$(PROJECT_DIR)/Flutter", 554 | ); 555 | INFOPLIST_FILE = Runner/Info.plist; 556 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 557 | LIBRARY_SEARCH_PATHS = ( 558 | "$(inherited)", 559 | "$(PROJECT_DIR)/Flutter", 560 | ); 561 | PRODUCT_BUNDLE_IDENTIFIER = com.xuexiang.flutter_template; 562 | PRODUCT_NAME = "$(TARGET_NAME)"; 563 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 564 | SWIFT_VERSION = 5.0; 565 | VERSIONING_SYSTEM = "apple-generic"; 566 | }; 567 | name = Release; 568 | }; 569 | /* End XCBuildConfiguration section */ 570 | 571 | /* Begin XCConfigurationList section */ 572 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 573 | isa = XCConfigurationList; 574 | buildConfigurations = ( 575 | 97C147031CF9000F007C117D /* Debug */, 576 | 97C147041CF9000F007C117D /* Release */, 577 | 249021D3217E4FDB00AE95B9 /* Profile */, 578 | ); 579 | defaultConfigurationIsVisible = 0; 580 | defaultConfigurationName = Release; 581 | }; 582 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 583 | isa = XCConfigurationList; 584 | buildConfigurations = ( 585 | 97C147061CF9000F007C117D /* Debug */, 586 | 97C147071CF9000F007C117D /* Release */, 587 | 249021D4217E4FDB00AE95B9 /* Profile */, 588 | ); 589 | defaultConfigurationIsVisible = 0; 590 | defaultConfigurationName = Release; 591 | }; 592 | /* End XCConfigurationList section */ 593 | }; 594 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 595 | } 596 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /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/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 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/xuexiangjys/flutter_template/85177b1ba88820161420725d69327cb931fb822a/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/xuexiangjys/flutter_template/85177b1ba88820161420725d69327cb931fb822a/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/xuexiangjys/flutter_template/85177b1ba88820161420725d69327cb931fb822a/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/xuexiangjys/flutter_template/85177b1ba88820161420725d69327cb931fb822a/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/xuexiangjys/flutter_template/85177b1ba88820161420725d69327cb931fb822a/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/xuexiangjys/flutter_template/85177b1ba88820161420725d69327cb931fb822a/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/xuexiangjys/flutter_template/85177b1ba88820161420725d69327cb931fb822a/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/xuexiangjys/flutter_template/85177b1ba88820161420725d69327cb931fb822a/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/xuexiangjys/flutter_template/85177b1ba88820161420725d69327cb931fb822a/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/xuexiangjys/flutter_template/85177b1ba88820161420725d69327cb931fb822a/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/xuexiangjys/flutter_template/85177b1ba88820161420725d69327cb931fb822a/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/xuexiangjys/flutter_template/85177b1ba88820161420725d69327cb931fb822a/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/xuexiangjys/flutter_template/85177b1ba88820161420725d69327cb931fb822a/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/xuexiangjys/flutter_template/85177b1ba88820161420725d69327cb931fb822a/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/xuexiangjys/flutter_template/85177b1ba88820161420725d69327cb931fb822a/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/xuexiangjys/flutter_template/85177b1ba88820161420725d69327cb931fb822a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/flutter_template/85177b1ba88820161420725d69327cb931fb822a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuexiangjys/flutter_template/85177b1ba88820161420725d69327cb931fb822a/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_template 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleLocalizations 16 | 17 | en 18 | es 19 | ru 20 | 21 | CFBundleName 22 | $(PRODUCT_NAME) 23 | CFBundlePackageType 24 | APPL 25 | CFBundleShortVersionString 26 | $(FLUTTER_BUILD_NAME) 27 | CFBundleSignature 28 | ???? 29 | CFBundleVersion 30 | $(FLUTTER_BUILD_NUMBER) 31 | LSRequiresIPhoneOS 32 | 33 | NSAppTransportSecurity 34 | 35 | NSAllowsArbitraryLoads 36 | 37 | NSAllowsArbitraryLoadsInWebContent 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIMainStoryboardFile 43 | Main 44 | UISupportedInterfaceOrientations 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationLandscapeLeft 48 | UIInterfaceOrientationLandscapeRight 49 | 50 | UISupportedInterfaceOrientations~ipad 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationPortraitUpsideDown 54 | UIInterfaceOrientationLandscapeLeft 55 | UIInterfaceOrientationLandscapeRight 56 | 57 | UIViewControllerBasedStatusBarAppearance 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/Runner/zh-Hans.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ios/Runner/zh-Hans.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /lib/core/http/http.dart: -------------------------------------------------------------------------------- 1 | import 'package:cookie_jar/cookie_jar.dart'; 2 | import 'package:dio/dio.dart'; 3 | import 'package:dio_cookie_manager/dio_cookie_manager.dart'; 4 | import 'package:flutter_template/core/utils/path.dart'; 5 | 6 | class XHttp { 7 | XHttp._internal(); 8 | 9 | ///网络请求配置 10 | static final Dio dio = Dio(BaseOptions( 11 | baseUrl: "https://www.wanandroid.com", 12 | connectTimeout: 5000, 13 | receiveTimeout: 3000, 14 | )); 15 | 16 | ///初始化dio 17 | static void init() { 18 | ///初始化cookie 19 | PathUtils.getDocumentsDirPath().then((value) { 20 | var cookieJar = 21 | PersistCookieJar(storage: FileStorage(value + "/.cookies/")); 22 | dio.interceptors.add(CookieManager(cookieJar)); 23 | }); 24 | 25 | //添加拦截器 26 | dio.interceptors 27 | .add(InterceptorsWrapper(onRequest: (RequestOptions options, handler) { 28 | print("请求之前"); 29 | return handler.next(options); 30 | }, onResponse: (Response response, handler) { 31 | print("响应之前"); 32 | return handler.next(response); 33 | }, onError: (DioError e, handler) { 34 | print("错误之前"); 35 | handleError(e); 36 | return handler.next(e); 37 | })); 38 | } 39 | 40 | ///error统一处理 41 | static void handleError(DioError e) { 42 | switch (e.type) { 43 | case DioErrorType.connectTimeout: 44 | print("连接超时"); 45 | break; 46 | case DioErrorType.sendTimeout: 47 | print("请求超时"); 48 | break; 49 | case DioErrorType.receiveTimeout: 50 | print("响应超时"); 51 | break; 52 | case DioErrorType.response: 53 | print("出现异常"); 54 | break; 55 | case DioErrorType.cancel: 56 | print("请求取消"); 57 | break; 58 | default: 59 | print("未知错误"); 60 | break; 61 | } 62 | } 63 | 64 | ///get请求 65 | static Future get(String url, [Map params]) async { 66 | Response response; 67 | if (params != null) { 68 | response = await dio.get(url, queryParameters: params); 69 | } else { 70 | response = await dio.get(url); 71 | } 72 | return response.data; 73 | } 74 | 75 | ///post 表单请求 76 | static Future post(String url, [Map params]) async { 77 | Response response = await dio.post(url, queryParameters: params); 78 | return response.data; 79 | } 80 | 81 | ///post body请求 82 | static Future postJson(String url, [Map data]) async { 83 | Response response = await dio.post(url, data: data); 84 | return response.data; 85 | } 86 | 87 | ///下载文件 88 | static Future downloadFile(urlPath, savePath) async { 89 | Response response; 90 | try { 91 | response = await dio.download(urlPath, savePath, 92 | onReceiveProgress: (int count, int total) { 93 | //进度 94 | print("$count $total"); 95 | }); 96 | } on DioError catch (e) { 97 | handleError(e); 98 | } 99 | return response.data; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /lib/core/utils/click.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'toast.dart'; 3 | 4 | class ClickUtils { 5 | ClickUtils._internal(); 6 | 7 | static DateTime _lastPressedAt; //上次点击时间 8 | 9 | //双击返回 10 | static Future exitBy2Click( 11 | {int duration = 1000, ScaffoldState status}) async { 12 | if (status != null && status.isDrawerOpen) { 13 | return Future.value(true); 14 | } 15 | 16 | if (_lastPressedAt == null || 17 | DateTime.now().difference(_lastPressedAt) > 18 | Duration(milliseconds: duration)) { 19 | //两次点击间隔超过1秒则重新计时 20 | ToastUtils.toast("再按一次退出程序"); 21 | _lastPressedAt = DateTime.now(); 22 | return Future.value(false); 23 | } 24 | return Future.value(true); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/core/utils/event.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:event_bus/event_bus.dart'; 4 | 5 | //EventBus工具类 6 | class XEvent { 7 | XEvent._internal(); 8 | 9 | static Map sEventPool = {}; 10 | 11 | static Map> sStreamPool = {}; 12 | 13 | static EventBus getEvent(String eventName, {bool isSync = false}) { 14 | EventBus event = sEventPool[eventName]; 15 | if (event == null) { 16 | event = new EventBus(sync: isSync); 17 | sEventPool[eventName] = event; 18 | } 19 | return event; 20 | } 21 | 22 | //订阅信息, 默认是异步的 23 | static StreamSubscription on(String eventName, void onData(T event), 24 | {bool isSync = false, 25 | Function onError, 26 | void onDone(), 27 | bool cancelOnError}) { 28 | StreamSubscription stream = getEvent(eventName, isSync: isSync) 29 | .on() 30 | .listen(onData, 31 | onError: onError, onDone: onDone, cancelOnError: cancelOnError); 32 | 33 | List streams = sStreamPool[eventName]; 34 | if (streams == null) { 35 | streams = []; 36 | streams.add(stream); 37 | sStreamPool[eventName] = streams; 38 | } else { 39 | streams.add(stream); 40 | } 41 | return stream; 42 | } 43 | 44 | //事件发送 45 | static void post(String eventName, event) { 46 | EventBus eventBus = getEvent(eventName); 47 | eventBus.fire(event); 48 | } 49 | 50 | //订阅取消 51 | static void cancelAll(String eventName) { 52 | List streams = sStreamPool[eventName]; 53 | if (streams != null) { 54 | for (StreamSubscription item in streams) { 55 | item.cancel(); 56 | } 57 | streams.clear(); 58 | } 59 | } 60 | 61 | //订阅取消 62 | static void cancel(String eventName, StreamSubscription subscription) { 63 | if (subscription == null) return; 64 | List streams = sStreamPool[eventName]; 65 | if (streams != null) { 66 | subscription.cancel(); 67 | streams.remove(subscription); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/core/utils/locale.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:devicelocale/devicelocale.dart'; 4 | 5 | class LocaleUtils { 6 | 7 | /// 内部构造方法,可避免外部暴露构造函数,进行实例化 8 | LocaleUtils._internal(); 9 | 10 | static Locale _systemLocale; 11 | 12 | static Future init() async { 13 | _systemLocale = await getSystemLocaleAsync(); 14 | } 15 | 16 | static Locale getSystemLocale() { 17 | return _systemLocale; 18 | } 19 | 20 | static Future getSystemLocaleAsync() async { 21 | String locale = await Devicelocale.currentLocale; 22 | var array = locale.split("_"); 23 | if (array.length > 1) { 24 | return Locale(array[0], array[1]); 25 | } 26 | return null; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/core/utils/path.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:path_provider/path_provider.dart'; 3 | 4 | ///文件路径工具类 5 | class PathUtils { 6 | PathUtils._internal(); 7 | 8 | ///获取缓存目录路径 9 | static Future getCacheDirPath() async { 10 | Directory directory = await getTemporaryDirectory(); 11 | return directory.path; 12 | } 13 | 14 | ///获取文件缓存目录路径 15 | static Future getFilesDirPath() async { 16 | Directory directory = await getApplicationSupportDirectory(); 17 | return directory.path; 18 | } 19 | 20 | ///获取文档存储目录路径 21 | static Future getDocumentsDirPath() async { 22 | Directory directory = await getApplicationDocumentsDirectory(); 23 | return directory.path; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/core/utils/privacy.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/gestures.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_template/generated/i18n.dart'; 6 | import 'package:flutter_template/router/router.dart'; 7 | import 'utils.dart'; 8 | 9 | //隐私弹窗工具 10 | class PrivacyUtils { 11 | PrivacyUtils._internal(); 12 | 13 | //隐私服务政策地址 14 | static const PRIVACY_URL = 15 | 'https://gitee.com/xuexiangjys/flutter_template/raw/master/LICENSE'; 16 | 17 | static void showPrivacyDialog(BuildContext context, 18 | {VoidCallback onAgressCallback}) { 19 | Utils.getPackageInfo().then((packageInfo) { 20 | showDialog( 21 | context: context, 22 | barrierDismissible: false, 23 | builder: (BuildContext context) { 24 | return AlertDialog( 25 | title: Text(I18n.of(context).reminder), 26 | content: SingleChildScrollView( 27 | child: ListBody( 28 | children: [ 29 | Text(I18n.of(context).welcome(packageInfo.appName)), 30 | SizedBox(height: 5), 31 | Text(I18n.of(context).welcome1), 32 | SizedBox(height: 5), 33 | Text.rich(TextSpan(children: [ 34 | TextSpan(text: I18n.of(context).welcome2), 35 | TextSpan( 36 | text: I18n.of(context).privacyName(packageInfo.appName), 37 | style: TextStyle(color: Theme.of(context).primaryColor), 38 | recognizer: TapGestureRecognizer() 39 | ..onTap = () { 40 | XRouter.goWeb( 41 | PRIVACY_URL, 42 | I18n.of(context) 43 | .privacyName(packageInfo.appName)); 44 | }), 45 | TextSpan(text: I18n.of(context).welcome3), 46 | ])), 47 | SizedBox(height: 5), 48 | Text.rich(TextSpan(children: [ 49 | TextSpan(text: I18n.of(context).welcome4), 50 | TextSpan( 51 | text: I18n.of(context).privacyName(packageInfo.appName), 52 | style: TextStyle(color: Theme.of(context).primaryColor), 53 | recognizer: TapGestureRecognizer() 54 | ..onTap = () { 55 | XRouter.goWeb( 56 | PRIVACY_URL, 57 | I18n.of(context) 58 | .privacyName(packageInfo.appName)); 59 | }), 60 | TextSpan(text: I18n.of(context).welcome5), 61 | ])), 62 | ], 63 | ), 64 | ), 65 | actions: [ 66 | TextButton( 67 | child: Text(I18n.of(context).disagree), 68 | onPressed: () { 69 | Navigator.of(context).pop(); 70 | showPrivacySecond(context, 71 | onAgressCallback: onAgressCallback); 72 | }, 73 | ), 74 | TextButton( 75 | child: Text(I18n.of(context).agree), 76 | onPressed: onAgressCallback == null 77 | ? () { 78 | Navigator.of(context).pop(); 79 | } 80 | : onAgressCallback, 81 | ), 82 | ], 83 | ); 84 | }, 85 | ); 86 | }); 87 | } 88 | 89 | ///第二次提醒 90 | static void showPrivacySecond(BuildContext context, 91 | {VoidCallback onAgressCallback}) { 92 | Utils.getPackageInfo().then((packageInfo) { 93 | showDialog( 94 | context: context, 95 | barrierDismissible: false, 96 | builder: (BuildContext context) { 97 | return AlertDialog( 98 | title: Text(I18n.of(context).reminder), 99 | content: SingleChildScrollView( 100 | child: ListBody( 101 | children: [ 102 | Text(I18n.of(context) 103 | .privacyExplainAgain(packageInfo.appName)), 104 | ], 105 | ), 106 | ), 107 | actions: [ 108 | TextButton( 109 | child: Text(I18n.of(context).stillDisagree), 110 | onPressed: () { 111 | Navigator.of(context).pop(); 112 | showPrivacyThird(context, onAgressCallback: onAgressCallback); 113 | }, 114 | ), 115 | TextButton( 116 | child: Text(I18n.of(context).lookAgain), 117 | onPressed: () { 118 | Navigator.of(context).pop(); 119 | showPrivacyDialog(context, 120 | onAgressCallback: onAgressCallback); 121 | }, 122 | ), 123 | ], 124 | ); 125 | }, 126 | ); 127 | }); 128 | } 129 | 130 | ///第三次提醒 131 | static void showPrivacyThird(BuildContext context, 132 | {VoidCallback onAgressCallback}) { 133 | Utils.getPackageInfo().then((packageInfo) { 134 | showDialog( 135 | context: context, 136 | barrierDismissible: false, 137 | builder: (BuildContext context) { 138 | return AlertDialog( 139 | content: SingleChildScrollView( 140 | child: ListBody( 141 | children: [ 142 | Text(I18n.of(context).thinkAboutItAgain), 143 | ], 144 | ), 145 | ), 146 | actions: [ 147 | TextButton( 148 | child: Text(I18n.of(context).exitApp), 149 | onPressed: () { 150 | //退出程序 151 | // SystemNavigator.pop(); 152 | exit(0); 153 | }, 154 | ), 155 | TextButton( 156 | child: Text(I18n.of(context).lookAgain), 157 | onPressed: () { 158 | Navigator.of(context).pop(); 159 | showPrivacyDialog(context, 160 | onAgressCallback: onAgressCallback); 161 | }, 162 | ), 163 | ], 164 | ); 165 | }, 166 | ); 167 | }); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /lib/core/utils/toast.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:oktoast/oktoast.dart'; 3 | 4 | const DEFAULT_TOAST_DURATION = Duration(seconds: 2); 5 | const DEFAULT_TOAST_COLOR = Color(0xFF424242); 6 | 7 | class ToastUtils { 8 | ToastUtils._internal(); 9 | 10 | ///全局初始化Toast配置, child为MaterialApp 11 | static init(Widget child) { 12 | return OKToast( 13 | ///字体大小 14 | textStyle: TextStyle(fontSize: 16, color: Colors.white), 15 | backgroundColor: DEFAULT_TOAST_COLOR, 16 | radius: 10, 17 | dismissOtherOnShow: true, 18 | textPadding: EdgeInsets.fromLTRB(20, 10, 20, 10), 19 | child: child, 20 | duration: DEFAULT_TOAST_DURATION, 21 | ); 22 | } 23 | 24 | static void toast(String msg, 25 | {Duration duration = DEFAULT_TOAST_DURATION, 26 | Color color = DEFAULT_TOAST_COLOR}) { 27 | showToast(msg, duration: duration, backgroundColor: color); 28 | } 29 | 30 | static void waring(String msg, {Duration duration = DEFAULT_TOAST_DURATION}) { 31 | showToast(msg, duration: duration, backgroundColor: Colors.yellow); 32 | } 33 | 34 | static void error(String msg, {Duration duration = DEFAULT_TOAST_DURATION}) { 35 | showToast(msg, duration: duration, backgroundColor: Colors.red); 36 | } 37 | 38 | static void success(String msg, 39 | {Duration duration = DEFAULT_TOAST_DURATION}) { 40 | showToast(msg, duration: duration, backgroundColor: Colors.lightGreen); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/core/utils/utils.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:date_format/date_format.dart'; 3 | import 'package:package_info/package_info.dart'; 4 | import 'package:url_launcher/url_launcher.dart'; 5 | 6 | import 'toast.dart'; 7 | 8 | ///常用工具类 9 | class Utils { 10 | Utils._internal(); 11 | 12 | //=============url_launcher==================// 13 | 14 | ///处理链接 15 | static void launchURL(String url) async { 16 | if (await canLaunch(url)) { 17 | await launch(url); 18 | } else { 19 | ToastUtils.error("暂不能处理这条链接:$url"); 20 | } 21 | } 22 | 23 | //=============package_info==================// 24 | 25 | ///获取应用包信息 26 | static Future getPackageInfo() { 27 | return PackageInfo.fromPlatform(); 28 | } 29 | 30 | ///获取应用包信息 31 | static Future> getPackageInfoMap() async { 32 | PackageInfo packageInfo = await PackageInfo.fromPlatform(); 33 | return { 34 | 'appName': packageInfo.appName, 35 | 'packageName': packageInfo.packageName, 36 | 'version': packageInfo.version, 37 | 'buildNumber': packageInfo.buildNumber, 38 | }; 39 | } 40 | 41 | //=============date_format==================// 42 | 43 | static String timestamp() => DateTime.now().millisecondsSinceEpoch.toString(); 44 | 45 | static String formatDateTime(DateTime dateTime) => 46 | formatDate(dateTime, [yyyy, '-', mm, '-', dd]); 47 | 48 | } 49 | -------------------------------------------------------------------------------- /lib/core/utils/xuifont.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | //自定义字体库 4 | class XUIIcons { 5 | XUIIcons._internal(); 6 | 7 | static const List icons = [ 8 | file, 9 | chat, 10 | voice, 11 | delete, 12 | delete1, 13 | delete2, 14 | delete3, 15 | back, 16 | back1, 17 | add, 18 | add1, 19 | add2, 20 | reset, 21 | complete, 22 | complete1, 23 | collect, 24 | emoj, 25 | boy, 26 | girl, 27 | eye_on, 28 | eye_off, 29 | logout 30 | ]; 31 | 32 | static const IconData file = 33 | const IconData(0xe600, fontFamily: 'xuifont', matchTextDirection: true); 34 | static const IconData chat = 35 | const IconData(0xe601, fontFamily: 'xuifont', matchTextDirection: true); 36 | static const IconData voice = 37 | const IconData(0xe602, fontFamily: 'xuifont', matchTextDirection: true); 38 | static const IconData delete = 39 | const IconData(0xe603, fontFamily: 'xuifont', matchTextDirection: true); 40 | static const IconData delete1 = 41 | const IconData(0xe613, fontFamily: 'xuifont', matchTextDirection: true); 42 | static const IconData delete2 = 43 | const IconData(0xe630, fontFamily: 'xuifont', matchTextDirection: true); 44 | static const IconData delete3 = 45 | const IconData(0xe658, fontFamily: 'xuifont', matchTextDirection: true); 46 | static const IconData back = 47 | const IconData(0xe609, fontFamily: 'xuifont', matchTextDirection: true); 48 | static const IconData back1 = 49 | const IconData(0xe614, fontFamily: 'xuifont', matchTextDirection: true); 50 | static const IconData add = 51 | const IconData(0xe612, fontFamily: 'xuifont', matchTextDirection: true); 52 | static const IconData add1 = 53 | const IconData(0xe615, fontFamily: 'xuifont', matchTextDirection: true); 54 | static const IconData add2 = 55 | const IconData(0xe631, fontFamily: 'xuifont', matchTextDirection: true); 56 | static const IconData reset = 57 | const IconData(0xe616, fontFamily: 'xuifont', matchTextDirection: true); 58 | static const IconData complete = 59 | const IconData(0xe650, fontFamily: 'xuifont', matchTextDirection: true); 60 | static const IconData complete1 = 61 | const IconData(0xe673, fontFamily: 'xuifont', matchTextDirection: true); 62 | static const IconData collect = 63 | const IconData(0xe77f, fontFamily: 'xuifont', matchTextDirection: true); 64 | static const IconData emoj = 65 | const IconData(0xe628, fontFamily: 'xuifont', matchTextDirection: true); 66 | static const IconData boy = 67 | const IconData(0xe6c5, fontFamily: 'xuifont', matchTextDirection: true); 68 | static const IconData girl = 69 | const IconData(0xe61b, fontFamily: 'xuifont', matchTextDirection: true); 70 | static const IconData eye_on = 71 | const IconData(0xe632, fontFamily: 'xuifont', matchTextDirection: true); 72 | static const IconData eye_off = 73 | const IconData(0xe749, fontFamily: 'xuifont', matchTextDirection: true); 74 | static const IconData logout = 75 | const IconData(0xe723, fontFamily: 'xuifont', matchTextDirection: true); 76 | } 77 | -------------------------------------------------------------------------------- /lib/core/utils/xupdate.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter_template/core/utils/toast.dart'; 4 | import 'package:flutter_xupdate/flutter_xupdate.dart'; 5 | 6 | ///版本更新工具 7 | class XUpdate { 8 | XUpdate._internal(); 9 | 10 | ///版本更新检查的地址 11 | static const String UPDATE_URL = ""; 12 | 13 | static void initAndCheck() { 14 | if (Platform.isAndroid) { 15 | init(url: UPDATE_URL); 16 | } 17 | } 18 | 19 | ///初始化XUpdate 20 | static void init({String url = ""}) { 21 | FlutterXUpdate.init( 22 | debug: true, 23 | enableRetry: true, 24 | retryContent: "Github下载速度太慢了,是否考虑切换蒲公英下载?", 25 | retryUrl: "https://www.pgyer.com/flutter_learn") 26 | .then((_result) { 27 | if (url.isNotEmpty) { 28 | checkUpdate(url); 29 | } 30 | }); 31 | FlutterXUpdate.setErrorHandler( 32 | onUpdateError: (Map message) async { 33 | ///2004是无最新版本 34 | if (message['code'] != 2004) { 35 | ///4000是下载失败 36 | if (message['code'] == 4000) { 37 | FlutterXUpdate.showRetryUpdateTipDialog( 38 | retryContent: "Github被墙无法继续下载,是否考虑切换蒲公英下载?", 39 | retryUrl: "https://www.pgyer.com/flutter_learn"); 40 | } else { 41 | ToastUtils.error(message['detailMsg']); 42 | } 43 | } 44 | }); 45 | } 46 | 47 | ///初始化XUpdate 48 | static void checkUpdate(String url) { 49 | if (url != null && url.isNotEmpty) { 50 | FlutterXUpdate.checkUpdate(url: url, widthRatio: 0.7); 51 | } 52 | } 53 | 54 | ///初始化XUpdate 55 | static void checkUpdateWithErrorTip({String url = UPDATE_URL}) { 56 | FlutterXUpdate.setErrorHandler( 57 | onUpdateError: (Map message) async { 58 | ///4000是下载失败 59 | if (message['code'] == 4000) { 60 | FlutterXUpdate.showRetryUpdateTipDialog( 61 | retryContent: "Github被墙无法继续下载,是否考虑切换蒲公英下载?", 62 | retryUrl: "https://www.pgyer.com/flutter_learn"); 63 | } else { 64 | ToastUtils.error(message['message']); 65 | } 66 | }); 67 | checkUpdate(url); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/core/widget/grid/grid_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// 列表项 4 | class GridItem extends StatelessWidget { 5 | // 文字 6 | final String title; 7 | // 颜色 8 | final Color color; 9 | //是否可点击 10 | final bool enabled; 11 | //点击事件 12 | final GestureTapCallback onTap; 13 | 14 | // 构造函数 15 | const GridItem( 16 | {Key key, this.title, this.color, this.enabled = true, this.onTap}) 17 | : super(key: key); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return InkWell( 22 | onTap: enabled ? onTap : null, 23 | child: Padding( 24 | padding: EdgeInsets.only(top: 8), 25 | child: Column(children: [ 26 | ClipOval( 27 | child: Container( 28 | alignment: Alignment.center, 29 | color: color, 30 | child: Text(title.substring(0, 1), 31 | style: TextStyle(color: Colors.white, fontSize: 16)), 32 | width: 40, 33 | height: 40, 34 | ), 35 | ), 36 | Padding(padding: const EdgeInsets.only(top: 10), child: Text(title, style: TextStyle(fontSize: 14))) 37 | ]), 38 | )); 39 | } 40 | } 41 | 42 | class ActionItem { 43 | final String title; 44 | final Color color; 45 | const ActionItem(this.title, this.color); 46 | } 47 | -------------------------------------------------------------------------------- /lib/core/widget/list/article_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_template/router/router.dart'; 4 | 5 | /// 资讯列表详情 6 | class ArticleListItem extends StatelessWidget { 7 | //文章地址 8 | final String articleUrl; 9 | 10 | //图片地址 11 | final String imageUrl; 12 | 13 | //文章标题 14 | final String title; 15 | 16 | //文章作者 17 | final String author; 18 | 19 | //作者描述 20 | final String description; 21 | 22 | //文章摘要 23 | final String summary; 24 | 25 | const ArticleListItem( 26 | {Key key, 27 | this.articleUrl = '', 28 | this.imageUrl = '', 29 | this.title = '这里是标题', 30 | this.author = '作者', 31 | this.description = 'xxxx', 32 | this.summary = 33 | 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'}) 34 | : super(key: key); 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | return InkWell( 39 | onTap: () { 40 | XRouter.goWeb(articleUrl, title); 41 | }, 42 | child: Card( 43 | clipBehavior: Clip.antiAlias, 44 | elevation: 4, 45 | shape: 46 | RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)), 47 | margin: EdgeInsets.symmetric(horizontal: 16, vertical: 6), 48 | child: Container( 49 | child: Row( 50 | children: [ 51 | Container( 52 | padding: EdgeInsets.only(left: 10, right: 5), 53 | width: 100.0, 54 | child: AspectRatio( 55 | aspectRatio: 1.2, 56 | child: CachedNetworkImage( 57 | fit: BoxFit.fill, 58 | placeholder: (context, url) => Container( 59 | color: Colors.grey[200], 60 | ), 61 | imageUrl: imageUrl), 62 | ), 63 | ), 64 | Expanded( 65 | flex: 1, 66 | child: Container( 67 | padding: 68 | EdgeInsets.symmetric(horizontal: 5, vertical: 10), 69 | color: Colors.white, 70 | child: Column( 71 | children: [ 72 | Row( 73 | children: [ 74 | Expanded( 75 | child: Column( 76 | crossAxisAlignment: CrossAxisAlignment.start, 77 | children: [ 78 | Container( 79 | child: Text(title, 80 | overflow: TextOverflow.ellipsis, 81 | style: TextStyle( 82 | color: Colors.black, fontSize: 15)), 83 | ), 84 | Container( 85 | margin: EdgeInsets.only(top: 2.0), 86 | child: Text( 87 | '$author $description', 88 | style: TextStyle( 89 | fontSize: 12, color: Colors.grey), 90 | ), 91 | ), 92 | ], 93 | )), 94 | ], 95 | ), 96 | SizedBox( 97 | height: 4, 98 | ), 99 | Text( 100 | summary, 101 | maxLines: 3, 102 | style: 103 | TextStyle(fontSize: 12, color: Colors.black87), 104 | overflow: TextOverflow.ellipsis, 105 | ) 106 | ], 107 | )), 108 | ), 109 | ], 110 | ), 111 | ), 112 | )); 113 | } 114 | } 115 | 116 | class ArticleInfo { 117 | //文章地址 118 | final String articleUrl; 119 | 120 | //图片地址 121 | final String imageUrl; 122 | 123 | //文章标题 124 | final String title; 125 | 126 | //文章作者 127 | final String author; 128 | 129 | //作者描述 130 | final String description; 131 | 132 | //文章摘要 133 | final String summary; 134 | 135 | const ArticleInfo(this.articleUrl, this.imageUrl, this.title, this.summary, 136 | {this.author = 'xuexiang', this.description = 'Android架构师'}); 137 | } 138 | -------------------------------------------------------------------------------- /lib/core/widget/list/list_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// 列表项 4 | class ListItem extends StatefulWidget { 5 | // 点击事件 6 | final VoidCallback onPressed; 7 | 8 | // 图标 9 | final Widget icon; 10 | 11 | // 标题 12 | final String title; 13 | final Color titleColor; 14 | 15 | // 描述 16 | final String describe; 17 | final double describeSpace; 18 | final Color describeColor; 19 | 20 | // 右侧控件 21 | final Widget rightWidget; 22 | 23 | // 构造函数 24 | ListItem({ 25 | Key key, 26 | this.onPressed, 27 | this.icon, 28 | this.title, 29 | this.titleColor: Colors.black, 30 | this.describe, 31 | this.describeSpace: 3.0, 32 | this.describeColor: Colors.grey, 33 | this.rightWidget, 34 | }) : super(key: key); 35 | 36 | @override 37 | _ListItemState createState() => _ListItemState(); 38 | } 39 | 40 | class _ListItemState extends State { 41 | @override 42 | Widget build(BuildContext context) { 43 | return TextButton( 44 | onPressed: widget.onPressed, 45 | child: Container( 46 | height: 60.0, 47 | width: double.infinity, 48 | child: Row( 49 | children: [ 50 | widget.icon != null 51 | ? Container( 52 | padding: EdgeInsets.all(14.0), 53 | child: SizedBox( 54 | height: 32.0, 55 | width: 32.0, 56 | child: widget.icon, 57 | ), 58 | ) 59 | : Container( 60 | width: 14.0, 61 | ), 62 | Expanded( 63 | flex: 1, 64 | child: Column( 65 | mainAxisAlignment: MainAxisAlignment.center, 66 | crossAxisAlignment: CrossAxisAlignment.start, 67 | children: [ 68 | widget.title != null 69 | ? Text( 70 | widget.title, 71 | style: TextStyle( 72 | color: widget.titleColor, 73 | fontSize: 14.0, 74 | fontWeight: FontWeight.bold, 75 | ), 76 | ) 77 | : Container(), 78 | widget.describe != null 79 | ? Padding( 80 | padding: EdgeInsets.only(top: widget.describeSpace), 81 | child: Text( 82 | widget.describe, 83 | maxLines: 2, 84 | style: TextStyle( 85 | color: widget.describeColor, fontSize: 12.0), 86 | )) 87 | : Container(), 88 | ], 89 | ), 90 | ), 91 | widget.rightWidget == null ? Container() : widget.rightWidget, 92 | Container( 93 | width: 14.0, 94 | ), 95 | ], 96 | )), 97 | ); 98 | } 99 | } 100 | 101 | /// 空图标 102 | class EmptyIcon extends Icon { 103 | const EmptyIcon() : super(Icons.hourglass_empty); 104 | 105 | @override 106 | Widget build(BuildContext context) { 107 | return Container(); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /lib/core/widget/list/sample_list_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// 简单列表项 4 | class SampleListItem extends StatelessWidget { 5 | /// 方向 6 | final Axis direction; 7 | 8 | /// 宽度 9 | final double width; 10 | 11 | const SampleListItem({ 12 | Key key, 13 | this.direction = Axis.vertical, 14 | this.width = double.infinity, 15 | }) : super(key: key); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return direction == Axis.vertical 20 | ? Card( 21 | margin: EdgeInsets.symmetric(horizontal: 16, vertical: 4), 22 | child: Container( 23 | child: Row( 24 | children: [ 25 | Container( 26 | height: 100.0, 27 | child: AspectRatio( 28 | aspectRatio: 1.0, 29 | child: Container( 30 | color: Colors.grey[200], 31 | ), 32 | ), 33 | ), 34 | Expanded( 35 | flex: 1, 36 | child: Container( 37 | padding: EdgeInsets.all( 38 | 10.0, 39 | ), 40 | color: Colors.white, 41 | child: Column( 42 | children: [ 43 | Row( 44 | children: [ 45 | Column( 46 | crossAxisAlignment: CrossAxisAlignment.start, 47 | children: [ 48 | Container( 49 | width: 120.0, 50 | height: 15.0, 51 | color: Colors.grey[200], 52 | ), 53 | Container( 54 | width: 60.0, 55 | height: 10.0, 56 | margin: EdgeInsets.only(top: 8.0), 57 | color: Colors.grey[200], 58 | ), 59 | ], 60 | ), 61 | Expanded( 62 | flex: 1, 63 | child: SizedBox(), 64 | ), 65 | Icon( 66 | Icons.star, 67 | color: Colors.grey[200], 68 | ) 69 | ], 70 | ), 71 | SizedBox( 72 | height: 8.0, 73 | ), 74 | Column( 75 | crossAxisAlignment: CrossAxisAlignment.start, 76 | children: [ 77 | Container( 78 | height: 10.0, 79 | color: Colors.grey[200], 80 | ), 81 | SizedBox( 82 | height: 4.0, 83 | ), 84 | Container( 85 | height: 10.0, 86 | color: Colors.grey[200], 87 | ), 88 | SizedBox( 89 | height: 4.0, 90 | ), 91 | Container( 92 | height: 10.0, 93 | width: 150.0, 94 | color: Colors.grey[200], 95 | ), 96 | ], 97 | ), 98 | ], 99 | )), 100 | ), 101 | ], 102 | ), 103 | ), 104 | ) 105 | : Card( 106 | margin: EdgeInsets.symmetric(horizontal: 8, vertical: 8), 107 | child: Container( 108 | alignment: Alignment.center, 109 | child: Column( 110 | children: [ 111 | Container( 112 | height: 100.0, 113 | width: width, 114 | color: Colors.grey[200], 115 | ), 116 | Container( 117 | width: width, 118 | padding: EdgeInsets.all(10.0), 119 | child: Column( 120 | children: [ 121 | Row( 122 | children: [ 123 | Column( 124 | crossAxisAlignment: CrossAxisAlignment.start, 125 | children: [ 126 | Container( 127 | width: 80.0, 128 | height: 15.0, 129 | color: Colors.grey[200], 130 | ), 131 | Container( 132 | width: 60.0, 133 | height: 10.0, 134 | margin: EdgeInsets.only(top: 8.0), 135 | color: Colors.grey[200], 136 | ), 137 | ], 138 | ), 139 | Expanded( 140 | flex: 1, 141 | child: SizedBox(), 142 | ), 143 | Icon( 144 | Icons.star, 145 | color: Colors.grey[200], 146 | ) 147 | ], 148 | ), 149 | SizedBox( 150 | height: 8.0, 151 | ), 152 | Column( 153 | crossAxisAlignment: CrossAxisAlignment.start, 154 | children: [ 155 | Container( 156 | height: 10.0, 157 | color: Colors.grey[200], 158 | ), 159 | SizedBox( 160 | height: 4.0, 161 | ), 162 | Container( 163 | height: 10.0, 164 | color: Colors.grey[200], 165 | ), 166 | SizedBox( 167 | height: 4.0, 168 | ), 169 | Container( 170 | height: 10.0, 171 | width: 100.0, 172 | color: Colors.grey[200], 173 | ), 174 | ], 175 | ), 176 | ], 177 | ), 178 | ), 179 | ], 180 | ), 181 | ), 182 | ); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /lib/core/widget/loading_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | //loading加载框 4 | class LoadingDialog extends Dialog { 5 | //loading动画 6 | final Widget loadingView; 7 | //提示内容 8 | final String content; 9 | //是否显示提示文字 10 | final bool showContent; 11 | //圆角大小 12 | final double radius; 13 | //背景颜色 14 | final Color backgroundColor; 15 | 16 | LoadingDialog( 17 | {Key key, 18 | this.loadingView, 19 | this.content = "加载中...", 20 | this.showContent = true, 21 | this.radius = 10, 22 | this.backgroundColor = Colors.white}) 23 | : super(key: key); 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return Material( 28 | type: MaterialType.transparency, 29 | child: Center( 30 | child: SizedBox( 31 | width: showContent ? 120 : 80, 32 | height: showContent ? 120 : 80, 33 | child: Container( 34 | decoration: ShapeDecoration( 35 | color: backgroundColor, 36 | shape: RoundedRectangleBorder( 37 | borderRadius: BorderRadius.all( 38 | Radius.circular(radius), 39 | ), 40 | ), 41 | ), 42 | child: Column( 43 | mainAxisAlignment: MainAxisAlignment.center, 44 | crossAxisAlignment: CrossAxisAlignment.center, 45 | children: [ 46 | loadingView == null ? CircularProgressIndicator() : loadingView, 47 | showContent 48 | ? Padding( 49 | padding: const EdgeInsets.only( 50 | top: 16, 51 | ), 52 | child: Text(content), 53 | ) 54 | : SizedBox(), 55 | ], 56 | ), 57 | ), 58 | ), 59 | ), 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/core/widget/web_view_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:core'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; 5 | import 'package:get/get.dart'; 6 | import 'package:share/share.dart'; 7 | 8 | class WebViewPage extends StatefulWidget { 9 | WebViewPage(); 10 | _WebViewPageState createState() => _WebViewPageState(); 11 | } 12 | 13 | class _WebViewPageState extends State { 14 | final GlobalKey _scaffoldKey = GlobalKey(); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | String url = Get.parameters['url']; 19 | return WebviewScaffold( 20 | url: url, 21 | withLocalStorage: true, 22 | withJavascript: true, 23 | hidden: true, 24 | key: _scaffoldKey, 25 | appBar: AppBar( 26 | title: Text(Get.parameters['title'], style: TextStyle(fontSize: 15)), 27 | titleSpacing: 0, 28 | actions: [ 29 | IconButton( 30 | icon: Icon(Icons.share), 31 | onPressed: () { 32 | Share.share(url); 33 | }), 34 | ], 35 | ), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/generated/i18n.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter/material.dart'; 5 | // ignore_for_file: non_constant_identifier_names 6 | // ignore_for_file: camel_case_types 7 | // ignore_for_file: prefer_single_quotes 8 | // ignore_for_file: unnecessary_brace_in_string_interps 9 | 10 | //WARNING: This file is automatically generated. DO NOT EDIT, all your changes would be lost. 11 | 12 | typedef LocaleChangeCallback = void Function(Locale locale); 13 | 14 | class I18n implements WidgetsLocalizations { 15 | const I18n(); 16 | static Locale _locale; 17 | static bool _shouldReload = false; 18 | 19 | static set locale(Locale newLocale) { 20 | _shouldReload = true; 21 | I18n._locale = newLocale; 22 | } 23 | 24 | static const GeneratedLocalizationsDelegate delegate = GeneratedLocalizationsDelegate(); 25 | 26 | /// function to be invoked when changing the language 27 | static LocaleChangeCallback onLocaleChanged; 28 | 29 | static I18n of(BuildContext context) => 30 | Localizations.of(context, WidgetsLocalizations); 31 | 32 | @override 33 | TextDirection get textDirection => TextDirection.ltr; 34 | 35 | /// "Flutter Template" 36 | String get title => "Flutter Template"; 37 | /// "Login" 38 | String get login => "Login"; 39 | /// "Logout" 40 | String get logout => "Logout"; 41 | /// "LoginName" 42 | String get loginName => "LoginName"; 43 | /// "Please enter your login name or email" 44 | String get loginNameHint => "Please enter your login name or email"; 45 | /// "LoginName cannot be empty!" 46 | String get loginNameError => "LoginName cannot be empty!"; 47 | /// "Password" 48 | String get password => "Password"; 49 | /// "Please enter your password" 50 | String get passwordHint => "Please enter your password"; 51 | /// "Password cannot be less than 6 digits!" 52 | String get passwordError => "Password cannot be less than 6 digits!"; 53 | /// "Login Success" 54 | String get loginSuccess => "Login Success"; 55 | /// "Register" 56 | String get register => "Register"; 57 | /// "Repeat Password" 58 | String get repeatPassword => "Repeat Password"; 59 | /// "Register Success" 60 | String get registerSuccess => "Register Success"; 61 | /// "Settings" 62 | String get settings => "Settings"; 63 | /// "Theme" 64 | String get theme => "Theme"; 65 | /// "Language" 66 | String get language => "Language"; 67 | /// "Chinese" 68 | String get chinese => "Chinese"; 69 | /// "English" 70 | String get english => "English"; 71 | /// "Auto" 72 | String get auto => "Auto"; 73 | /// "About" 74 | String get about => "About"; 75 | /// "Version" 76 | String get versionName => "Version"; 77 | /// "Author" 78 | String get author => "Author"; 79 | /// "QQ Group" 80 | String get qqgroup => "QQ Group"; 81 | /// "AppUpdate" 82 | String get appupdate => "AppUpdate"; 83 | /// "Sponsor" 84 | String get sponsor => "Sponsor"; 85 | /// "Your reward is the motivation for me to maintain. I will make a list of all reward staff on GitHub as a voucher." 86 | String get sponsorDescription => "Your reward is the motivation for me to maintain. I will make a list of all reward staff on GitHub as a voucher."; 87 | /// "Home" 88 | String get home => "Home"; 89 | /// "Category" 90 | String get category => "Category"; 91 | /// "Activity" 92 | String get activity => "Activity"; 93 | /// "Message" 94 | String get message => "Message"; 95 | /// "Profile" 96 | String get profile => "Profile"; 97 | /// "Reminder" 98 | String get reminder => "Reminder"; 99 | /// "Agree" 100 | String get agree => "Agree"; 101 | /// "Disagree" 102 | String get disagree => "Disagree"; 103 | /// "Look Again" 104 | String get lookAgain => "Look Again"; 105 | /// "Still Disagree" 106 | String get stillDisagree => "Still Disagree"; 107 | /// " Do you want to think about it again?" 108 | String get thinkAboutItAgain => " Do you want to think about it again?"; 109 | /// " We attach great importance to the protection of your personal information and promise to protect and process your information in strict accordance with the 《${appName} privacy policy》. If you do not agree with the policy, we regret that we will not be able to provide you with services." 110 | String privacyExplainAgain(String appName) => " We attach great importance to the protection of your personal information and promise to protect and process your information in strict accordance with the 《${appName} privacy policy》. If you do not agree with the policy, we regret that we will not be able to provide you with services."; 111 | /// "Exit App" 112 | String get exitApp => "Exit App"; 113 | /// "《${appName} privacy policy》" 114 | String privacyName(String appName) => "《${appName} privacy policy》"; 115 | /// " Welcome to ${appName}!" 116 | String welcome(String appName) => " Welcome to ${appName}!"; 117 | /// " We know the importance of personal information to you and thank you for your trust in us." 118 | String get welcome1 => " We know the importance of personal information to you and thank you for your trust in us."; 119 | /// " In order to better protect your rights and interests and comply with the relevant regulatory requirements, we will explain to you through " 120 | String get welcome2 => " In order to better protect your rights and interests and comply with the relevant regulatory requirements, we will explain to you through "; 121 | /// " how we will collect, store, protect, use and provide your information to the outside world, and explain your rights." 122 | String get welcome3 => " how we will collect, store, protect, use and provide your information to the outside world, and explain your rights."; 123 | /// " For more details, please refer to" 124 | String get welcome4 => " For more details, please refer to"; 125 | /// " the full text." 126 | String get welcome5 => " the full text."; 127 | /// "Privacy agreement agreed!" 128 | String get agreePrivacy => "Privacy agreement agreed!"; 129 | /// "Dark Theme" 130 | String get darkTheme => "Dark Theme"; 131 | } 132 | 133 | class _I18n_en_US extends I18n { 134 | const _I18n_en_US(); 135 | 136 | @override 137 | TextDirection get textDirection => TextDirection.ltr; 138 | } 139 | 140 | class _I18n_zh_CN extends I18n { 141 | const _I18n_zh_CN(); 142 | 143 | /// "Flutter模版工程" 144 | @override 145 | String get title => "Flutter模版工程"; 146 | /// "登录" 147 | @override 148 | String get login => "登录"; 149 | /// "登出" 150 | @override 151 | String get logout => "登出"; 152 | /// "用户名" 153 | @override 154 | String get loginName => "用户名"; 155 | /// "请输入您的用户名或邮箱" 156 | @override 157 | String get loginNameHint => "请输入您的用户名或邮箱"; 158 | /// "用户名不能为空!" 159 | @override 160 | String get loginNameError => "用户名不能为空!"; 161 | /// "密码" 162 | @override 163 | String get password => "密码"; 164 | /// "请输入您的密码" 165 | @override 166 | String get passwordHint => "请输入您的密码"; 167 | /// "密码不能少于6位!" 168 | @override 169 | String get passwordError => "密码不能少于6位!"; 170 | /// "登录成功" 171 | @override 172 | String get loginSuccess => "登录成功"; 173 | /// "注册" 174 | @override 175 | String get register => "注册"; 176 | /// "重复密码" 177 | @override 178 | String get repeatPassword => "重复密码"; 179 | /// "注册成功" 180 | @override 181 | String get registerSuccess => "注册成功"; 182 | /// "设置" 183 | @override 184 | String get settings => "设置"; 185 | /// "主题" 186 | @override 187 | String get theme => "主题"; 188 | /// "语言" 189 | @override 190 | String get language => "语言"; 191 | /// "简体中文" 192 | @override 193 | String get chinese => "简体中文"; 194 | /// "英语" 195 | @override 196 | String get english => "英语"; 197 | /// "系统默认" 198 | @override 199 | String get auto => "系统默认"; 200 | /// "关于" 201 | @override 202 | String get about => "关于"; 203 | /// "版本号" 204 | @override 205 | String get versionName => "版本号"; 206 | /// "作者" 207 | @override 208 | String get author => "作者"; 209 | /// "QQ群" 210 | @override 211 | String get qqgroup => "QQ群"; 212 | /// "版本更新" 213 | @override 214 | String get appupdate => "版本更新"; 215 | /// "赞助" 216 | @override 217 | String get sponsor => "赞助"; 218 | /// "你的打赏是我维护的动力,我将会列出所有打赏人员的清单在Github上作为凭证." 219 | @override 220 | String get sponsorDescription => "你的打赏是我维护的动力,我将会列出所有打赏人员的清单在Github上作为凭证."; 221 | /// "主页" 222 | @override 223 | String get home => "主页"; 224 | /// "分类" 225 | @override 226 | String get category => "分类"; 227 | /// "活动" 228 | @override 229 | String get activity => "活动"; 230 | /// "消息" 231 | @override 232 | String get message => "消息"; 233 | /// "我的" 234 | @override 235 | String get profile => "我的"; 236 | /// "温馨提醒" 237 | @override 238 | String get reminder => "温馨提醒"; 239 | /// "同意" 240 | @override 241 | String get agree => "同意"; 242 | /// "不同意" 243 | @override 244 | String get disagree => "不同意"; 245 | /// "再次查看" 246 | @override 247 | String get lookAgain => "再次查看"; 248 | /// "仍不同意" 249 | @override 250 | String get stillDisagree => "仍不同意"; 251 | /// " 要不要再想想?" 252 | @override 253 | String get thinkAboutItAgain => " 要不要再想想?"; 254 | /// " 我们非常重视对你个人信息的保护,承诺严格按照《${appName}隐私权政策》保护及处理你的信息。如果你不同意该政策,很遗憾我们将无法为你提供服务。" 255 | @override 256 | String privacyExplainAgain(String appName) => " 我们非常重视对你个人信息的保护,承诺严格按照《${appName}隐私权政策》保护及处理你的信息。如果你不同意该政策,很遗憾我们将无法为你提供服务。"; 257 | /// "退出应用" 258 | @override 259 | String get exitApp => "退出应用"; 260 | /// "《${appName}隐私权政策》" 261 | @override 262 | String privacyName(String appName) => "《${appName}隐私权政策》"; 263 | /// " 欢迎来到${appName}!" 264 | @override 265 | String welcome(String appName) => " 欢迎来到${appName}!"; 266 | /// " 我们深知个人信息对你的重要性,也感谢你对我们的信任。" 267 | @override 268 | String get welcome1 => " 我们深知个人信息对你的重要性,也感谢你对我们的信任。"; 269 | /// " 为了更好地保护你的权益,同时遵守相关监管的要求,我们将通过" 270 | @override 271 | String get welcome2 => " 为了更好地保护你的权益,同时遵守相关监管的要求,我们将通过"; 272 | /// "向你说明我们会如何收集、存储、保护、使用及对外提供你的信息,并说明你享有的权利。" 273 | @override 274 | String get welcome3 => "向你说明我们会如何收集、存储、保护、使用及对外提供你的信息,并说明你享有的权利。"; 275 | /// " 更多详情,敬请查阅" 276 | @override 277 | String get welcome4 => " 更多详情,敬请查阅"; 278 | /// "全文。" 279 | @override 280 | String get welcome5 => "全文。"; 281 | /// "已同意隐私协议!" 282 | @override 283 | String get agreePrivacy => "已同意隐私协议!"; 284 | /// "深色主题" 285 | @override 286 | String get darkTheme => "深色主题"; 287 | 288 | @override 289 | TextDirection get textDirection => TextDirection.ltr; 290 | } 291 | 292 | class GeneratedLocalizationsDelegate extends LocalizationsDelegate { 293 | const GeneratedLocalizationsDelegate(); 294 | List get supportedLocales { 295 | return const [ 296 | Locale("en", "US"), 297 | Locale("zh", "CN") 298 | ]; 299 | } 300 | 301 | LocaleResolutionCallback resolution({Locale fallback}) { 302 | return (Locale locale, Iterable supported) { 303 | if (isSupported(locale)) { 304 | return locale; 305 | } 306 | final Locale fallbackLocale = fallback ?? supported.first; 307 | return fallbackLocale; 308 | }; 309 | } 310 | 311 | @override 312 | Future load(Locale locale) { 313 | I18n._locale ??= locale; 314 | I18n._shouldReload = false; 315 | final String lang = I18n._locale != null ? I18n._locale.toString() : ""; 316 | final String languageCode = I18n._locale != null ? I18n._locale.languageCode : ""; 317 | if ("en_US" == lang) { 318 | return SynchronousFuture(const _I18n_en_US()); 319 | } 320 | else if ("zh_CN" == lang) { 321 | return SynchronousFuture(const _I18n_zh_CN()); 322 | } 323 | else if ("en" == languageCode) { 324 | return SynchronousFuture(const _I18n_en_US()); 325 | } 326 | else if ("zh" == languageCode) { 327 | return SynchronousFuture(const _I18n_zh_CN()); 328 | } 329 | 330 | return SynchronousFuture(const I18n()); 331 | } 332 | 333 | @override 334 | bool isSupported(Locale locale) { 335 | for (var i = 0; i < supportedLocales.length && locale != null; i++) { 336 | final l = supportedLocales[i]; 337 | if (l.languageCode == locale.languageCode) { 338 | return true; 339 | } 340 | } 341 | return false; 342 | } 343 | 344 | @override 345 | bool shouldReload(GeneratedLocalizationsDelegate old) => I18n._shouldReload; 346 | } -------------------------------------------------------------------------------- /lib/init/app_init.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'default_app.dart'; 5 | 6 | //应用初始化 7 | class AppInit { 8 | 9 | static void run() { 10 | //捕获异常 11 | catchException(() => DefaultApp.run()); 12 | } 13 | 14 | ///异常捕获处理 15 | static void catchException(T callback()) { 16 | //捕获异常的回调 17 | FlutterError.onError = (FlutterErrorDetails details) { 18 | reportErrorAndLog(details); 19 | }; 20 | runZoned>( 21 | () async { 22 | callback(); 23 | }, 24 | zoneSpecification: ZoneSpecification( 25 | print: (Zone self, ZoneDelegate parent, Zone zone, String line) { 26 | collectLog(parent, zone, line); // 收集日志 27 | }, 28 | ), 29 | //未捕获的异常的回调 30 | onError: (Object obj, StackTrace stack) { 31 | var details = makeDetails(obj, stack); 32 | reportErrorAndLog(details); 33 | }, 34 | ); 35 | } 36 | 37 | //日志拦截, 收集日志 38 | static void collectLog(ZoneDelegate parent, Zone zone, String line) { 39 | parent.print(zone, "日志拦截: $line"); 40 | } 41 | 42 | //上报错误和日志逻辑 43 | static void reportErrorAndLog(FlutterErrorDetails details) { 44 | print(details); 45 | } 46 | 47 | // 构建错误信息 48 | static FlutterErrorDetails makeDetails(Object obj, StackTrace stack) { 49 | return FlutterErrorDetails(stack: stack); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/init/default_app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_localizations/flutter_localizations.dart'; 3 | import 'package:flutter_template/core/http/http.dart'; 4 | import 'package:flutter_template/core/utils/locale.dart'; 5 | import 'package:flutter_template/core/utils/toast.dart'; 6 | import 'package:flutter_template/generated/i18n.dart'; 7 | import 'package:flutter_template/router/route_map.dart'; 8 | import 'package:flutter_template/utils/provider.dart'; 9 | import 'package:flutter_template/utils/sputils.dart'; 10 | import 'package:get/get.dart'; 11 | import 'package:provider/provider.dart'; 12 | 13 | //默认App的启动 14 | class DefaultApp { 15 | //运行app 16 | static void run() { 17 | WidgetsFlutterBinding.ensureInitialized(); 18 | initFirst().then((value) => runApp(Store.init(ToastUtils.init(MyApp())))); 19 | initApp(); 20 | } 21 | 22 | /// 必须要优先初始化的内容 23 | static Future initFirst() async { 24 | await SPUtils.init(); 25 | await LocaleUtils.init(); 26 | } 27 | 28 | /// 程序初始化操作 29 | static void initApp() { 30 | XHttp.init(); 31 | } 32 | } 33 | 34 | class MyApp extends StatelessWidget { 35 | @override 36 | Widget build(BuildContext context) { 37 | return Consumer2( 38 | builder: (context, appTheme, localeModel, _) { 39 | return GetMaterialApp( 40 | title: 'Flutter Project', 41 | theme: ThemeData( 42 | brightness: appTheme.brightness, 43 | primarySwatch: appTheme.themeColor, 44 | buttonColor: appTheme.themeColor, 45 | ), 46 | getPages: RouteMap.getPages, 47 | defaultTransition: Transition.rightToLeft, 48 | locale: localeModel.getLocale(), 49 | supportedLocales: I18n.delegate.supportedLocales, 50 | localizationsDelegates: [ 51 | I18n.delegate, 52 | GlobalMaterialLocalizations.delegate, 53 | GlobalWidgetsLocalizations.delegate, 54 | GlobalCupertinoLocalizations.delegate, 55 | ], 56 | localeResolutionCallback: 57 | (Locale _locale, Iterable supportedLocales) { 58 | if (localeModel.getLocale() != null) { 59 | //如果已经选定语言,则不跟随系统 60 | return localeModel.getLocale(); 61 | } else { 62 | //跟随系统 63 | Locale systemLocale = LocaleUtils.getSystemLocale(); 64 | if (I18n.delegate.isSupported(systemLocale)) { 65 | return systemLocale; 66 | } 67 | return supportedLocales.first; 68 | } 69 | }, 70 | ); 71 | }); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/init/splash.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_template/utils/sputils.dart'; 3 | import 'package:get/get.dart'; 4 | 5 | //类似广告启动页 6 | class SplashPage extends StatefulWidget { 7 | @override 8 | _SplashPageState createState() => _SplashPageState(); 9 | } 10 | 11 | class _SplashPageState extends State { 12 | @override 13 | void initState() { 14 | super.initState(); 15 | countDown(); 16 | } 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return Container( 21 | decoration: BoxDecoration( 22 | color: Colors.white, 23 | ), 24 | child: Center(child: FlutterLogo(size: 96))); 25 | } 26 | 27 | //倒计时 28 | void countDown() { 29 | var _duration = Duration(seconds: 2); 30 | new Future.delayed(_duration, goHomePage); 31 | } 32 | 33 | //页面跳转 34 | void goHomePage() { 35 | Get.offNamed(SPUtils.isLogined() ? '/home' : '/login'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | //程序的主入口 2 | import 'init/app_init.dart'; 3 | 4 | void main() => AppInit.run(); 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /lib/page/home/tab_home.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_easyrefresh/easy_refresh.dart'; 4 | import 'package:flutter_easyrefresh/material_footer.dart'; 5 | import 'package:flutter_easyrefresh/material_header.dart'; 6 | import 'package:flutter_swiper/flutter_swiper.dart'; 7 | import 'package:flutter_template/core/utils/toast.dart'; 8 | import 'package:flutter_template/core/widget/grid/grid_item.dart'; 9 | import 'package:flutter_template/core/widget/list/article_item.dart'; 10 | 11 | class TabHomePage extends StatefulWidget { 12 | @override 13 | _TabHomePageState createState() => _TabHomePageState(); 14 | } 15 | 16 | class _TabHomePageState extends State { 17 | int _count = 5; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return EasyRefresh.custom( 22 | header: MaterialHeader(), 23 | footer: MaterialFooter(), 24 | onRefresh: () async { 25 | await Future.delayed(Duration(seconds: 1), () { 26 | setState(() { 27 | _count = 5; 28 | }); 29 | }); 30 | }, 31 | onLoad: () async { 32 | await Future.delayed(Duration(seconds: 1), () { 33 | setState(() { 34 | _count += 5; 35 | }); 36 | }); 37 | }, 38 | slivers: [ 39 | //=====轮播图=====// 40 | SliverToBoxAdapter(child: getBannerWidget()), 41 | 42 | //=====网格菜单=====// 43 | SliverPadding( 44 | padding: EdgeInsets.only(top: 10), 45 | sliver: SliverGrid( 46 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 47 | crossAxisCount: 4, 48 | mainAxisSpacing: 0, 49 | crossAxisSpacing: 10, 50 | ), 51 | delegate: SliverChildBuilderDelegate( 52 | (BuildContext context, int index) { 53 | //创建子widget 54 | var action = actions[index]; 55 | return GridItem( 56 | title: action.title, 57 | color: action.color, 58 | onTap: () { 59 | ToastUtils.toast('点击-->${action.title}'); 60 | }); 61 | }, 62 | childCount: actions.length, 63 | ), 64 | )), 65 | 66 | SliverToBoxAdapter( 67 | child: Padding( 68 | padding: EdgeInsets.fromLTRB(16, 10, 16, 10), 69 | child: Text( 70 | '资讯', 71 | style: TextStyle(fontSize: 18), 72 | ))), 73 | 74 | //=====列表=====// 75 | SliverList( 76 | delegate: SliverChildBuilderDelegate( 77 | (context, index) { 78 | ArticleInfo info = articles[index % 5]; 79 | return ArticleListItem( 80 | articleUrl: info.articleUrl, 81 | imageUrl: info.imageUrl, 82 | title: info.title, 83 | author: info.author, 84 | description: info.description, 85 | summary: info.summary); 86 | }, 87 | childCount: _count, 88 | ), 89 | ), 90 | ], 91 | ); 92 | } 93 | 94 | //这里是演示,所以写死 95 | final List urls = [ 96 | "https://z3.ax1x.com/2021/07/12/WCvW2d.jpg", //伪装者:胡歌演绎"痞子特工" 97 | "https://z3.ax1x.com/2021/07/12/WCv7a8.jpg", //无心法师:生死离别!月牙遭虐杀 98 | "https://z3.ax1x.com/2021/07/12/WCv4KI.jpg", //花千骨:尊上沦为花千骨 99 | "https://z3.ax1x.com/2021/07/12/WCvIqP.jpg", //综艺饭:胖轩偷看夏天洗澡掀波澜 100 | "https://z3.ax1x.com/2021/07/12/WCv5rt.jpg", //碟中谍4:阿汤哥高塔命悬一线,超越不可能 101 | ]; 102 | 103 | Widget getBannerWidget() { 104 | return SizedBox( 105 | height: 200, 106 | child: Swiper( 107 | autoplay: true, 108 | duration: 2000, 109 | autoplayDelay: 5000, 110 | itemBuilder: (context, index) { 111 | return Container( 112 | color: Colors.transparent, 113 | child: ClipRRect( 114 | borderRadius: BorderRadius.circular(0), 115 | child: Image( 116 | fit: BoxFit.fill, 117 | image: CachedNetworkImageProvider( 118 | urls[index], 119 | ), 120 | )), 121 | ); 122 | }, 123 | onTap: (value) { 124 | ToastUtils.toast("点击--->" + value.toString()); 125 | }, 126 | itemCount: urls.length, 127 | pagination: SwiperPagination(), 128 | ), 129 | ); 130 | } 131 | 132 | //这里是演示,所以写死 133 | final List actions = [ 134 | ActionItem('美食', Color(0xFFEF5362)), 135 | ActionItem('甜点', Color(0xFFFE6D4B)), 136 | ActionItem('烧烤', Color(0xFFFFCF47)), 137 | ActionItem('夜宵', Color(0xFF9FD661)), 138 | ActionItem('水果', Color(0xFF3FD0AD)), 139 | ActionItem('药品', Color(0xFF2BBDF3)), 140 | ActionItem('蔬菜', Color(0xFF5A9AEF)), 141 | ActionItem('跑腿', Color(0xFFAC8FEF)), 142 | ]; 143 | 144 | //这里是演示,所以写死 145 | final List articles = [ 146 | ArticleInfo( 147 | 'https://juejin.im/post/5c3ed1dae51d4543805ea48d', 148 | 'https://img-blog.csdnimg.cn/2019011614245559.png', 149 | 'XUI 一个简洁而优雅的Android原生UI框架,解放你的双手', 150 | '涵盖绝大部分的UI组件:TextView、Button、EditText、ImageView、Spinner、Picker、Dialog、PopupWindow、ProgressBar、LoadingView、StateLayout、FlowLayout、Switch、Actionbar、TabBar、Banner、GuideView、BadgeView、MarqueeView、WebView、SearchView等一系列的组件和丰富多彩的样式主题。'), 151 | ArticleInfo( 152 | 'https://juejin.im/post/5b480b79e51d45190905ef44', 153 | 'https://img-blog.csdnimg.cn/20201101003155717.png', 154 | 'XUpdate 一个轻量级、高可用性的Android版本更新框架', 155 | 'XUpdate 一个轻量级、高可用性的Android版本更新框架。本框架借鉴了AppUpdate中的部分思想和UI界面,将版本更新中的各部分环节抽离出来,形成了如下几个部分:'), 156 | ArticleInfo( 157 | 'https://juejin.im/post/5b480b79e51d45190905ef44', 158 | 'https://img-blog.csdnimg.cn/20201101003155717.png', 159 | 'XHttp2 一个功能强悍的网络请求库,使用RxJava2 + Retrofit2 + OKHttp进行组装', 160 | '一个功能强悍的网络请求库,使用RxJava2 + Retrofit2 + OKHttp组合进行封装。还不赶紧点击使用说明文档,体验一下吧!'), 161 | ArticleInfo( 162 | 'https://juejin.im/post/5d431825e51d45620611599a', 163 | 'https://img-blog.csdnimg.cn/20190802004127185.png', 164 | '你真的会使用github吗?', 165 | 'github作为全球最大的开源软件托管平台,自2008年上线以来,一直吸引了无数的程序开发者在上面开源分享自己的项目代码。尤其是在微软收购github之后,更是吸引了很多非程序开发者将自己的知识和经验通过平台分享出来,可以说github是一个蕴藏了无数价值和宝藏的大宝库。然而,对于这样一个极具价值的平台,你真的会使用吗?'), 166 | ArticleInfo( 167 | 'https://juejin.im/post/5e39a1b8518825497467e4ec', 168 | 'https://pic4.zhimg.com/v2-1236d741cbb3aabf5a9910a5e4b73e4c_1200x500.jpg', 169 | 'Flutter学习指南App,一起来玩Flutter吧~', 170 | 'Flutter是谷歌的移动UI框架,可以快速在iOS、Android、Web和PC上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费、开源的。同时它也是构建未来的Google Fuchsia应用的主要方式。'), 171 | ]; 172 | } 173 | -------------------------------------------------------------------------------- /lib/page/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_template/core/utils/click.dart'; 3 | import 'package:flutter_template/core/utils/privacy.dart'; 4 | import 'package:flutter_template/core/utils/toast.dart'; 5 | import 'package:flutter_template/core/utils/xupdate.dart'; 6 | import 'package:flutter_template/generated/i18n.dart'; 7 | import 'package:flutter_template/page/home/tab_home.dart'; 8 | import 'package:flutter_template/utils/provider.dart'; 9 | import 'package:get/get.dart'; 10 | import 'package:provider/provider.dart'; 11 | 12 | import 'menu/menu_drawer.dart'; 13 | 14 | class MainHomePage extends StatefulWidget { 15 | MainHomePage({Key key}) : super(key: key); 16 | 17 | @override 18 | _MainHomePageState createState() => _MainHomePageState(); 19 | } 20 | 21 | class _MainHomePageState extends State { 22 | List getTabs(BuildContext context) => [ 23 | BottomNavigationBarItem( 24 | label: I18n.of(context).home, icon: Icon(Icons.home)), 25 | BottomNavigationBarItem( 26 | label: I18n.of(context).category, icon: Icon(Icons.list)), 27 | BottomNavigationBarItem( 28 | label: I18n.of(context).activity, icon: Icon(Icons.local_activity)), 29 | BottomNavigationBarItem( 30 | label: I18n.of(context).message, icon: Icon(Icons.notifications)), 31 | BottomNavigationBarItem( 32 | label: I18n.of(context).profile, icon: Icon(Icons.person)), 33 | ]; 34 | 35 | List getTabWidget(BuildContext context) => [ 36 | TabHomePage(), 37 | Center(child: Text(I18n.of(context).category)), 38 | Center(child: Text(I18n.of(context).activity)), 39 | Center(child: Text(I18n.of(context).message)), 40 | Center(child: Text(I18n.of(context).profile)), 41 | ]; 42 | 43 | final GlobalKey _scaffoldKey = GlobalKey(); 44 | 45 | @override 46 | void initState() { 47 | super.initState(); 48 | XUpdate.initAndCheck(); 49 | } 50 | 51 | @override 52 | void dispose() { 53 | super.dispose(); 54 | } 55 | 56 | @override 57 | Widget build(BuildContext context) { 58 | var tabs = getTabs(context); 59 | return Consumer( 60 | builder: (BuildContext context, AppStatus status, Widget child) { 61 | return WillPopScope( 62 | child: Scaffold( 63 | key: _scaffoldKey, 64 | appBar: AppBar( 65 | title: Text(tabs[status.tabIndex].label), 66 | actions: [ 67 | IconButton( 68 | icon: Icon(Icons.security), 69 | onPressed: () { 70 | PrivacyUtils.showPrivacyDialog(context, 71 | onAgressCallback: () { 72 | Navigator.of(context).pop(); 73 | ToastUtils.success(I18n.of(context).agreePrivacy); 74 | }); 75 | }), 76 | PopupMenuButton( 77 | itemBuilder: (BuildContext context) => 78 | >[ 79 | PopupMenuItem( 80 | value: "sponsor", 81 | child: ListTile( 82 | contentPadding: 83 | EdgeInsets.symmetric(horizontal: 10), 84 | leading: Icon( 85 | Icons.attach_money, 86 | ), 87 | title: Text(I18n.of(context).sponsor), 88 | ), 89 | ), 90 | PopupMenuItem( 91 | value: "settings", 92 | child: ListTile( 93 | contentPadding: 94 | EdgeInsets.symmetric(horizontal: 10), 95 | leading: Icon( 96 | Icons.settings, 97 | ), 98 | title: Text(I18n.of(context).settings), 99 | ), 100 | ), 101 | PopupMenuItem( 102 | value: "about", 103 | child: ListTile( 104 | contentPadding: 105 | EdgeInsets.symmetric(horizontal: 10), 106 | leading: Icon( 107 | Icons.error_outline, 108 | ), 109 | title: Text(I18n.of(context).about), 110 | ), 111 | ), 112 | ], 113 | onSelected: (String action) { 114 | Get.toNamed('/menu/$action-page'); 115 | }) 116 | ], 117 | ), 118 | drawer: MenuDrawer(), 119 | body: IndexedStack( 120 | index: status.tabIndex, 121 | children: getTabWidget(context), 122 | ), 123 | bottomNavigationBar: BottomNavigationBar( 124 | items: tabs, 125 | //高亮 被点击高亮 126 | currentIndex: status.tabIndex, 127 | //修改 页面 128 | onTap: (index) { 129 | status.tabIndex = index; 130 | }, 131 | type: BottomNavigationBarType.fixed, 132 | fixedColor: Theme.of(context).primaryColor, 133 | ), 134 | ), 135 | //监听导航栏返回,类似onKeyEvent 136 | onWillPop: () => 137 | ClickUtils.exitBy2Click(status: _scaffoldKey.currentState)); 138 | }); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /lib/page/menu/about.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_template/core/utils/utils.dart'; 3 | import 'package:flutter_template/core/utils/xupdate.dart'; 4 | import 'package:flutter_template/core/widget/list/list_item.dart'; 5 | import 'package:flutter_template/generated/i18n.dart'; 6 | import 'package:url_launcher/url_launcher.dart'; 7 | 8 | class AboutPage extends StatefulWidget { 9 | @override 10 | _AboutPageState createState() => _AboutPageState(); 11 | } 12 | 13 | class _AboutPageState extends State { 14 | String _versionName = ''; 15 | 16 | @override 17 | void initState() { 18 | super.initState(); 19 | Utils.getPackageInfo().then((value) => { 20 | setState(() { 21 | _versionName = value.version; 22 | }) 23 | }); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return Scaffold( 29 | appBar: AppBar(title: Text(I18n.of(context).about)), 30 | body: Container( 31 | color: Colors.white, 32 | child: ListView(children: [ 33 | Padding( 34 | padding: const EdgeInsets.only(top: 60), 35 | child: FlutterLogo( 36 | size: 80, 37 | )), 38 | Padding( 39 | padding: const EdgeInsets.only(top: 24), 40 | child: Center( 41 | child: Text( 42 | "${I18n.of(context).versionName}: $_versionName", 43 | style: TextStyle( 44 | color: Colors.grey[600], fontSize: 17)))), 45 | SizedBox(height: 15), 46 | Container( 47 | width: double.infinity, 48 | color: Colors.white, 49 | padding: EdgeInsets.all(15.0), 50 | child: Card( 51 | color: Theme.of(context).primaryColor, 52 | child: Container( 53 | padding: EdgeInsets.all(10.0), 54 | child: Column( 55 | children: [ 56 | ListItem( 57 | icon: Icon( 58 | Icons.assignment_ind, 59 | color: Colors.white, 60 | ), 61 | title: I18n.of(context).author, 62 | titleColor: Colors.white, 63 | describe: 'xuexiangjys', 64 | describeColor: Colors.white, 65 | onPressed: () { 66 | launch('https://github.com/xuexiangjys'); 67 | }, 68 | ), 69 | ListItem( 70 | icon: Icon( 71 | Icons.supervised_user_circle, 72 | color: Colors.white, 73 | ), 74 | title: I18n.of(context).qqgroup, 75 | titleColor: Colors.white, 76 | describe: '602082750', 77 | describeColor: Colors.white, 78 | onPressed: () { 79 | launch( 80 | 'http://qm.qq.com/cgi-bin/qm/qr?k=tiP-E6rDf0y77PRNfp2lNVcc9RsglPCM'); 81 | }, 82 | ), 83 | ListItem( 84 | icon: Icon( 85 | Icons.http, 86 | color: Colors.white, 87 | ), 88 | title: "Github", 89 | titleColor: Colors.white, 90 | describe: 91 | 'https://github.com/xuexiangjys/flutter_template', 92 | describeColor: Colors.white, 93 | onPressed: () { 94 | launch( 95 | 'https://github.com/xuexiangjys/flutter_template'); 96 | }, 97 | ), 98 | ListItem( 99 | icon: Icon( 100 | Icons.update, 101 | color: Colors.white, 102 | ), 103 | title: I18n.of(context).appupdate, 104 | titleColor: Colors.white, 105 | describeColor: Colors.white, 106 | onPressed: () { 107 | XUpdate.checkUpdateWithErrorTip(); 108 | }, 109 | ) 110 | ], 111 | ), 112 | )), 113 | ), 114 | ]))); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /lib/page/menu/language.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_template/generated/i18n.dart'; 3 | import 'package:flutter_template/utils/provider.dart'; 4 | import 'package:provider/provider.dart'; 5 | 6 | class LanguagePage extends StatefulWidget { 7 | @override 8 | _LanguagePageState createState() => _LanguagePageState(); 9 | } 10 | 11 | class _LanguagePageState extends State { 12 | @override 13 | Widget build(BuildContext context) { 14 | Color color = Theme.of(context).primaryColor; 15 | LocaleModel localeModel = Provider.of(context); 16 | I18n S = I18n.of(context); 17 | 18 | Widget _buildLanguageItem(String lan, value) { 19 | return ListTile( 20 | title: Text( 21 | lan, 22 | // 对APP当前语言进行高亮显示 23 | style: TextStyle(color: localeModel.locale == value ? color : null), 24 | ), 25 | trailing: 26 | localeModel.locale == value ? Icon(Icons.done, color: color) : null, 27 | onTap: () { 28 | // 此行代码会通知MaterialApp重新build 29 | localeModel.locale = value; 30 | }, 31 | ); 32 | } 33 | 34 | return Scaffold( 35 | appBar: AppBar(title: Text(S.language)), 36 | body: ListView( 37 | children: [ 38 | _buildLanguageItem(S.english, "en_US"), 39 | _buildLanguageItem(S.chinese, "zh_CN"), 40 | _buildLanguageItem(S.auto, LOCALE_FOLLOW_SYSTEM), 41 | ], 42 | )); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/page/menu/login.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_spinkit/flutter_spinkit.dart'; 3 | import 'package:flutter_template/core/http/http.dart'; 4 | import 'package:flutter_template/core/utils/privacy.dart'; 5 | import 'package:flutter_template/core/utils/toast.dart'; 6 | import 'package:flutter_template/core/widget/loading_dialog.dart'; 7 | import 'package:flutter_template/generated/i18n.dart'; 8 | import 'package:flutter_template/page/index.dart'; 9 | import 'package:flutter_template/page/menu/register.dart'; 10 | import 'package:flutter_template/utils/provider.dart'; 11 | import 'package:flutter_template/utils/sputils.dart'; 12 | import 'package:get/get.dart'; 13 | import 'package:provider/provider.dart'; 14 | 15 | class LoginPage extends StatefulWidget { 16 | @override 17 | _LoginPageState createState() => _LoginPageState(); 18 | } 19 | 20 | class _LoginPageState extends State { 21 | // 响应空白处的焦点的Node 22 | bool _isShowPassWord = false; 23 | FocusNode blankNode = FocusNode(); 24 | TextEditingController _unameController = TextEditingController(); 25 | TextEditingController _pwdController = TextEditingController(); 26 | GlobalKey _formKey = GlobalKey(); 27 | 28 | @override 29 | void initState() { 30 | super.initState(); 31 | if (!SPUtils.isAgreePrivacy()) { 32 | PrivacyUtils.showPrivacyDialog(context, onAgressCallback: () { 33 | Navigator.of(context).pop(); 34 | SPUtils.saveIsAgreePrivacy(true); 35 | ToastUtils.success(I18n.of(context).agreePrivacy); 36 | }); 37 | } 38 | } 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | return WillPopScope( 43 | child: Scaffold( 44 | appBar: AppBar( 45 | // leading: _leading(context), 46 | title: Text(I18n.of(context).login), 47 | actions: [ 48 | TextButton( 49 | child: Text(I18n.of(context).register, 50 | style: TextStyle(color: Colors.white)), 51 | onPressed: () { 52 | Get.to(() => RegisterPage()); 53 | }, 54 | ) 55 | ], 56 | ), 57 | body: GestureDetector( 58 | onTap: () { 59 | // 点击空白页面关闭键盘 60 | closeKeyboard(context); 61 | }, 62 | child: SingleChildScrollView( 63 | padding: 64 | const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0), 65 | child: buildForm(context), 66 | ), 67 | ), 68 | ), 69 | onWillPop: () async { 70 | return Future.value(false); 71 | }); 72 | } 73 | 74 | //构建表单 75 | Widget buildForm(BuildContext context) { 76 | return Form( 77 | key: _formKey, //设置globalKey,用于后面获取FormState 78 | autovalidateMode: AutovalidateMode.disabled, 79 | child: Column( 80 | children: [ 81 | Center( 82 | heightFactor: 1.5, 83 | child: FlutterLogo( 84 | size: 64, 85 | )), 86 | TextFormField( 87 | autofocus: false, 88 | controller: _unameController, 89 | decoration: InputDecoration( 90 | labelText: I18n.of(context).loginName, 91 | hintText: I18n.of(context).loginNameHint, 92 | hintStyle: TextStyle(fontSize: 12), 93 | icon: Icon(Icons.person)), 94 | //校验用户名 95 | validator: (v) { 96 | return v.trim().length > 0 97 | ? null 98 | : I18n.of(context).loginNameError; 99 | }), 100 | TextFormField( 101 | controller: _pwdController, 102 | decoration: InputDecoration( 103 | labelText: I18n.of(context).password, 104 | hintText: I18n.of(context).passwordHint, 105 | hintStyle: TextStyle(fontSize: 12), 106 | icon: Icon(Icons.lock), 107 | suffixIcon: IconButton( 108 | icon: Icon( 109 | _isShowPassWord 110 | ? Icons.visibility 111 | : Icons.visibility_off, 112 | color: Colors.black, 113 | ), 114 | onPressed: showPassWord)), 115 | obscureText: !_isShowPassWord, 116 | //校验密码 117 | validator: (v) { 118 | return v.trim().length >= 6 119 | ? null 120 | : I18n.of(context).passwordError; 121 | }), 122 | // 登录按钮 123 | Padding( 124 | padding: const EdgeInsets.only(top: 28.0), 125 | child: Row( 126 | children: [ 127 | Expanded(child: Builder(builder: (context) { 128 | return ElevatedButton( 129 | style: TextButton.styleFrom( 130 | primary: Theme.of(context).primaryColor, 131 | padding: EdgeInsets.all(15.0)), 132 | child: Text(I18n.of(context).login, 133 | style: TextStyle(color: Colors.white)), 134 | onPressed: () { 135 | //由于本widget也是Form的子代widget,所以可以通过下面方式获取FormState 136 | if (Form.of(context).validate()) { 137 | onSubmit(context); 138 | } 139 | }, 140 | ); 141 | })), 142 | ], 143 | ), 144 | ) 145 | ], 146 | ), 147 | ); 148 | } 149 | 150 | ///点击控制密码是否显示 151 | void showPassWord() { 152 | setState(() { 153 | _isShowPassWord = !_isShowPassWord; 154 | }); 155 | } 156 | 157 | void closeKeyboard(BuildContext context) { 158 | FocusScope.of(context).requestFocus(blankNode); 159 | } 160 | 161 | //验证通过提交数据 162 | void onSubmit(BuildContext context) { 163 | closeKeyboard(context); 164 | 165 | showDialog( 166 | context: context, 167 | barrierDismissible: false, 168 | builder: (BuildContext context) { 169 | return LoadingDialog( 170 | showContent: false, 171 | backgroundColor: Colors.black38, 172 | loadingView: SpinKitCircle(color: Colors.white), 173 | ); 174 | }); 175 | 176 | UserProfile userProfile = Provider.of(context, listen: false); 177 | 178 | XHttp.post("/user/login", { 179 | "username": _unameController.text, 180 | "password": _pwdController.text 181 | }).then((response) { 182 | Navigator.pop(context); 183 | if (response['errorCode'] == 0) { 184 | userProfile.nickName = response['data']['nickname']; 185 | ToastUtils.toast(I18n.of(context).loginSuccess); 186 | Get.off(() => MainHomePage()); 187 | } else { 188 | ToastUtils.error(response['errorMsg']); 189 | } 190 | }).catchError((onError) { 191 | Navigator.of(context).pop(); 192 | ToastUtils.error(onError); 193 | }); 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /lib/page/menu/menu_drawer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_template/core/utils/toast.dart'; 3 | import 'package:flutter_template/core/utils/xuifont.dart'; 4 | import 'package:flutter_template/generated/i18n.dart'; 5 | import 'package:flutter_template/page/menu/about.dart'; 6 | import 'package:flutter_template/page/menu/login.dart'; 7 | import 'package:flutter_template/page/menu/settings.dart'; 8 | import 'package:flutter_template/page/menu/sponsor.dart'; 9 | import 'package:flutter_template/utils/provider.dart'; 10 | import 'package:get/get.dart'; 11 | import 'package:provider/provider.dart'; 12 | 13 | class MenuDrawer extends StatelessWidget { 14 | const MenuDrawer({ 15 | Key key, 16 | }) : super(key: key); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return Consumer2(builder: (BuildContext context, 21 | UserProfile value, AppStatus status, Widget child) { 22 | return Drawer( 23 | child: SingleChildScrollView( 24 | child: Column( 25 | children: [ 26 | GestureDetector( 27 | child: Container( 28 | color: Theme.of(context).primaryColor, 29 | padding: EdgeInsets.only(top: 40, bottom: 20), 30 | child: Row( 31 | children: [ 32 | Padding( 33 | padding: const EdgeInsets.symmetric(horizontal: 16), 34 | child: ClipOval( 35 | // 如果已登录,则显示用户头像;若未登录,则显示默认头像 36 | child: FlutterLogo( 37 | size: 80, 38 | ), 39 | ), 40 | ), 41 | Expanded( 42 | child: Text( 43 | value.nickName != null 44 | ? value.nickName 45 | : I18n.of(context).title, 46 | overflow: TextOverflow.ellipsis, 47 | style: TextStyle( 48 | fontWeight: FontWeight.bold, 49 | fontSize: 20, 50 | color: Colors.white, 51 | ), 52 | )) 53 | ], 54 | ), 55 | ), 56 | onTap: () { 57 | ToastUtils.toast("点击头像"); 58 | }, 59 | ), 60 | MediaQuery.removePadding( 61 | context: context, 62 | // DrawerHeader consumes top MediaQuery padding. 63 | removeTop: true, 64 | child: ListView( 65 | shrinkWrap: true, //为true可以解决子控件必须设置高度的问题 66 | physics: NeverScrollableScrollPhysics(), //禁用滑动事件 67 | scrollDirection: Axis.vertical, // 水平listView 68 | children: [ 69 | //首页 70 | ListTile( 71 | leading: Icon(Icons.home), 72 | title: Text(I18n.of(context).home), 73 | onTap: () { 74 | status.tabIndex = TAB_HOME_INDEX; 75 | Navigator.pop(context); 76 | }, 77 | selected: status.tabIndex == TAB_HOME_INDEX, 78 | ), 79 | ListTile( 80 | leading: Icon(Icons.list), 81 | title: Text(I18n.of(context).category), 82 | onTap: () { 83 | status.tabIndex = TAB_CATEGORY_INDEX; 84 | Navigator.pop(context); 85 | }, 86 | selected: status.tabIndex == TAB_CATEGORY_INDEX, 87 | ), 88 | ListTile( 89 | leading: Icon(Icons.local_activity), 90 | title: Text(I18n.of(context).activity), 91 | onTap: () { 92 | status.tabIndex = TAB_ACTIVITY_INDEX; 93 | Navigator.pop(context); 94 | }, 95 | selected: status.tabIndex == TAB_ACTIVITY_INDEX, 96 | ), 97 | ListTile( 98 | leading: Icon(Icons.notifications), 99 | title: Text(I18n.of(context).message), 100 | onTap: () { 101 | status.tabIndex = TAB_MESSAGE_INDEX; 102 | Navigator.pop(context); 103 | }, 104 | selected: status.tabIndex == TAB_MESSAGE_INDEX, 105 | ), 106 | ListTile( 107 | leading: Icon(Icons.person), 108 | title: Text(I18n.of(context).profile), 109 | onTap: () { 110 | status.tabIndex = TAB_PROFILE_INDEX; 111 | Navigator.pop(context); 112 | }, 113 | selected: status.tabIndex == TAB_PROFILE_INDEX, 114 | ), 115 | //设置、关于、赞助 116 | Divider(height: 1.0, color: Colors.grey), 117 | ListTile( 118 | leading: Icon(Icons.attach_money), 119 | title: Text(I18n.of(context).sponsor), 120 | onTap: () { 121 | Get.to(() => SponsorPage()); 122 | }, 123 | ), 124 | ListTile( 125 | leading: Icon(Icons.settings), 126 | title: Text(I18n.of(context).settings), 127 | onTap: () { 128 | Get.to(() => SettingsPage()); 129 | }, 130 | ), 131 | ListTile( 132 | leading: Icon(Icons.error_outline), 133 | title: Text(I18n.of(context).about), 134 | onTap: () { 135 | Get.to(() => AboutPage()); 136 | }, 137 | ), 138 | //退出 139 | Divider(height: 1.0, color: Colors.grey), 140 | ListTile( 141 | leading: Icon(XUIIcons.logout), 142 | title: Text(I18n.of(context).logout), 143 | onTap: () { 144 | value.nickName = ""; 145 | Get.offAll(() => LoginPage()); 146 | }, 147 | ) 148 | ], 149 | ), 150 | ), 151 | ], 152 | ), 153 | )); 154 | }); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /lib/page/menu/register.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_spinkit/flutter_spinkit.dart'; 3 | import 'package:flutter_template/core/http/http.dart'; 4 | import 'package:flutter_template/core/utils/toast.dart'; 5 | import 'package:flutter_template/core/widget/loading_dialog.dart'; 6 | import 'package:flutter_template/generated/i18n.dart'; 7 | 8 | class RegisterPage extends StatefulWidget { 9 | @override 10 | _RegisterPageState createState() => _RegisterPageState(); 11 | } 12 | 13 | class _RegisterPageState extends State { 14 | // 响应空白处的焦点的Node 15 | bool _isShowPassWord = false; 16 | bool _isShowPassWordRepeat = false; 17 | FocusNode blankNode = FocusNode(); 18 | TextEditingController _unameController = TextEditingController(); 19 | TextEditingController _pwdController = TextEditingController(); 20 | TextEditingController _pwdRepeatController = TextEditingController(); 21 | GlobalKey _formKey = GlobalKey(); 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return Scaffold( 26 | appBar: AppBar(title: Text(I18n.of(context).register)), 27 | body: GestureDetector( 28 | onTap: () { 29 | // 点击空白页面关闭键盘 30 | closeKeyboard(context); 31 | }, 32 | child: SingleChildScrollView( 33 | padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0), 34 | child: buildForm(context), 35 | ), 36 | ), 37 | ); 38 | } 39 | 40 | //构建表单 41 | Widget buildForm(BuildContext context) { 42 | return Form( 43 | key: _formKey, //设置globalKey,用于后面获取FormState 44 | autovalidateMode: AutovalidateMode.disabled, 45 | child: Column( 46 | children: [ 47 | TextFormField( 48 | autofocus: false, 49 | controller: _unameController, 50 | decoration: InputDecoration( 51 | labelText: I18n.of(context).loginName, 52 | hintText: I18n.of(context).loginNameHint, 53 | hintStyle: TextStyle(fontSize: 12), 54 | icon: Icon(Icons.person)), 55 | //校验用户名 56 | validator: (v) { 57 | return v.trim().length > 0 58 | ? null 59 | : I18n.of(context).loginNameError; 60 | }), 61 | TextFormField( 62 | controller: _pwdController, 63 | decoration: InputDecoration( 64 | labelText: I18n.of(context).password, 65 | hintText: I18n.of(context).passwordHint, 66 | hintStyle: TextStyle(fontSize: 12), 67 | icon: Icon(Icons.lock), 68 | suffixIcon: IconButton( 69 | icon: Icon( 70 | _isShowPassWord 71 | ? Icons.visibility 72 | : Icons.visibility_off, 73 | color: Colors.black, 74 | ), 75 | onPressed: showPassWord)), 76 | obscureText: !_isShowPassWord, 77 | //校验密码 78 | validator: (v) { 79 | return v.trim().length >= 6 80 | ? null 81 | : I18n.of(context).passwordError; 82 | }), 83 | 84 | TextFormField( 85 | controller: _pwdRepeatController, 86 | decoration: InputDecoration( 87 | labelText: I18n.of(context).repeatPassword, 88 | hintText: I18n.of(context).passwordHint, 89 | hintStyle: TextStyle(fontSize: 12), 90 | icon: Icon(Icons.lock), 91 | suffixIcon: IconButton( 92 | icon: Icon( 93 | _isShowPassWordRepeat 94 | ? Icons.visibility 95 | : Icons.visibility_off, 96 | color: Colors.black, 97 | ), 98 | onPressed: showPassWordRepeat)), 99 | obscureText: !_isShowPassWordRepeat, 100 | //校验密码 101 | validator: (v) { 102 | return v.trim().length >= 6 103 | ? null 104 | : I18n.of(context).passwordError; 105 | }), 106 | 107 | // 登录按钮 108 | Padding( 109 | padding: const EdgeInsets.only(top: 28.0), 110 | child: Row( 111 | children: [ 112 | Expanded(child: Builder(builder: (context) { 113 | return ElevatedButton( 114 | style: TextButton.styleFrom( 115 | primary: Theme.of(context).primaryColor, 116 | padding: EdgeInsets.all(15.0)), 117 | child: Text(I18n.of(context).register, 118 | style: TextStyle(color: Colors.white)), 119 | onPressed: () { 120 | //由于本widget也是Form的子代widget,所以可以通过下面方式获取FormState 121 | if (Form.of(context).validate()) { 122 | onSubmit(context); 123 | } 124 | }, 125 | ); 126 | })), 127 | ], 128 | ), 129 | ) 130 | ], 131 | ), 132 | ); 133 | } 134 | 135 | ///点击控制密码是否显示 136 | void showPassWord() { 137 | setState(() { 138 | _isShowPassWord = !_isShowPassWord; 139 | }); 140 | } 141 | 142 | ///点击控制密码是否显示 143 | void showPassWordRepeat() { 144 | setState(() { 145 | _isShowPassWordRepeat = !_isShowPassWordRepeat; 146 | }); 147 | } 148 | 149 | void closeKeyboard(BuildContext context) { 150 | FocusScope.of(context).requestFocus(blankNode); 151 | } 152 | 153 | //验证通过提交数据 154 | void onSubmit(BuildContext context) { 155 | closeKeyboard(context); 156 | 157 | showDialog( 158 | context: context, 159 | barrierDismissible: false, 160 | builder: (BuildContext context) { 161 | return LoadingDialog( 162 | showContent: false, 163 | backgroundColor: Colors.black38, 164 | loadingView: SpinKitCircle(color: Colors.white), 165 | ); 166 | }); 167 | 168 | XHttp.post("/user/register", { 169 | "username": _unameController.text, 170 | "password": _pwdController.text, 171 | "repassword": _pwdRepeatController.text 172 | }).then((response) { 173 | Navigator.pop(context); 174 | if (response['errorCode'] == 0) { 175 | ToastUtils.toast(I18n.of(context).registerSuccess); 176 | Navigator.of(context).pop(); 177 | } else { 178 | ToastUtils.error(response['errorMsg']); 179 | } 180 | }).catchError((onError) { 181 | Navigator.of(context).pop(); 182 | ToastUtils.error(onError); 183 | }); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /lib/page/menu/settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_template/generated/i18n.dart'; 3 | import 'package:flutter_template/page/menu/language.dart'; 4 | import 'package:flutter_template/page/menu/theme_color.dart'; 5 | import 'package:get/get.dart'; 6 | 7 | class SettingsPage extends StatefulWidget { 8 | @override 9 | _SettingPageState createState() => _SettingPageState(); 10 | } 11 | 12 | class _SettingPageState extends State { 13 | @override 14 | Widget build(BuildContext context) { 15 | return Scaffold( 16 | appBar: AppBar(title: Text(I18n.of(context).settings)), 17 | body: SingleChildScrollView( 18 | child: ListBody(children: [ 19 | SizedBox(height: 10), 20 | ListTile( 21 | leading: Icon(Icons.color_lens), 22 | title: Text(I18n.of(context).theme), 23 | trailing: Icon(Icons.keyboard_arrow_right), 24 | contentPadding: EdgeInsets.only(left: 20, right: 10), 25 | onTap: () { 26 | Get.to(() => ThemeColorPage()); 27 | }), 28 | ListTile( 29 | leading: Icon(Icons.language), 30 | title: Text(I18n.of(context).language), 31 | trailing: Icon(Icons.keyboard_arrow_right), 32 | contentPadding: EdgeInsets.only(left: 20, right: 10), 33 | onTap: () { 34 | Get.to(() => LanguagePage()); 35 | }, 36 | ), 37 | ]))); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/page/menu/sponsor.dart: -------------------------------------------------------------------------------- 1 | import 'package:extended_image/extended_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_template/core/utils/toast.dart'; 4 | import 'package:flutter_template/core/utils/utils.dart'; 5 | import 'package:flutter_template/generated/i18n.dart'; 6 | import 'package:image_picker_saver/image_picker_saver.dart'; 7 | import 'package:permission_handler/permission_handler.dart'; 8 | import 'package:share/share.dart'; 9 | 10 | class SponsorPage extends StatefulWidget { 11 | @override 12 | _SponsorPageState createState() => _SponsorPageState(); 13 | } 14 | 15 | class _SponsorPageState extends State { 16 | @override 17 | Widget build(BuildContext context) { 18 | return Scaffold( 19 | appBar: AppBar(title: Text(I18n.of(context).sponsor)), 20 | body: SingleChildScrollView( 21 | padding: const EdgeInsets.all(10), 22 | child: Column(children: [ 23 | Text(I18n.of(context).sponsorDescription, 24 | style: TextStyle(color: Colors.grey[700], fontSize: 15)), 25 | SizedBox(height: 15), 26 | _loadImage( 27 | "https://gitee.com/xuexiangjys/Resource/raw/master/img/pay/alipay.jpeg", 28 | linkUrl: "https://qr.alipay.com/fkx14433o4e5gqkhi9fsr2f"), 29 | SizedBox(height: 15), 30 | _loadImage( 31 | "https://gitee.com/xuexiangjys/Resource/raw/master/img/pay/weixinpay.jpeg"), 32 | ]))); 33 | } 34 | 35 | Widget _loadImage(String url, {String linkUrl = ''}) => SizedBox( 36 | width: 166, 37 | height: 249, 38 | child: Center( 39 | child: GestureDetector( 40 | child: ExtendedImage.network( 41 | url, 42 | ), 43 | onLongPress: () => showMenuDialog(url, linkUrl), 44 | )), 45 | ); 46 | 47 | ///显示操作菜单弹窗 48 | void showMenuDialog(String url, String linkUrl) { 49 | showDialog( 50 | context: context, 51 | builder: (BuildContext context) { 52 | return SimpleDialog( 53 | titlePadding: const EdgeInsets.fromLTRB(20, 10, 10, 0), 54 | title: Text('操作'), 55 | children: [ 56 | linkUrl.isNotEmpty 57 | ? SimpleDialogOption( 58 | child: Text('打开应用'), 59 | onPressed: () { 60 | Utils.launchURL(linkUrl); 61 | Navigator.of(context).pop(); 62 | }, 63 | ) 64 | : SizedBox(), 65 | SimpleDialogOption( 66 | child: Text('保存到本地'), 67 | onPressed: () { 68 | saveImage(url); 69 | Navigator.of(context).pop(); 70 | }, 71 | ), 72 | SimpleDialogOption( 73 | child: Text('分享给好友'), 74 | onPressed: () { 75 | shareImage(url); 76 | Navigator.of(context).pop(); 77 | }, 78 | ), 79 | ], 80 | ); 81 | }, 82 | ); 83 | } 84 | 85 | ///保存网络图片 86 | void saveImage(String url) { 87 | Permission.storage.request().then((value) { 88 | if (PermissionStatus.granted == value) { 89 | saveNetworkImageToPhoto(url).then((value) { 90 | if (value != null && value.isNotEmpty) { 91 | ToastUtils.success("图片保存成功: $value"); 92 | } else { 93 | ToastUtils.error("图片保存失败!"); 94 | } 95 | }).catchError((onError) { 96 | ToastUtils.error("图片保存失败:$onError"); 97 | }); 98 | } else { 99 | ToastUtils.error("权限申请失败!"); 100 | } 101 | }); 102 | } 103 | 104 | Future saveNetworkImageToPhoto(String url, 105 | {bool useCache: true}) async { 106 | var data = await getNetworkImageData(url, useCache: useCache); 107 | return await ImagePickerSaver.saveFile(fileData: data); 108 | } 109 | 110 | void shareImage(String url) { 111 | Share.share(url); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /lib/page/menu/theme_color.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_template/generated/i18n.dart'; 3 | import 'package:flutter_template/utils/provider.dart'; 4 | import 'package:provider/provider.dart'; 5 | 6 | class ThemeColorPage extends StatefulWidget { 7 | @override 8 | _ThemeColorPageState createState() => _ThemeColorPageState(); 9 | } 10 | 11 | class _ThemeColorPageState extends State { 12 | bool _isDark; 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | AppTheme appTheme = Provider.of(context); 17 | _isDark = appTheme.brightness == Brightness.dark; 18 | return Scaffold( 19 | appBar: AppBar(title: Text(I18n.of(context).theme)), 20 | body: CustomScrollView( 21 | slivers: [ 22 | SliverPadding( 23 | padding: const EdgeInsets.fromLTRB(10, 10, 10, 0), 24 | sliver: SliverFixedExtentList( 25 | itemExtent: 50.0, 26 | delegate: SliverChildBuilderDelegate( 27 | (BuildContext context, int index) { 28 | //创建列表项 29 | return ListTile( 30 | title: Text( 31 | I18n.of(context).darkTheme, 32 | style: TextStyle( 33 | color: _isDark 34 | ? Colors.white 35 | : Theme.of(context).primaryColor), 36 | ), 37 | trailing: Switch( 38 | value: _isDark, 39 | onChanged: (value) { 40 | Store.value(context).changeBrightness(value); 41 | }, 42 | ), 43 | ); 44 | }, childCount: 1), 45 | ), 46 | ), 47 | SliverPadding( 48 | padding: const EdgeInsets.all(10), 49 | sliver: SliverGrid( 50 | delegate: SliverChildBuilderDelegate( 51 | (BuildContext context, int index) { 52 | //创建子widget 53 | return GestureDetector( 54 | onTap: () { 55 | Store.value(context).changeColor(index); 56 | }, 57 | child: 58 | Container(color: AppTheme.materialColors[index]), 59 | ); 60 | }, 61 | childCount: AppTheme.materialColors.length, 62 | ), 63 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 64 | //横轴元素个数 65 | crossAxisCount: 3, 66 | //纵轴间距 67 | mainAxisSpacing: 10.0, 68 | //横轴间距 69 | crossAxisSpacing: 10.0, 70 | //子组件宽高长度比例 71 | childAspectRatio: 1.0))) 72 | ], 73 | )); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/router/route_map.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_template/core/widget/web_view_page.dart'; 3 | import 'package:flutter_template/init/splash.dart'; 4 | import 'package:flutter_template/page/index.dart'; 5 | import 'package:flutter_template/page/menu/about.dart'; 6 | import 'package:flutter_template/page/menu/login.dart'; 7 | import 'package:flutter_template/page/menu/settings.dart'; 8 | import 'package:flutter_template/page/menu/sponsor.dart'; 9 | import 'package:get/get.dart'; 10 | 11 | class RouteMap { 12 | static List getPages = [ 13 | GetPage(name: '/', page: () => SplashPage()), 14 | GetPage(name: '/login', page: () => LoginPage()), 15 | GetPage(name: '/home', page: () => MainHomePage()), 16 | GetPage(name: '/web', page: () => WebViewPage()), 17 | GetPage(name: '/menu/sponsor-page', page: () => SponsorPage()), 18 | GetPage(name: '/menu/settings-page', page: () => SettingsPage()), 19 | GetPage(name: '/menu/about-page', page: () => AboutPage()), 20 | ]; 21 | 22 | /// 页面切换动画 23 | static Widget getTransitions( 24 | BuildContext context, 25 | Animation animation1, 26 | Animation animation2, 27 | Widget child) { 28 | return SlideTransition( 29 | position: Tween( 30 | //1.0为右进右出,-1.0为左进左出 31 | begin: Offset(1.0, 0.0), 32 | end: Offset(0.0, 0.0)) 33 | .animate( 34 | CurvedAnimation(parent: animation1, curve: Curves.fastOutSlowIn)), 35 | child: child, 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/router/router.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | class XRouter { 4 | static void goWeb(String url, String title) { 5 | Get.toNamed( 6 | "/web?url=${Uri.encodeComponent(url)}&title=${Uri.encodeComponent(title)}"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lib/utils/provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_template/generated/i18n.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | import 'sputils.dart'; 6 | 7 | //状态管理 8 | class Store { 9 | Store._internal(); 10 | 11 | //全局初始化 12 | static init(Widget child) { 13 | //多个Provider 14 | return MultiProvider( 15 | providers: [ 16 | ChangeNotifierProvider( 17 | create: (_) => AppTheme(getDefaultTheme(), getDefaultBrightness())), 18 | ChangeNotifierProvider.value(value: LocaleModel(SPUtils.getLocale())), 19 | ChangeNotifierProvider.value(value: UserProfile(SPUtils.getNickName())), 20 | ChangeNotifierProvider.value(value: AppStatus(TAB_HOME_INDEX)), 21 | ], 22 | child: child, 23 | ); 24 | } 25 | 26 | //获取值 of(context) 这个会引起页面的整体刷新,如果全局是页面级别的 27 | static T value(BuildContext context, {bool listen = false}) { 28 | return Provider.of(context, listen: listen); 29 | } 30 | 31 | //获取值 of(context) 这个会引起页面的整体刷新,如果全局是页面级别的 32 | static T of(BuildContext context, {bool listen = true}) { 33 | return Provider.of(context, listen: listen); 34 | } 35 | 36 | // 不会引起页面的刷新,只刷新了 Consumer 的部分,极大地缩小你的控件刷新范围 37 | static Consumer connect({builder, child}) { 38 | return Consumer(builder: builder, child: child); 39 | } 40 | } 41 | 42 | MaterialColor getDefaultTheme() { 43 | return AppTheme.materialColors[SPUtils.getThemeIndex()]; 44 | } 45 | 46 | Brightness getDefaultBrightness() { 47 | return SPUtils.getBrightness(); 48 | } 49 | 50 | ///主题 51 | class AppTheme with ChangeNotifier { 52 | static final List materialColors = [ 53 | Colors.blue, 54 | Colors.lightBlue, 55 | Colors.red, 56 | Colors.pink, 57 | Colors.purple, 58 | Colors.grey, 59 | Colors.orange, 60 | Colors.amber, 61 | Colors.yellow, 62 | Colors.lightGreen, 63 | Colors.green, 64 | Colors.lime 65 | ]; 66 | 67 | MaterialColor _themeColor; 68 | 69 | Brightness _brightness; 70 | 71 | AppTheme(this._themeColor, this._brightness); 72 | 73 | void setColor(MaterialColor color) { 74 | _themeColor = color; 75 | notifyListeners(); 76 | } 77 | 78 | void changeColor(int index) { 79 | _themeColor = materialColors[index]; 80 | SPUtils.saveThemeIndex(index); 81 | notifyListeners(); 82 | } 83 | 84 | void setBrightness(bool isLight) { 85 | notifyListeners(); 86 | } 87 | 88 | void changeBrightness(bool isDark) { 89 | _brightness = isDark ? Brightness.dark : Brightness.light; 90 | SPUtils.saveBrightness(isDark); 91 | notifyListeners(); 92 | } 93 | 94 | get themeColor => _themeColor; 95 | 96 | get brightness => _brightness; 97 | } 98 | 99 | ///跟随系统 100 | const String LOCALE_FOLLOW_SYSTEM = "auto"; 101 | 102 | ///语言 103 | class LocaleModel with ChangeNotifier { 104 | // 获取当前用户的APP语言配置Locale类,如果为null,则语言跟随系统语言 105 | Locale getLocale() { 106 | if (_locale == LOCALE_FOLLOW_SYSTEM) return null; 107 | var t = _locale.split("_"); 108 | return Locale(t[0], t[1]); 109 | } 110 | 111 | String _locale = LOCALE_FOLLOW_SYSTEM; 112 | 113 | LocaleModel(this._locale); 114 | 115 | // 获取当前Locale的字符串表示 116 | String get locale => _locale; 117 | 118 | // 用户改变APP语言后,通知依赖项更新,新语言会立即生效 119 | set locale(String locale) { 120 | if (_locale != locale) { 121 | _locale = locale; 122 | I18n.locale = getLocale(); 123 | SPUtils.saveLocale(_locale); 124 | notifyListeners(); 125 | } 126 | } 127 | } 128 | 129 | ///用户账户信息 130 | class UserProfile with ChangeNotifier { 131 | String _nickName; 132 | 133 | UserProfile(this._nickName); 134 | 135 | String get nickName => _nickName; 136 | 137 | set nickName(String nickName) { 138 | _nickName = nickName; 139 | SPUtils.saveNickName(nickName); 140 | notifyListeners(); 141 | } 142 | } 143 | 144 | ///主页 145 | const int TAB_HOME_INDEX = 0; 146 | 147 | ///分类 148 | const int TAB_CATEGORY_INDEX = 1; 149 | 150 | ///活动 151 | const int TAB_ACTIVITY_INDEX = 2; 152 | 153 | ///消息 154 | const int TAB_MESSAGE_INDEX = 3; 155 | 156 | ///我的 157 | const int TAB_PROFILE_INDEX = 4; 158 | 159 | ///应用状态 160 | class AppStatus with ChangeNotifier { 161 | //主页tab的索引 162 | int _tabIndex; 163 | 164 | AppStatus(this._tabIndex); 165 | 166 | int get tabIndex => _tabIndex; 167 | 168 | set tabIndex(int index) { 169 | _tabIndex = index; 170 | notifyListeners(); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /lib/utils/sputils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter_template/utils/provider.dart'; 4 | import 'package:shared_preferences/shared_preferences.dart'; 5 | 6 | class SPUtils { 7 | /// 内部构造方法,可避免外部暴露构造函数,进行实例化 8 | SPUtils._internal(); 9 | 10 | static SharedPreferences _spf; 11 | 12 | static Future init() async { 13 | if (_spf == null) { 14 | _spf = await SharedPreferences.getInstance(); 15 | } 16 | return _spf; 17 | } 18 | 19 | ///主题 20 | static Future saveThemeIndex(int value) { 21 | return _spf.setInt('key_theme_color', value); 22 | } 23 | 24 | static int getThemeIndex() { 25 | if (_spf.containsKey('key_theme_color')) { 26 | return _spf.getInt('key_theme_color'); 27 | } 28 | return 0; 29 | } 30 | 31 | ///深色模式 32 | static Future saveBrightness(bool isDark) { 33 | return _spf.setBool('key_brightness', isDark); 34 | } 35 | 36 | static Brightness getBrightness() { 37 | bool isDark = _spf.containsKey('key_brightness') 38 | ? _spf.getBool('key_brightness') 39 | : false; 40 | return isDark ? Brightness.dark : Brightness.light; 41 | } 42 | 43 | ///语言 44 | static Future saveLocale(String locale) { 45 | return _spf.setString('key_locale', locale); 46 | } 47 | 48 | static String getLocale() { 49 | String locale = _spf.getString('key_locale'); 50 | if (locale == null) { 51 | locale = LOCALE_FOLLOW_SYSTEM; 52 | } 53 | return locale; 54 | } 55 | 56 | ///昵称 57 | static Future saveNickName(String nickName) { 58 | return _spf.setString('key_nickname', nickName); 59 | } 60 | 61 | static String getNickName() { 62 | return _spf.getString('key_nickname'); 63 | } 64 | 65 | ///是否同意隐私协议 66 | static Future saveIsAgreePrivacy(bool isAgree) { 67 | return _spf.setBool('key_agree_privacy', isAgree); 68 | } 69 | 70 | static bool isAgreePrivacy() { 71 | if (!_spf.containsKey('key_agree_privacy')) { 72 | return false; 73 | } 74 | return _spf.getBool('key_agree_privacy'); 75 | } 76 | 77 | ///是否已登陆 78 | static bool isLogined() { 79 | String nickName = getNickName(); 80 | return nickName != null && nickName.isNotEmpty; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_template 2 | description: A new Flutter project. 3 | 4 | version: 1.0.0+1 5 | 6 | environment: 7 | sdk: ">=2.2.0 <3.0.0" 8 | 9 | # 依赖库 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | flutter_localizations: 14 | sdk: flutter 15 | 16 | cached_network_image: 3.0.0 17 | cookie_jar: 3.0.1 18 | cupertino_icons: 1.0.2 19 | date_format: 2.0.2 20 | devicelocale: 0.4.2 21 | dio: 4.0.1 22 | dio_cookie_manager: 2.0.0 23 | extended_image: 4.0.1 24 | event_bus: 2.0.0 25 | flutter_easyrefresh: 2.1.6 26 | flutter_spinkit: 5.0.0 27 | flutter_swiper: 1.1.6 28 | flutter_webview_plugin: 0.3.11 29 | flutter_xupdate: 2.0.2 30 | get: 4.1.4 31 | image_picker_saver: 0.3.0 32 | oktoast: 3.0.0 33 | path_provider: 2.0.1 34 | package_info: 2.0.0 35 | permission_handler: 7.1.0 36 | provider: 5.0.0 37 | shared_preferences: 2.0.3 38 | share: 2.0.1 39 | url_launcher: 6.0.2 40 | 41 | dev_dependencies: 42 | flutter_test: 43 | sdk: flutter 44 | 45 | # 字体、图标、图片等资源加载 46 | flutter: 47 | uses-material-design: true 48 | assets: 49 | - assets/ 50 | 51 | fonts: 52 | - family: xuifont 53 | fonts: 54 | - asset: assets/fonts/iconfont.ttf -------------------------------------------------------------------------------- /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_template/init/default_app.dart'; 10 | import 'package:flutter_test/flutter_test.dart'; 11 | 12 | void main() { 13 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 14 | // Build our app and trigger a frame. 15 | await tester.pumpWidget(MyApp()); 16 | 17 | // Verify that our counter starts at 0. 18 | expect(find.text('0'), findsOneWidget); 19 | expect(find.text('1'), findsNothing); 20 | 21 | // Tap the '+' icon and trigger a frame. 22 | await tester.tap(find.byIcon(Icons.add)); 23 | await tester.pump(); 24 | 25 | // Verify that our counter has incremented. 26 | expect(find.text('0'), findsNothing); 27 | expect(find.text('1'), findsOneWidget); 28 | }); 29 | } 30 | --------------------------------------------------------------------------------