├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── android ├── app │ ├── build.gradle │ └── src │ │ ├── development │ │ └── res │ │ │ └── values │ │ │ └── strings.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── test │ │ │ │ └── flutterstarterkit │ │ │ │ └── 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 │ │ └── staging │ │ └── res │ │ └── values │ │ └── strings.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── settings_aar.gradle ├── images ├── 2.0x │ └── test.png ├── 3.0x │ └── test.png └── test.png ├── ios ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ ├── Release.xcconfig │ ├── development.xcconfig │ ├── production.xcconfig │ └── staging.xcconfig ├── Podfile ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ ├── Runner.xcscheme │ │ ├── development.xcscheme │ │ ├── production.xcscheme │ │ └── staging.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── WorkspaceSettings.xcsettings └── 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 │ ├── de.lproj │ ├── LaunchScreen.strings │ └── Main.strings │ ├── ja.lproj │ ├── LaunchScreen.strings │ └── Main.strings │ ├── zh-Hant.lproj │ ├── LaunchScreen.strings │ └── Main.strings │ └── zh.lproj │ ├── LaunchScreen.strings │ └── Main.strings ├── lib ├── app │ ├── bloc │ │ ├── AppDetailBloc.dart │ │ └── HomeBloc.dart │ ├── model │ │ ├── api │ │ │ ├── APIProvider.dart │ │ │ └── AppStoreAPIRepository.dart │ │ ├── core │ │ │ ├── AppComponent.dart │ │ │ ├── AppProvider.dart │ │ │ ├── AppRoutes.dart │ │ │ └── AppStoreApplication.dart │ │ ├── db │ │ │ ├── AppDatabaseMigrationListener.dart │ │ │ └── DBAppStoreRepository.dart │ │ └── pojo │ │ │ ├── AppContent.dart │ │ │ ├── AppContent.g.dart │ │ │ ├── Attribute.dart │ │ │ ├── Attribute.g.dart │ │ │ ├── Entry.dart │ │ │ ├── Entry.g.dart │ │ │ ├── Property.dart │ │ │ ├── Property.g.dart │ │ │ └── response │ │ │ ├── LookupResponse.dart │ │ │ ├── LookupResponse.g.dart │ │ │ ├── TopAppResponse.dart │ │ │ └── TopAppResponse.g.dart │ └── ui │ │ └── page │ │ ├── AppDetailPage.dart │ │ └── HomePage.dart ├── config │ ├── Env.dart │ ├── main_development.dart │ ├── main_production.dart │ └── main_staging.dart ├── generated │ ├── i18n.dart │ └── messages_all.dart └── utility │ ├── db │ └── DatabaseHelper.dart │ ├── framework │ └── Application.dart │ ├── http │ └── HttpException.dart │ ├── log │ ├── DioLogger.dart │ └── Log.dart │ └── widget │ └── StreamListItem.dart ├── pubspec.yaml ├── res └── string │ ├── string_de.json │ ├── string_en.json │ ├── string_ja.json │ └── string_zh_TW.json └── test └── widget_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.lock 4 | *.log 5 | *.pyc 6 | *.swp 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # Visual Studio Code related 20 | .vscode/ 21 | 22 | # Flutter/Dart/Pub related 23 | **/doc/api/ 24 | .dart_tool/ 25 | .flutter-plugins 26 | .packages 27 | .pub-cache/ 28 | .pub/ 29 | build/ 30 | .flutter-plugins-dependencies 31 | **/flutter_export_environment.sh 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Generated.xcconfig 62 | **/ios/Flutter/app.flx 63 | **/ios/Flutter/app.zip 64 | **/ios/Flutter/flutter_assets/ 65 | **/ios/ServiceDefinitions.json 66 | **/ios/Runner/GeneratedPluginRegistrant.* 67 | 68 | # Exceptions to above rules. 69 | !**/ios/**/default.mode1v3 70 | !**/ios/**/default.mode2v3 71 | !**/ios/**/default.pbxuser 72 | !**/ios/**/default.perspectivev3 73 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 74 | -------------------------------------------------------------------------------- /.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: 5391447fae6209bb21a89e6a5a6583cac1af9b4b 8 | channel: beta 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.4.1 2 | ### Update 3 | * Enable flutter_stetho 4 | 5 | ## 1.4.0 6 | ### Update 7 | * Support Flutter version 1.17.3 8 | * Update latest plugins version 9 | * Migrate to AndroidX 10 | 11 | ### Fix 12 | * Fix Apple Store news feed api change 13 | 14 | -------------------------------------------------------------------------------- /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, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flutter Starter Kit - App Store Example 2 | 3 | A starter kit for beginner learns with Bloc pattern, RxDart, sqflite, Fluro and Dio to architect a flutter project. This starter kit build an App Store app as a example 4 | 5 | ![App Store Flutter Demo](https://i.ibb.co/FsyWhpY/ezgif-3-5dbb34baf658.gif) 6 | 7 | ## Feature 8 | - Bloc Pattern 9 | - Navigate pages by [Fluro](https://github.com/theyakka/fluro) 10 | - Local cache by using [sqflite](https://github.com/tekartik/sqflite) 11 | - Restful api call by using [Dio](https://github.com/flutterchina/dio) 12 | - Database debugging (Android Only) by using [flutter_stetho](https://github.com/brianegan/flutter_stetho) 13 | - Loading Network Image 14 | - Localization by using [gen_lang](https://github.com/KingWu/gen_lang) 15 | and [lang_table](https://github.com/KingWu/lang_table) 16 | - Environment Variable & Project Config (Like App Name, Bundle Id) based on different project flavour (Development, Staging & Production) 17 | - Build pojo by using json_serializable 18 | - Update each list item instead of re-rendering whole list view when data set has changed on a list item 19 | - Hero animation 20 | - Show empty View when the list view is empty 21 | 22 | ## Install 23 | 24 | 1. Follow flutter [official setup guide](https://flutter.io/docs/get-started/install) to set up flutter environment 25 | 2. Download [flutter version 1.17.3](https://flutter.dev/docs/development/tools/sdk/releases) 26 | 27 | Remark: This starter kit support Flutter version - 1.17.3. It is because Flutter may have breaking change on latest version. 28 | 29 | ## Run Config 30 | 1. Click 'Edit Configuration' 31 | 2. Create different run configs for flavours 32 | 33 | ![Edit Config](https://i.ibb.co/sbkgnmN/Screen-Shot-2019-01-13-at-7-28-44-PM.png) 34 | 35 | ![Config](https://i.ibb.co/tqPgMVz/Screen-Shot-2019-01-13-at-7-52-38-PM.png) 36 | 37 | ![Flavour](https://i.ibb.co/hCP2QJ1/Screen-Shot-2019-01-13-at-7-40-16-PM.png) 38 | 39 | 40 | ## Useful Command 41 | 42 | ### Run flutter_starter_kit 43 | 44 | For development, 45 | 46 | ``` 47 | flutter run --flavor development -t lib/config/main_development.dart 48 | ``` 49 | 50 | For staging, 51 | ``` 52 | flutter run --flavor staging -t lib/config/main_staging.dart 53 | ``` 54 | 55 | For production, 56 | ``` 57 | flutter run --flavor production -t lib/config/main_production.dart 58 | ``` 59 | 60 | ### Generate json serialize and deserialize functions 61 | 62 | ``` 63 | flutter packages pub run build_runner build --delete-conflicting-outputs 64 | ``` 65 | 66 | ### lang_table 67 | ``` 68 | flutter packages pub run lang_table:generate --platform=airTable --input=https://api.airtable.com/v0/appZmh0WMg3y6APAg/example --api-key={YOUR API KEY} --target=Flutter 69 | ``` 70 | 71 | ### gen_lang 72 | ``` 73 | flutter packages pub run gen_lang:generate 74 | ``` 75 | 76 | ## Known Issues 77 | - [Unable to launch app on ios simulator with different flavours](https://github.com/flutter/flutter/issues/21335) 78 | 79 | ## Migration Guide 80 | - If you wanna to use this project as your project's base, please read 81 | [migration guide](https://github.com/KingWu/flutter_starter_kit/wiki/Migration-Guide) 82 | 83 | 84 | ## Reference 85 | 86 | - [My Flutter Learning Path](https://medium.com/@kingwu/flutter-learning-path-d6b3b0235799) 87 | 88 | #### From other platform? 89 | - [Flutter for Android developers](https://flutter.io/docs/get-started/flutter-for/android-devs) 90 | - [Flutter for iOS developers](https://flutter.io/docs/get-started/flutter-for/ios-devs) 91 | - [Flutter for React Native developers](https://flutter.io/docs/get-started/flutter-for/react-native-devs) 92 | - [Flutter for web developers](https://flutter.io/docs/get-started/flutter-for/web-devs) 93 | - [Flutter for Xamarin.Forms developers](https://flutter.io/docs/get-started/flutter-for/xamarin-forms-devs) 94 | 95 | #### Learn Widget & Layout 96 | - [Building Layouts](https://flutter.io/docs/development/ui/layout) 97 | - [Widget catalog](https://flutter.io/docs/development/ui/widgets) 98 | - [Series of flutter widget of the week](https://www.youtube.com/playlist?list=PLOU2XLYxmsIL0pH0zWe_ZOHgGhZ7UasUE) 99 | - [Series of Flutter Widgets 101](https://www.youtube.com/playlist?list=PLOU2XLYxmsIJyiwUPCou_OVTpRIn_8UMd) 100 | 101 | 102 | #### Bloc Pattern 103 | - [Architect your Flutter project using BLOC pattern](https://medium.com/flutterpub/architecting-your-flutter-project-bd04e144a8f1) 104 | 105 | #### Json Serialization 106 | - [JSON and serialization](https://flutter.io/docs/development/data-and-backend/json) 107 | 108 | #### Localization 109 | - [A new approach of localization in Flutter](https://medium.com/@kingwu/a-new-approach-of-localization-in-flutter-e18bfb2b14ab) 110 | - [Flutter: internationalization tutorials: Part 3— Android Studio plugin](https://medium.com/@datvt9312/flutter-internationalization-tutorials-part-3-android-studio-plugin-8604e2dc90f0) 111 | - [讓 Flutter App 支援多國語系的開發流程](https://medium.com/@zonble/%E8%AE%93-flutter-app-%E6%94%AF%E6%8F%B4%E5%A4%9A%E5%9C%8B%E8%AA%9E%E7%B3%BB%E7%9A%84%E9%96%8B%E7%99%BC%E6%B5%81%E7%A8%8B-ceb31532e2e1) 112 | 113 | #### Flavouring 114 | - [Flavoring Flutter](https://medium.com/@salvatoregiordanoo/flavoring-flutter-392aaa875f36) 115 | - [Creating flavors of a Flutter app (Flutter & Android setup)](http://cogitas.net/creating-flavors-of-a-flutter-app/) 116 | 117 | #### Advance Topic 118 | - [The Mahogany Staircase - Flutter's Layered Design](https://www.youtube.com/watch?time_continue=1&v=dkyY9WCGMi0) 119 | - [Flutter's Rendering Pipeline](https://www.youtube.com/watch?v=UUfXWzp0-DU) 120 | 121 | ### Powered By 122 | - [Plaker Lab 創玩坊](https://www.plakerlab.com/) 123 | - [Wenjetso 搵著數](https://www.wenjetso.com/zh_HK/) 124 | -------------------------------------------------------------------------------- /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 29 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.test.flutterstarterkit" 42 | minSdkVersion 16 43 | targetSdkVersion 29 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 | flavorDimensions "flutter-flavours" 58 | 59 | productFlavors{ 60 | development{ 61 | dimension "flutter-flavours" 62 | applicationIdSuffix ".dev" 63 | } 64 | staging{ 65 | dimension "flutter-flavours" 66 | applicationIdSuffix ".stg" 67 | } 68 | production{ 69 | dimension "flutter-flavours" 70 | } 71 | } 72 | } 73 | 74 | flutter { 75 | source '../..' 76 | } 77 | 78 | dependencies { 79 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 80 | testImplementation 'junit:junit:4.12' 81 | androidTestImplementation 'androidx.test:runner:1.1.0' 82 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' 83 | implementation 'com.github.bumptech.glide:glide:4.8.0' 84 | annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0' 85 | } 86 | -------------------------------------------------------------------------------- /android/app/src/development/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | (Dev) Flutter Starter Kit 4 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 15 | 19 | 26 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/test/flutterstarterkit/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.test.flutterstarterkit 2 | 3 | import android.os.Bundle 4 | 5 | import io.flutter.app.FlutterActivity 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | import io.flutter.plugin.common.MethodChannel 8 | import java.util.* 9 | 10 | 11 | class MainActivity: FlutterActivity() { 12 | // private static final String CHANNEL = "samples.flutter.io/test" 13 | companion object { 14 | const val CHANNEL:String = "samples.flutter.io/test" 15 | } 16 | 17 | var channel:MethodChannel? = null 18 | var counter:Timer? = null 19 | 20 | override fun onCreate(savedInstanceState: Bundle?) { 21 | super.onCreate(savedInstanceState) 22 | GeneratedPluginRegistrant.registerWith(this) 23 | 24 | // Log.d("test", "onCreate : ") 25 | // 26 | // channel = MethodChannel(flutterView, CHANNEL) 27 | // channel?.setMethodCallHandler { methodCall, result -> 28 | // Log.d("test", "methodCall.method : " + methodCall.method) 29 | // when (methodCall.method) { 30 | // "yo" -> { 31 | // Log.d("test", "value : " + methodCall.arguments.toString()) 32 | // result.success("message from android") 33 | // } 34 | // } 35 | // } 36 | } 37 | 38 | // override fun onResume() { 39 | // super.onResume() 40 | // counter = Timer() 41 | // 42 | // var result: MethodChannel.Result = object : MethodChannel.Result{ 43 | // override fun notImplemented() { 44 | // Log.d("test", "notImplemented") 45 | // } 46 | // 47 | // override fun error(p0: String?, p1: String?, p2: Any?) { 48 | // Log.d("test", "error") 49 | // 50 | // } 51 | // 52 | // override fun success(p0: Any?) { 53 | // Log.d("test", p0.toString()) 54 | // } 55 | // 56 | // } 57 | // 58 | // counter?.scheduleAtFixedRate(timerTask { 59 | // channel?.invokeMethod("foo", "hello world", result) 60 | // }, 0, 1000) 61 | // } 62 | 63 | 64 | } 65 | -------------------------------------------------------------------------------- /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/KingWu/flutter_starter_kit/6b72bc03a21a4802a4583ea51f3b2fd7c0e98f71/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KingWu/flutter_starter_kit/6b72bc03a21a4802a4583ea51f3b2fd7c0e98f71/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KingWu/flutter_starter_kit/6b72bc03a21a4802a4583ea51f3b2fd7c0e98f71/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KingWu/flutter_starter_kit/6b72bc03a21a4802a4583ea51f3b2fd7c0e98f71/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KingWu/flutter_starter_kit/6b72bc03a21a4802a4583ea51f3b2fd7c0e98f71/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Flutter Starter Kit 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/staging/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | (Stg) Flutter Starter Kit 4 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.3' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | 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.4.1-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 | -------------------------------------------------------------------------------- /android/settings_aar.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /images/2.0x/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KingWu/flutter_starter_kit/6b72bc03a21a4802a4583ea51f3b2fd7c0e98f71/images/2.0x/test.png -------------------------------------------------------------------------------- /images/3.0x/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KingWu/flutter_starter_kit/6b72bc03a21a4802a4583ea51f3b2fd7c0e98f71/images/3.0x/test.png -------------------------------------------------------------------------------- /images/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KingWu/flutter_starter_kit/6b72bc03a21a4802a4583ea51f3b2fd7c0e98f71/images/test.png -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 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" -------------------------------------------------------------------------------- /ios/Flutter/development.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | 4 | APP_NAME=(Dev) Flutter Starter Kit 5 | BUNDLE_SUFFIX=.dev -------------------------------------------------------------------------------- /ios/Flutter/production.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | 4 | APP_NAME=Flutter Starter Kit 5 | -------------------------------------------------------------------------------- /ios/Flutter/staging.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | 4 | APP_NAME=(Stg) Flutter Starter Kit 5 | BUNDLE_SUFFIX=.stg 6 | -------------------------------------------------------------------------------- /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 parse_KV_file(file, separator='=') 14 | file_abs_path = File.expand_path(file) 15 | if !File.exists? file_abs_path 16 | return []; 17 | end 18 | pods_ary = [] 19 | skip_line_start_symbols = ["#", "/"] 20 | File.foreach(file_abs_path) { |line| 21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 22 | plugin = line.split(pattern=separator) 23 | if plugin.length == 2 24 | podname = plugin[0].strip() 25 | path = plugin[1].strip() 26 | podpath = File.expand_path("#{path}", file_abs_path) 27 | pods_ary.push({:name => podname, :path => podpath}); 28 | else 29 | puts "Invalid plugin specification: #{line}" 30 | end 31 | } 32 | return pods_ary 33 | end 34 | 35 | target 'Runner' do 36 | use_frameworks! 37 | 38 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 39 | # referring to absolute paths on developers' machines. 40 | system('rm -rf .symlinks') 41 | system('mkdir -p .symlinks/plugins') 42 | 43 | # Flutter Pods 44 | generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') 45 | if generated_xcode_build_settings.empty? 46 | puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." 47 | end 48 | generated_xcode_build_settings.map { |p| 49 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR' 50 | symlink = File.join('.symlinks', 'flutter') 51 | File.symlink(File.dirname(p[:path]), symlink) 52 | pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) 53 | end 54 | } 55 | 56 | # Plugin Pods 57 | plugin_pods = parse_KV_file('../.flutter-plugins') 58 | plugin_pods.map { |p| 59 | symlink = File.join('.symlinks', 'plugins', p[:name]) 60 | File.symlink(p[:path], symlink) 61 | pod p[:name], :path => File.join(symlink, 'ios') 62 | } 63 | end 64 | 65 | post_install do |installer| 66 | installer.pods_project.targets.each do |target| 67 | target.build_configurations.each do |config| 68 | config.build_settings['ENABLE_BITCODE'] = 'NO' 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /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 | 0FBC411921EB4A6A000179D3 /* staging.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 0FBC411721EB4A69000179D3 /* staging.xcconfig */; }; 11 | 0FBC411A21EB4A6A000179D3 /* development.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 0FBC411821EB4A69000179D3 /* development.xcconfig */; }; 12 | 0FBC411C21EB4AF6000179D3 /* production.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 0FBC411B21EB4AF6000179D3 /* production.xcconfig */; }; 13 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 14 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 15 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 16 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 17 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 18 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 19 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 20 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 21 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 22 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 23 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 24 | E29AE4AC9A85FC1CFCF25FAD /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7ECD58DBA383B8EBBA145492 /* Pods_Runner.framework */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXCopyFilesBuildPhase section */ 28 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 29 | isa = PBXCopyFilesBuildPhase; 30 | buildActionMask = 2147483647; 31 | dstPath = ""; 32 | dstSubfolderSpec = 10; 33 | files = ( 34 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, 35 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, 36 | ); 37 | name = "Embed Frameworks"; 38 | runOnlyForDeploymentPostprocessing = 0; 39 | }; 40 | /* End PBXCopyFilesBuildPhase section */ 41 | 42 | /* Begin PBXFileReference section */ 43 | 0F6C5F1921E4DCFA00DF5B0B /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Main.strings"; sourceTree = ""; }; 44 | 0F6C5F1A21E4DCFA00DF5B0B /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/LaunchScreen.strings"; sourceTree = ""; }; 45 | 0F6C5F1B21E4DD8100DF5B0B /* zh */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh; path = zh.lproj/Main.strings; sourceTree = ""; }; 46 | 0F6C5F1C21E4DD8100DF5B0B /* zh */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh; path = zh.lproj/LaunchScreen.strings; sourceTree = ""; }; 47 | 0F6C5F1D21E4DE5C00DF5B0B /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Main.strings; sourceTree = ""; }; 48 | 0F6C5F1E21E4DE5C00DF5B0B /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/LaunchScreen.strings; sourceTree = ""; }; 49 | 0FBC411721EB4A69000179D3 /* staging.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = staging.xcconfig; path = Flutter/staging.xcconfig; sourceTree = ""; }; 50 | 0FBC411821EB4A69000179D3 /* development.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = development.xcconfig; path = Flutter/development.xcconfig; sourceTree = ""; }; 51 | 0FBC411B21EB4AF6000179D3 /* production.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = production.xcconfig; path = Flutter/production.xcconfig; sourceTree = ""; }; 52 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 53 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 54 | 3869BA3A5C1AB17078566850 /* Pods-Runner.release-production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release-production.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release-production.xcconfig"; sourceTree = ""; }; 55 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 56 | 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 57 | 46A5597D1B2394C187A4C219 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 58 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 59 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 60 | 74C09DEB934CD08D1080A170 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 61 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 62 | 7D8E3A39A7A6516A2914838E /* Pods-Runner.release-development.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release-development.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release-development.xcconfig"; sourceTree = ""; }; 63 | 7ECD58DBA383B8EBBA145492 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 64 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 65 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 66 | 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 67 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 68 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 69 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 70 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 71 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 72 | C763E80E4F6EF24B95B02C49 /* Pods-Runner.debug-development.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug-development.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug-development.xcconfig"; sourceTree = ""; }; 73 | CA107340ABA465041774FF12 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 74 | E2BBAB54802AA2A60740E8F8 /* Pods-Runner.release-staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release-staging.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release-staging.xcconfig"; sourceTree = ""; }; 75 | F0906D8745EEF9740E6C5433 /* Pods-Runner.debug-production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug-production.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug-production.xcconfig"; sourceTree = ""; }; 76 | F6CC6E33F9346C7274E7135B /* Pods-Runner.debug-staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug-staging.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug-staging.xcconfig"; sourceTree = ""; }; 77 | FEC6451D21E984D400024C55 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Main.strings; sourceTree = ""; }; 78 | FEC6451E21E984D400024C55 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/LaunchScreen.strings; sourceTree = ""; }; 79 | /* End PBXFileReference section */ 80 | 81 | /* Begin PBXFrameworksBuildPhase section */ 82 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 83 | isa = PBXFrameworksBuildPhase; 84 | buildActionMask = 2147483647; 85 | files = ( 86 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 87 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 88 | E29AE4AC9A85FC1CFCF25FAD /* Pods_Runner.framework in Frameworks */, 89 | ); 90 | runOnlyForDeploymentPostprocessing = 0; 91 | }; 92 | /* End PBXFrameworksBuildPhase section */ 93 | 94 | /* Begin PBXGroup section */ 95 | 2E21882295FBF727AE8ACE40 /* Frameworks */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | 7ECD58DBA383B8EBBA145492 /* Pods_Runner.framework */, 99 | ); 100 | name = Frameworks; 101 | sourceTree = ""; 102 | }; 103 | 9740EEB11CF90186004384FC /* Flutter */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | 0FBC411B21EB4AF6000179D3 /* production.xcconfig */, 107 | 0FBC411821EB4A69000179D3 /* development.xcconfig */, 108 | 0FBC411721EB4A69000179D3 /* staging.xcconfig */, 109 | 3B80C3931E831B6300D905FE /* App.framework */, 110 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 111 | 9740EEBA1CF902C7004384FC /* Flutter.framework */, 112 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 113 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 114 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 115 | ); 116 | name = Flutter; 117 | sourceTree = ""; 118 | }; 119 | 97C146E51CF9000F007C117D = { 120 | isa = PBXGroup; 121 | children = ( 122 | 9740EEB11CF90186004384FC /* Flutter */, 123 | 97C146F01CF9000F007C117D /* Runner */, 124 | 97C146EF1CF9000F007C117D /* Products */, 125 | F25004F02FF280D4545A80CB /* Pods */, 126 | 2E21882295FBF727AE8ACE40 /* Frameworks */, 127 | ); 128 | sourceTree = ""; 129 | }; 130 | 97C146EF1CF9000F007C117D /* Products */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | 97C146EE1CF9000F007C117D /* Runner.app */, 134 | ); 135 | name = Products; 136 | sourceTree = ""; 137 | }; 138 | 97C146F01CF9000F007C117D /* Runner */ = { 139 | isa = PBXGroup; 140 | children = ( 141 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 142 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 143 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 144 | 97C147021CF9000F007C117D /* Info.plist */, 145 | 97C146F11CF9000F007C117D /* Supporting Files */, 146 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 147 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 148 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 149 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 150 | ); 151 | path = Runner; 152 | sourceTree = ""; 153 | }; 154 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 155 | isa = PBXGroup; 156 | children = ( 157 | ); 158 | name = "Supporting Files"; 159 | sourceTree = ""; 160 | }; 161 | F25004F02FF280D4545A80CB /* Pods */ = { 162 | isa = PBXGroup; 163 | children = ( 164 | 74C09DEB934CD08D1080A170 /* Pods-Runner.debug.xcconfig */, 165 | F0906D8745EEF9740E6C5433 /* Pods-Runner.debug-production.xcconfig */, 166 | F6CC6E33F9346C7274E7135B /* Pods-Runner.debug-staging.xcconfig */, 167 | C763E80E4F6EF24B95B02C49 /* Pods-Runner.debug-development.xcconfig */, 168 | CA107340ABA465041774FF12 /* Pods-Runner.release.xcconfig */, 169 | 3869BA3A5C1AB17078566850 /* Pods-Runner.release-production.xcconfig */, 170 | E2BBAB54802AA2A60740E8F8 /* Pods-Runner.release-staging.xcconfig */, 171 | 7D8E3A39A7A6516A2914838E /* Pods-Runner.release-development.xcconfig */, 172 | 46A5597D1B2394C187A4C219 /* Pods-Runner.profile.xcconfig */, 173 | ); 174 | name = Pods; 175 | sourceTree = ""; 176 | }; 177 | /* End PBXGroup section */ 178 | 179 | /* Begin PBXNativeTarget section */ 180 | 97C146ED1CF9000F007C117D /* Runner */ = { 181 | isa = PBXNativeTarget; 182 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 183 | buildPhases = ( 184 | EF2CE564A429415D583034EC /* [CP] Check Pods Manifest.lock */, 185 | 9740EEB61CF901F6004384FC /* Run Script */, 186 | 97C146EA1CF9000F007C117D /* Sources */, 187 | 97C146EB1CF9000F007C117D /* Frameworks */, 188 | 97C146EC1CF9000F007C117D /* Resources */, 189 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 190 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 191 | F322E670619037123FCEF213 /* [CP] Embed Pods Frameworks */, 192 | ); 193 | buildRules = ( 194 | ); 195 | dependencies = ( 196 | ); 197 | name = Runner; 198 | productName = Runner; 199 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 200 | productType = "com.apple.product-type.application"; 201 | }; 202 | /* End PBXNativeTarget section */ 203 | 204 | /* Begin PBXProject section */ 205 | 97C146E61CF9000F007C117D /* Project object */ = { 206 | isa = PBXProject; 207 | attributes = { 208 | LastUpgradeCheck = 0910; 209 | ORGANIZATIONNAME = "The Chromium Authors"; 210 | TargetAttributes = { 211 | 97C146ED1CF9000F007C117D = { 212 | CreatedOnToolsVersion = 7.3.1; 213 | LastSwiftMigration = 0910; 214 | }; 215 | }; 216 | }; 217 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 218 | compatibilityVersion = "Xcode 3.2"; 219 | developmentRegion = English; 220 | hasScannedForEncodings = 0; 221 | knownRegions = ( 222 | en, 223 | Base, 224 | ); 225 | mainGroup = 97C146E51CF9000F007C117D; 226 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 227 | projectDirPath = ""; 228 | projectRoot = ""; 229 | targets = ( 230 | 97C146ED1CF9000F007C117D /* Runner */, 231 | ); 232 | }; 233 | /* End PBXProject section */ 234 | 235 | /* Begin PBXResourcesBuildPhase section */ 236 | 97C146EC1CF9000F007C117D /* Resources */ = { 237 | isa = PBXResourcesBuildPhase; 238 | buildActionMask = 2147483647; 239 | files = ( 240 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 241 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 242 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 243 | 0FBC411921EB4A6A000179D3 /* staging.xcconfig in Resources */, 244 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 245 | 0FBC411A21EB4A6A000179D3 /* development.xcconfig in Resources */, 246 | 0FBC411C21EB4AF6000179D3 /* production.xcconfig in Resources */, 247 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 248 | ); 249 | runOnlyForDeploymentPostprocessing = 0; 250 | }; 251 | /* End PBXResourcesBuildPhase section */ 252 | 253 | /* Begin PBXShellScriptBuildPhase section */ 254 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 255 | isa = PBXShellScriptBuildPhase; 256 | buildActionMask = 2147483647; 257 | files = ( 258 | ); 259 | inputPaths = ( 260 | ); 261 | name = "Thin Binary"; 262 | outputPaths = ( 263 | ); 264 | runOnlyForDeploymentPostprocessing = 0; 265 | shellPath = /bin/sh; 266 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; 267 | }; 268 | 9740EEB61CF901F6004384FC /* Run Script */ = { 269 | isa = PBXShellScriptBuildPhase; 270 | buildActionMask = 2147483647; 271 | files = ( 272 | ); 273 | inputPaths = ( 274 | ); 275 | name = "Run Script"; 276 | outputPaths = ( 277 | ); 278 | runOnlyForDeploymentPostprocessing = 0; 279 | shellPath = /bin/sh; 280 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 281 | }; 282 | EF2CE564A429415D583034EC /* [CP] Check Pods Manifest.lock */ = { 283 | isa = PBXShellScriptBuildPhase; 284 | buildActionMask = 2147483647; 285 | files = ( 286 | ); 287 | inputPaths = ( 288 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 289 | "${PODS_ROOT}/Manifest.lock", 290 | ); 291 | name = "[CP] Check Pods Manifest.lock"; 292 | outputPaths = ( 293 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 294 | ); 295 | runOnlyForDeploymentPostprocessing = 0; 296 | shellPath = /bin/sh; 297 | 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"; 298 | showEnvVarsInLog = 0; 299 | }; 300 | F322E670619037123FCEF213 /* [CP] Embed Pods Frameworks */ = { 301 | isa = PBXShellScriptBuildPhase; 302 | buildActionMask = 2147483647; 303 | files = ( 304 | ); 305 | inputPaths = ( 306 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", 307 | "${BUILT_PRODUCTS_DIR}/FMDB/FMDB.framework", 308 | "${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework", 309 | "${BUILT_PRODUCTS_DIR}/flutter_stetho/flutter_stetho.framework", 310 | "${BUILT_PRODUCTS_DIR}/path_provider/path_provider.framework", 311 | "${BUILT_PRODUCTS_DIR}/sqflite/sqflite.framework", 312 | ); 313 | name = "[CP] Embed Pods Frameworks"; 314 | outputPaths = ( 315 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FMDB.framework", 316 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", 317 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_stetho.framework", 318 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider.framework", 319 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqflite.framework", 320 | ); 321 | runOnlyForDeploymentPostprocessing = 0; 322 | shellPath = /bin/sh; 323 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 324 | showEnvVarsInLog = 0; 325 | }; 326 | /* End PBXShellScriptBuildPhase section */ 327 | 328 | /* Begin PBXSourcesBuildPhase section */ 329 | 97C146EA1CF9000F007C117D /* Sources */ = { 330 | isa = PBXSourcesBuildPhase; 331 | buildActionMask = 2147483647; 332 | files = ( 333 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 334 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 335 | ); 336 | runOnlyForDeploymentPostprocessing = 0; 337 | }; 338 | /* End PBXSourcesBuildPhase section */ 339 | 340 | /* Begin PBXVariantGroup section */ 341 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 342 | isa = PBXVariantGroup; 343 | children = ( 344 | 97C146FB1CF9000F007C117D /* Base */, 345 | 0F6C5F1921E4DCFA00DF5B0B /* zh-Hant */, 346 | 0F6C5F1B21E4DD8100DF5B0B /* zh */, 347 | 0F6C5F1D21E4DE5C00DF5B0B /* de */, 348 | FEC6451D21E984D400024C55 /* ja */, 349 | ); 350 | name = Main.storyboard; 351 | sourceTree = ""; 352 | }; 353 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 354 | isa = PBXVariantGroup; 355 | children = ( 356 | 97C147001CF9000F007C117D /* Base */, 357 | 0F6C5F1A21E4DCFA00DF5B0B /* zh-Hant */, 358 | 0F6C5F1C21E4DD8100DF5B0B /* zh */, 359 | 0F6C5F1E21E4DE5C00DF5B0B /* de */, 360 | FEC6451E21E984D400024C55 /* ja */, 361 | ); 362 | name = LaunchScreen.storyboard; 363 | sourceTree = ""; 364 | }; 365 | /* End PBXVariantGroup section */ 366 | 367 | /* Begin XCBuildConfiguration section */ 368 | 0FBC411D21EB4C21000179D3 /* Debug-development */ = { 369 | isa = XCBuildConfiguration; 370 | baseConfigurationReference = 0FBC411821EB4A69000179D3 /* development.xcconfig */; 371 | buildSettings = { 372 | ALWAYS_SEARCH_USER_PATHS = NO; 373 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 374 | CLANG_ANALYZER_NONNULL = YES; 375 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 376 | CLANG_CXX_LIBRARY = "libc++"; 377 | CLANG_ENABLE_MODULES = YES; 378 | CLANG_ENABLE_OBJC_ARC = YES; 379 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 380 | CLANG_WARN_BOOL_CONVERSION = YES; 381 | CLANG_WARN_COMMA = YES; 382 | CLANG_WARN_CONSTANT_CONVERSION = YES; 383 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 384 | CLANG_WARN_EMPTY_BODY = YES; 385 | CLANG_WARN_ENUM_CONVERSION = YES; 386 | CLANG_WARN_INFINITE_RECURSION = YES; 387 | CLANG_WARN_INT_CONVERSION = YES; 388 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 389 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 390 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 391 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 392 | CLANG_WARN_STRICT_PROTOTYPES = YES; 393 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 394 | CLANG_WARN_UNREACHABLE_CODE = YES; 395 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 396 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 397 | COPY_PHASE_STRIP = NO; 398 | DEBUG_INFORMATION_FORMAT = dwarf; 399 | ENABLE_STRICT_OBJC_MSGSEND = YES; 400 | ENABLE_TESTABILITY = YES; 401 | GCC_C_LANGUAGE_STANDARD = gnu99; 402 | GCC_DYNAMIC_NO_PIC = NO; 403 | GCC_NO_COMMON_BLOCKS = YES; 404 | GCC_OPTIMIZATION_LEVEL = 0; 405 | GCC_PREPROCESSOR_DEFINITIONS = ( 406 | "DEBUG=1", 407 | "$(inherited)", 408 | ); 409 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 410 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 411 | GCC_WARN_UNDECLARED_SELECTOR = YES; 412 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 413 | GCC_WARN_UNUSED_FUNCTION = YES; 414 | GCC_WARN_UNUSED_VARIABLE = YES; 415 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 416 | MTL_ENABLE_DEBUG_INFO = YES; 417 | ONLY_ACTIVE_ARCH = YES; 418 | SDKROOT = iphoneos; 419 | TARGETED_DEVICE_FAMILY = "1,2"; 420 | }; 421 | name = "Debug-development"; 422 | }; 423 | 0FBC411E21EB4C21000179D3 /* Debug-development */ = { 424 | isa = XCBuildConfiguration; 425 | baseConfigurationReference = 0FBC411821EB4A69000179D3 /* development.xcconfig */; 426 | buildSettings = { 427 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 428 | CLANG_ENABLE_MODULES = YES; 429 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 430 | ENABLE_BITCODE = NO; 431 | FRAMEWORK_SEARCH_PATHS = ( 432 | "$(inherited)", 433 | "$(PROJECT_DIR)/Flutter", 434 | ); 435 | INFOPLIST_FILE = Runner/Info.plist; 436 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 437 | LIBRARY_SEARCH_PATHS = ( 438 | "$(inherited)", 439 | "$(PROJECT_DIR)/Flutter", 440 | ); 441 | PRODUCT_BUNDLE_IDENTIFIER = "com.test.flutterstarterkit$(BUNDLE_SUFFIX)"; 442 | PRODUCT_NAME = "$(TARGET_NAME)"; 443 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 444 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 445 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 446 | SWIFT_VERSION = 4.0; 447 | VERSIONING_SYSTEM = "apple-generic"; 448 | }; 449 | name = "Debug-development"; 450 | }; 451 | 0FBC411F21EB4C52000179D3 /* Release-development */ = { 452 | isa = XCBuildConfiguration; 453 | baseConfigurationReference = 0FBC411821EB4A69000179D3 /* development.xcconfig */; 454 | buildSettings = { 455 | ALWAYS_SEARCH_USER_PATHS = NO; 456 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 457 | CLANG_ANALYZER_NONNULL = YES; 458 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 459 | CLANG_CXX_LIBRARY = "libc++"; 460 | CLANG_ENABLE_MODULES = YES; 461 | CLANG_ENABLE_OBJC_ARC = YES; 462 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 463 | CLANG_WARN_BOOL_CONVERSION = YES; 464 | CLANG_WARN_COMMA = YES; 465 | CLANG_WARN_CONSTANT_CONVERSION = YES; 466 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 467 | CLANG_WARN_EMPTY_BODY = YES; 468 | CLANG_WARN_ENUM_CONVERSION = YES; 469 | CLANG_WARN_INFINITE_RECURSION = YES; 470 | CLANG_WARN_INT_CONVERSION = YES; 471 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 472 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 473 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 474 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 475 | CLANG_WARN_STRICT_PROTOTYPES = YES; 476 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 477 | CLANG_WARN_UNREACHABLE_CODE = YES; 478 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 479 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 480 | COPY_PHASE_STRIP = NO; 481 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 482 | ENABLE_NS_ASSERTIONS = NO; 483 | ENABLE_STRICT_OBJC_MSGSEND = YES; 484 | GCC_C_LANGUAGE_STANDARD = gnu99; 485 | GCC_NO_COMMON_BLOCKS = YES; 486 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 487 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 488 | GCC_WARN_UNDECLARED_SELECTOR = YES; 489 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 490 | GCC_WARN_UNUSED_FUNCTION = YES; 491 | GCC_WARN_UNUSED_VARIABLE = YES; 492 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 493 | MTL_ENABLE_DEBUG_INFO = NO; 494 | SDKROOT = iphoneos; 495 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 496 | TARGETED_DEVICE_FAMILY = "1,2"; 497 | VALIDATE_PRODUCT = YES; 498 | }; 499 | name = "Release-development"; 500 | }; 501 | 0FBC412021EB4C52000179D3 /* Release-development */ = { 502 | isa = XCBuildConfiguration; 503 | baseConfigurationReference = 0FBC411821EB4A69000179D3 /* development.xcconfig */; 504 | buildSettings = { 505 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 506 | CLANG_ENABLE_MODULES = YES; 507 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 508 | ENABLE_BITCODE = NO; 509 | FRAMEWORK_SEARCH_PATHS = ( 510 | "$(inherited)", 511 | "$(PROJECT_DIR)/Flutter", 512 | ); 513 | INFOPLIST_FILE = Runner/Info.plist; 514 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 515 | LIBRARY_SEARCH_PATHS = ( 516 | "$(inherited)", 517 | "$(PROJECT_DIR)/Flutter", 518 | ); 519 | PRODUCT_BUNDLE_IDENTIFIER = "com.test.flutterstarterkit$(BUNDLE_SUFFIX)"; 520 | PRODUCT_NAME = "$(TARGET_NAME)"; 521 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 522 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 523 | SWIFT_VERSION = 4.0; 524 | VERSIONING_SYSTEM = "apple-generic"; 525 | }; 526 | name = "Release-development"; 527 | }; 528 | 0FBC412121EB4C64000179D3 /* Debug-staging */ = { 529 | isa = XCBuildConfiguration; 530 | baseConfigurationReference = 0FBC411721EB4A69000179D3 /* staging.xcconfig */; 531 | buildSettings = { 532 | ALWAYS_SEARCH_USER_PATHS = NO; 533 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 534 | CLANG_ANALYZER_NONNULL = YES; 535 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 536 | CLANG_CXX_LIBRARY = "libc++"; 537 | CLANG_ENABLE_MODULES = YES; 538 | CLANG_ENABLE_OBJC_ARC = YES; 539 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 540 | CLANG_WARN_BOOL_CONVERSION = YES; 541 | CLANG_WARN_COMMA = YES; 542 | CLANG_WARN_CONSTANT_CONVERSION = YES; 543 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 544 | CLANG_WARN_EMPTY_BODY = YES; 545 | CLANG_WARN_ENUM_CONVERSION = YES; 546 | CLANG_WARN_INFINITE_RECURSION = YES; 547 | CLANG_WARN_INT_CONVERSION = YES; 548 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 549 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 550 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 551 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 552 | CLANG_WARN_STRICT_PROTOTYPES = YES; 553 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 554 | CLANG_WARN_UNREACHABLE_CODE = YES; 555 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 556 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 557 | COPY_PHASE_STRIP = NO; 558 | DEBUG_INFORMATION_FORMAT = dwarf; 559 | ENABLE_STRICT_OBJC_MSGSEND = YES; 560 | ENABLE_TESTABILITY = YES; 561 | GCC_C_LANGUAGE_STANDARD = gnu99; 562 | GCC_DYNAMIC_NO_PIC = NO; 563 | GCC_NO_COMMON_BLOCKS = YES; 564 | GCC_OPTIMIZATION_LEVEL = 0; 565 | GCC_PREPROCESSOR_DEFINITIONS = ( 566 | "DEBUG=1", 567 | "$(inherited)", 568 | ); 569 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 570 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 571 | GCC_WARN_UNDECLARED_SELECTOR = YES; 572 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 573 | GCC_WARN_UNUSED_FUNCTION = YES; 574 | GCC_WARN_UNUSED_VARIABLE = YES; 575 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 576 | MTL_ENABLE_DEBUG_INFO = YES; 577 | ONLY_ACTIVE_ARCH = YES; 578 | SDKROOT = iphoneos; 579 | TARGETED_DEVICE_FAMILY = "1,2"; 580 | }; 581 | name = "Debug-staging"; 582 | }; 583 | 0FBC412221EB4C64000179D3 /* Debug-staging */ = { 584 | isa = XCBuildConfiguration; 585 | baseConfigurationReference = 0FBC411721EB4A69000179D3 /* staging.xcconfig */; 586 | buildSettings = { 587 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 588 | CLANG_ENABLE_MODULES = YES; 589 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 590 | ENABLE_BITCODE = NO; 591 | FRAMEWORK_SEARCH_PATHS = ( 592 | "$(inherited)", 593 | "$(PROJECT_DIR)/Flutter", 594 | ); 595 | INFOPLIST_FILE = Runner/Info.plist; 596 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 597 | LIBRARY_SEARCH_PATHS = ( 598 | "$(inherited)", 599 | "$(PROJECT_DIR)/Flutter", 600 | ); 601 | PRODUCT_BUNDLE_IDENTIFIER = "com.test.flutterstarterkit$(BUNDLE_SUFFIX)"; 602 | PRODUCT_NAME = "$(TARGET_NAME)"; 603 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 604 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 605 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 606 | SWIFT_VERSION = 4.0; 607 | VERSIONING_SYSTEM = "apple-generic"; 608 | }; 609 | name = "Debug-staging"; 610 | }; 611 | 0FBC412321EB4C75000179D3 /* Release-staging */ = { 612 | isa = XCBuildConfiguration; 613 | baseConfigurationReference = 0FBC411721EB4A69000179D3 /* staging.xcconfig */; 614 | buildSettings = { 615 | ALWAYS_SEARCH_USER_PATHS = NO; 616 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 617 | CLANG_ANALYZER_NONNULL = YES; 618 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 619 | CLANG_CXX_LIBRARY = "libc++"; 620 | CLANG_ENABLE_MODULES = YES; 621 | CLANG_ENABLE_OBJC_ARC = YES; 622 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 623 | CLANG_WARN_BOOL_CONVERSION = YES; 624 | CLANG_WARN_COMMA = YES; 625 | CLANG_WARN_CONSTANT_CONVERSION = YES; 626 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 627 | CLANG_WARN_EMPTY_BODY = YES; 628 | CLANG_WARN_ENUM_CONVERSION = YES; 629 | CLANG_WARN_INFINITE_RECURSION = YES; 630 | CLANG_WARN_INT_CONVERSION = YES; 631 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 632 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 633 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 634 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 635 | CLANG_WARN_STRICT_PROTOTYPES = YES; 636 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 637 | CLANG_WARN_UNREACHABLE_CODE = YES; 638 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 639 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 640 | COPY_PHASE_STRIP = NO; 641 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 642 | ENABLE_NS_ASSERTIONS = NO; 643 | ENABLE_STRICT_OBJC_MSGSEND = YES; 644 | GCC_C_LANGUAGE_STANDARD = gnu99; 645 | GCC_NO_COMMON_BLOCKS = YES; 646 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 647 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 648 | GCC_WARN_UNDECLARED_SELECTOR = YES; 649 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 650 | GCC_WARN_UNUSED_FUNCTION = YES; 651 | GCC_WARN_UNUSED_VARIABLE = YES; 652 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 653 | MTL_ENABLE_DEBUG_INFO = NO; 654 | SDKROOT = iphoneos; 655 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 656 | TARGETED_DEVICE_FAMILY = "1,2"; 657 | VALIDATE_PRODUCT = YES; 658 | }; 659 | name = "Release-staging"; 660 | }; 661 | 0FBC412421EB4C75000179D3 /* Release-staging */ = { 662 | isa = XCBuildConfiguration; 663 | baseConfigurationReference = 0FBC411721EB4A69000179D3 /* staging.xcconfig */; 664 | buildSettings = { 665 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 666 | CLANG_ENABLE_MODULES = YES; 667 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 668 | ENABLE_BITCODE = NO; 669 | FRAMEWORK_SEARCH_PATHS = ( 670 | "$(inherited)", 671 | "$(PROJECT_DIR)/Flutter", 672 | ); 673 | INFOPLIST_FILE = Runner/Info.plist; 674 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 675 | LIBRARY_SEARCH_PATHS = ( 676 | "$(inherited)", 677 | "$(PROJECT_DIR)/Flutter", 678 | ); 679 | PRODUCT_BUNDLE_IDENTIFIER = "com.test.flutterstarterkit$(BUNDLE_SUFFIX)"; 680 | PRODUCT_NAME = "$(TARGET_NAME)"; 681 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 682 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 683 | SWIFT_VERSION = 4.0; 684 | VERSIONING_SYSTEM = "apple-generic"; 685 | }; 686 | name = "Release-staging"; 687 | }; 688 | 0FBC412521EB4C83000179D3 /* Debug-production */ = { 689 | isa = XCBuildConfiguration; 690 | baseConfigurationReference = 0FBC411B21EB4AF6000179D3 /* production.xcconfig */; 691 | buildSettings = { 692 | ALWAYS_SEARCH_USER_PATHS = NO; 693 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 694 | CLANG_ANALYZER_NONNULL = YES; 695 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 696 | CLANG_CXX_LIBRARY = "libc++"; 697 | CLANG_ENABLE_MODULES = YES; 698 | CLANG_ENABLE_OBJC_ARC = YES; 699 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 700 | CLANG_WARN_BOOL_CONVERSION = YES; 701 | CLANG_WARN_COMMA = YES; 702 | CLANG_WARN_CONSTANT_CONVERSION = YES; 703 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 704 | CLANG_WARN_EMPTY_BODY = YES; 705 | CLANG_WARN_ENUM_CONVERSION = YES; 706 | CLANG_WARN_INFINITE_RECURSION = YES; 707 | CLANG_WARN_INT_CONVERSION = YES; 708 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 709 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 710 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 711 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 712 | CLANG_WARN_STRICT_PROTOTYPES = YES; 713 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 714 | CLANG_WARN_UNREACHABLE_CODE = YES; 715 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 716 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 717 | COPY_PHASE_STRIP = NO; 718 | DEBUG_INFORMATION_FORMAT = dwarf; 719 | ENABLE_STRICT_OBJC_MSGSEND = YES; 720 | ENABLE_TESTABILITY = YES; 721 | GCC_C_LANGUAGE_STANDARD = gnu99; 722 | GCC_DYNAMIC_NO_PIC = NO; 723 | GCC_NO_COMMON_BLOCKS = YES; 724 | GCC_OPTIMIZATION_LEVEL = 0; 725 | GCC_PREPROCESSOR_DEFINITIONS = ( 726 | "DEBUG=1", 727 | "$(inherited)", 728 | ); 729 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 730 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 731 | GCC_WARN_UNDECLARED_SELECTOR = YES; 732 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 733 | GCC_WARN_UNUSED_FUNCTION = YES; 734 | GCC_WARN_UNUSED_VARIABLE = YES; 735 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 736 | MTL_ENABLE_DEBUG_INFO = YES; 737 | ONLY_ACTIVE_ARCH = YES; 738 | SDKROOT = iphoneos; 739 | TARGETED_DEVICE_FAMILY = "1,2"; 740 | }; 741 | name = "Debug-production"; 742 | }; 743 | 0FBC412621EB4C83000179D3 /* Debug-production */ = { 744 | isa = XCBuildConfiguration; 745 | baseConfigurationReference = 0FBC411B21EB4AF6000179D3 /* production.xcconfig */; 746 | buildSettings = { 747 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 748 | CLANG_ENABLE_MODULES = YES; 749 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 750 | ENABLE_BITCODE = NO; 751 | FRAMEWORK_SEARCH_PATHS = ( 752 | "$(inherited)", 753 | "$(PROJECT_DIR)/Flutter", 754 | ); 755 | INFOPLIST_FILE = Runner/Info.plist; 756 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 757 | LIBRARY_SEARCH_PATHS = ( 758 | "$(inherited)", 759 | "$(PROJECT_DIR)/Flutter", 760 | ); 761 | PRODUCT_BUNDLE_IDENTIFIER = "com.test.flutterstarterkit$(BUNDLE_SUFFIX)"; 762 | PRODUCT_NAME = "$(TARGET_NAME)"; 763 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 764 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 765 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 766 | SWIFT_VERSION = 4.0; 767 | VERSIONING_SYSTEM = "apple-generic"; 768 | }; 769 | name = "Debug-production"; 770 | }; 771 | 0FBC412721EB4C90000179D3 /* Release-production */ = { 772 | isa = XCBuildConfiguration; 773 | baseConfigurationReference = 0FBC411B21EB4AF6000179D3 /* production.xcconfig */; 774 | buildSettings = { 775 | ALWAYS_SEARCH_USER_PATHS = NO; 776 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 777 | CLANG_ANALYZER_NONNULL = YES; 778 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 779 | CLANG_CXX_LIBRARY = "libc++"; 780 | CLANG_ENABLE_MODULES = YES; 781 | CLANG_ENABLE_OBJC_ARC = YES; 782 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 783 | CLANG_WARN_BOOL_CONVERSION = YES; 784 | CLANG_WARN_COMMA = YES; 785 | CLANG_WARN_CONSTANT_CONVERSION = YES; 786 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 787 | CLANG_WARN_EMPTY_BODY = YES; 788 | CLANG_WARN_ENUM_CONVERSION = YES; 789 | CLANG_WARN_INFINITE_RECURSION = YES; 790 | CLANG_WARN_INT_CONVERSION = YES; 791 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 792 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 793 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 794 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 795 | CLANG_WARN_STRICT_PROTOTYPES = YES; 796 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 797 | CLANG_WARN_UNREACHABLE_CODE = YES; 798 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 799 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 800 | COPY_PHASE_STRIP = NO; 801 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 802 | ENABLE_NS_ASSERTIONS = NO; 803 | ENABLE_STRICT_OBJC_MSGSEND = YES; 804 | GCC_C_LANGUAGE_STANDARD = gnu99; 805 | GCC_NO_COMMON_BLOCKS = YES; 806 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 807 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 808 | GCC_WARN_UNDECLARED_SELECTOR = YES; 809 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 810 | GCC_WARN_UNUSED_FUNCTION = YES; 811 | GCC_WARN_UNUSED_VARIABLE = YES; 812 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 813 | MTL_ENABLE_DEBUG_INFO = NO; 814 | SDKROOT = iphoneos; 815 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 816 | TARGETED_DEVICE_FAMILY = "1,2"; 817 | VALIDATE_PRODUCT = YES; 818 | }; 819 | name = "Release-production"; 820 | }; 821 | 0FBC412821EB4C90000179D3 /* Release-production */ = { 822 | isa = XCBuildConfiguration; 823 | baseConfigurationReference = 0FBC411B21EB4AF6000179D3 /* production.xcconfig */; 824 | buildSettings = { 825 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 826 | CLANG_ENABLE_MODULES = YES; 827 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 828 | ENABLE_BITCODE = NO; 829 | FRAMEWORK_SEARCH_PATHS = ( 830 | "$(inherited)", 831 | "$(PROJECT_DIR)/Flutter", 832 | ); 833 | INFOPLIST_FILE = Runner/Info.plist; 834 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 835 | LIBRARY_SEARCH_PATHS = ( 836 | "$(inherited)", 837 | "$(PROJECT_DIR)/Flutter", 838 | ); 839 | PRODUCT_BUNDLE_IDENTIFIER = "com.test.flutterstarterkit$(BUNDLE_SUFFIX)"; 840 | PRODUCT_NAME = "$(TARGET_NAME)"; 841 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 842 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 843 | SWIFT_VERSION = 4.0; 844 | VERSIONING_SYSTEM = "apple-generic"; 845 | }; 846 | name = "Release-production"; 847 | }; 848 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 849 | isa = XCBuildConfiguration; 850 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 851 | buildSettings = { 852 | ALWAYS_SEARCH_USER_PATHS = NO; 853 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 854 | CLANG_ANALYZER_NONNULL = YES; 855 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 856 | CLANG_CXX_LIBRARY = "libc++"; 857 | CLANG_ENABLE_MODULES = YES; 858 | CLANG_ENABLE_OBJC_ARC = YES; 859 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 860 | CLANG_WARN_BOOL_CONVERSION = YES; 861 | CLANG_WARN_COMMA = YES; 862 | CLANG_WARN_CONSTANT_CONVERSION = YES; 863 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 864 | CLANG_WARN_EMPTY_BODY = YES; 865 | CLANG_WARN_ENUM_CONVERSION = YES; 866 | CLANG_WARN_INFINITE_RECURSION = YES; 867 | CLANG_WARN_INT_CONVERSION = YES; 868 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 869 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 870 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 871 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 872 | CLANG_WARN_STRICT_PROTOTYPES = YES; 873 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 874 | CLANG_WARN_UNREACHABLE_CODE = YES; 875 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 876 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 877 | COPY_PHASE_STRIP = NO; 878 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 879 | ENABLE_NS_ASSERTIONS = NO; 880 | ENABLE_STRICT_OBJC_MSGSEND = YES; 881 | GCC_C_LANGUAGE_STANDARD = gnu99; 882 | GCC_NO_COMMON_BLOCKS = YES; 883 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 884 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 885 | GCC_WARN_UNDECLARED_SELECTOR = YES; 886 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 887 | GCC_WARN_UNUSED_FUNCTION = YES; 888 | GCC_WARN_UNUSED_VARIABLE = YES; 889 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 890 | MTL_ENABLE_DEBUG_INFO = NO; 891 | SDKROOT = iphoneos; 892 | TARGETED_DEVICE_FAMILY = "1,2"; 893 | VALIDATE_PRODUCT = YES; 894 | }; 895 | name = Profile; 896 | }; 897 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 898 | isa = XCBuildConfiguration; 899 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 900 | buildSettings = { 901 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 902 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 903 | DEVELOPMENT_TEAM = S8QB4VV633; 904 | ENABLE_BITCODE = NO; 905 | FRAMEWORK_SEARCH_PATHS = ( 906 | "$(inherited)", 907 | "$(PROJECT_DIR)/Flutter", 908 | ); 909 | INFOPLIST_FILE = Runner/Info.plist; 910 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 911 | LIBRARY_SEARCH_PATHS = ( 912 | "$(inherited)", 913 | "$(PROJECT_DIR)/Flutter", 914 | ); 915 | PRODUCT_BUNDLE_IDENTIFIER = "com.test.flutterstarterkit$(BUNDLE_SUFFIX)"; 916 | PRODUCT_NAME = "$(TARGET_NAME)"; 917 | SWIFT_VERSION = 4.0; 918 | VERSIONING_SYSTEM = "apple-generic"; 919 | }; 920 | name = Profile; 921 | }; 922 | 97C147031CF9000F007C117D /* Debug */ = { 923 | isa = XCBuildConfiguration; 924 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 925 | buildSettings = { 926 | ALWAYS_SEARCH_USER_PATHS = NO; 927 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 928 | CLANG_ANALYZER_NONNULL = YES; 929 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 930 | CLANG_CXX_LIBRARY = "libc++"; 931 | CLANG_ENABLE_MODULES = YES; 932 | CLANG_ENABLE_OBJC_ARC = YES; 933 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 934 | CLANG_WARN_BOOL_CONVERSION = YES; 935 | CLANG_WARN_COMMA = YES; 936 | CLANG_WARN_CONSTANT_CONVERSION = YES; 937 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 938 | CLANG_WARN_EMPTY_BODY = YES; 939 | CLANG_WARN_ENUM_CONVERSION = YES; 940 | CLANG_WARN_INFINITE_RECURSION = YES; 941 | CLANG_WARN_INT_CONVERSION = YES; 942 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 943 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 944 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 945 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 946 | CLANG_WARN_STRICT_PROTOTYPES = YES; 947 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 948 | CLANG_WARN_UNREACHABLE_CODE = YES; 949 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 950 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 951 | COPY_PHASE_STRIP = NO; 952 | DEBUG_INFORMATION_FORMAT = dwarf; 953 | ENABLE_STRICT_OBJC_MSGSEND = YES; 954 | ENABLE_TESTABILITY = YES; 955 | GCC_C_LANGUAGE_STANDARD = gnu99; 956 | GCC_DYNAMIC_NO_PIC = NO; 957 | GCC_NO_COMMON_BLOCKS = YES; 958 | GCC_OPTIMIZATION_LEVEL = 0; 959 | GCC_PREPROCESSOR_DEFINITIONS = ( 960 | "DEBUG=1", 961 | "$(inherited)", 962 | ); 963 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 964 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 965 | GCC_WARN_UNDECLARED_SELECTOR = YES; 966 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 967 | GCC_WARN_UNUSED_FUNCTION = YES; 968 | GCC_WARN_UNUSED_VARIABLE = YES; 969 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 970 | MTL_ENABLE_DEBUG_INFO = YES; 971 | ONLY_ACTIVE_ARCH = YES; 972 | SDKROOT = iphoneos; 973 | TARGETED_DEVICE_FAMILY = "1,2"; 974 | }; 975 | name = Debug; 976 | }; 977 | 97C147041CF9000F007C117D /* Release */ = { 978 | isa = XCBuildConfiguration; 979 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 980 | buildSettings = { 981 | ALWAYS_SEARCH_USER_PATHS = NO; 982 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 983 | CLANG_ANALYZER_NONNULL = YES; 984 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 985 | CLANG_CXX_LIBRARY = "libc++"; 986 | CLANG_ENABLE_MODULES = YES; 987 | CLANG_ENABLE_OBJC_ARC = YES; 988 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 989 | CLANG_WARN_BOOL_CONVERSION = YES; 990 | CLANG_WARN_COMMA = YES; 991 | CLANG_WARN_CONSTANT_CONVERSION = YES; 992 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 993 | CLANG_WARN_EMPTY_BODY = YES; 994 | CLANG_WARN_ENUM_CONVERSION = YES; 995 | CLANG_WARN_INFINITE_RECURSION = YES; 996 | CLANG_WARN_INT_CONVERSION = YES; 997 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 998 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 999 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 1000 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 1001 | CLANG_WARN_STRICT_PROTOTYPES = YES; 1002 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 1003 | CLANG_WARN_UNREACHABLE_CODE = YES; 1004 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 1005 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 1006 | COPY_PHASE_STRIP = NO; 1007 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 1008 | ENABLE_NS_ASSERTIONS = NO; 1009 | ENABLE_STRICT_OBJC_MSGSEND = YES; 1010 | GCC_C_LANGUAGE_STANDARD = gnu99; 1011 | GCC_NO_COMMON_BLOCKS = YES; 1012 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 1013 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 1014 | GCC_WARN_UNDECLARED_SELECTOR = YES; 1015 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 1016 | GCC_WARN_UNUSED_FUNCTION = YES; 1017 | GCC_WARN_UNUSED_VARIABLE = YES; 1018 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 1019 | MTL_ENABLE_DEBUG_INFO = NO; 1020 | SDKROOT = iphoneos; 1021 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 1022 | TARGETED_DEVICE_FAMILY = "1,2"; 1023 | VALIDATE_PRODUCT = YES; 1024 | }; 1025 | name = Release; 1026 | }; 1027 | 97C147061CF9000F007C117D /* Debug */ = { 1028 | isa = XCBuildConfiguration; 1029 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 1030 | buildSettings = { 1031 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 1032 | CLANG_ENABLE_MODULES = YES; 1033 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 1034 | ENABLE_BITCODE = NO; 1035 | FRAMEWORK_SEARCH_PATHS = ( 1036 | "$(inherited)", 1037 | "$(PROJECT_DIR)/Flutter", 1038 | ); 1039 | INFOPLIST_FILE = Runner/Info.plist; 1040 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 1041 | LIBRARY_SEARCH_PATHS = ( 1042 | "$(inherited)", 1043 | "$(PROJECT_DIR)/Flutter", 1044 | ); 1045 | PRODUCT_BUNDLE_IDENTIFIER = "com.test.flutterstarterkit$(BUNDLE_SUFFIX)"; 1046 | PRODUCT_NAME = "$(TARGET_NAME)"; 1047 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 1048 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 1049 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 1050 | SWIFT_VERSION = 4.0; 1051 | VERSIONING_SYSTEM = "apple-generic"; 1052 | }; 1053 | name = Debug; 1054 | }; 1055 | 97C147071CF9000F007C117D /* Release */ = { 1056 | isa = XCBuildConfiguration; 1057 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 1058 | buildSettings = { 1059 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 1060 | CLANG_ENABLE_MODULES = YES; 1061 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 1062 | ENABLE_BITCODE = NO; 1063 | FRAMEWORK_SEARCH_PATHS = ( 1064 | "$(inherited)", 1065 | "$(PROJECT_DIR)/Flutter", 1066 | ); 1067 | INFOPLIST_FILE = Runner/Info.plist; 1068 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 1069 | LIBRARY_SEARCH_PATHS = ( 1070 | "$(inherited)", 1071 | "$(PROJECT_DIR)/Flutter", 1072 | ); 1073 | PRODUCT_BUNDLE_IDENTIFIER = "com.test.flutterstarterkit$(BUNDLE_SUFFIX)"; 1074 | PRODUCT_NAME = "$(TARGET_NAME)"; 1075 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 1076 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 1077 | SWIFT_VERSION = 4.0; 1078 | VERSIONING_SYSTEM = "apple-generic"; 1079 | }; 1080 | name = Release; 1081 | }; 1082 | /* End XCBuildConfiguration section */ 1083 | 1084 | /* Begin XCConfigurationList section */ 1085 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 1086 | isa = XCConfigurationList; 1087 | buildConfigurations = ( 1088 | 97C147031CF9000F007C117D /* Debug */, 1089 | 0FBC412521EB4C83000179D3 /* Debug-production */, 1090 | 0FBC412121EB4C64000179D3 /* Debug-staging */, 1091 | 0FBC411D21EB4C21000179D3 /* Debug-development */, 1092 | 97C147041CF9000F007C117D /* Release */, 1093 | 0FBC412721EB4C90000179D3 /* Release-production */, 1094 | 0FBC412321EB4C75000179D3 /* Release-staging */, 1095 | 0FBC411F21EB4C52000179D3 /* Release-development */, 1096 | 249021D3217E4FDB00AE95B9 /* Profile */, 1097 | ); 1098 | defaultConfigurationIsVisible = 0; 1099 | defaultConfigurationName = Release; 1100 | }; 1101 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 1102 | isa = XCConfigurationList; 1103 | buildConfigurations = ( 1104 | 97C147061CF9000F007C117D /* Debug */, 1105 | 0FBC412621EB4C83000179D3 /* Debug-production */, 1106 | 0FBC412221EB4C64000179D3 /* Debug-staging */, 1107 | 0FBC411E21EB4C21000179D3 /* Debug-development */, 1108 | 97C147071CF9000F007C117D /* Release */, 1109 | 0FBC412821EB4C90000179D3 /* Release-production */, 1110 | 0FBC412421EB4C75000179D3 /* Release-staging */, 1111 | 0FBC412021EB4C52000179D3 /* Release-development */, 1112 | 249021D4217E4FDB00AE95B9 /* Profile */, 1113 | ); 1114 | defaultConfigurationIsVisible = 0; 1115 | defaultConfigurationName = Release; 1116 | }; 1117 | /* End XCConfigurationList section */ 1118 | }; 1119 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 1120 | } 1121 | -------------------------------------------------------------------------------- /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 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/development.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/production.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/staging.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Original 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: [UIApplicationLaunchOptionsKey: 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/KingWu/flutter_starter_kit/6b72bc03a21a4802a4583ea51f3b2fd7c0e98f71/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/KingWu/flutter_starter_kit/6b72bc03a21a4802a4583ea51f3b2fd7c0e98f71/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/KingWu/flutter_starter_kit/6b72bc03a21a4802a4583ea51f3b2fd7c0e98f71/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/KingWu/flutter_starter_kit/6b72bc03a21a4802a4583ea51f3b2fd7c0e98f71/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/KingWu/flutter_starter_kit/6b72bc03a21a4802a4583ea51f3b2fd7c0e98f71/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/KingWu/flutter_starter_kit/6b72bc03a21a4802a4583ea51f3b2fd7c0e98f71/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/KingWu/flutter_starter_kit/6b72bc03a21a4802a4583ea51f3b2fd7c0e98f71/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/KingWu/flutter_starter_kit/6b72bc03a21a4802a4583ea51f3b2fd7c0e98f71/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/KingWu/flutter_starter_kit/6b72bc03a21a4802a4583ea51f3b2fd7c0e98f71/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/KingWu/flutter_starter_kit/6b72bc03a21a4802a4583ea51f3b2fd7c0e98f71/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/KingWu/flutter_starter_kit/6b72bc03a21a4802a4583ea51f3b2fd7c0e98f71/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/KingWu/flutter_starter_kit/6b72bc03a21a4802a4583ea51f3b2fd7c0e98f71/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/KingWu/flutter_starter_kit/6b72bc03a21a4802a4583ea51f3b2fd7c0e98f71/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/KingWu/flutter_starter_kit/6b72bc03a21a4802a4583ea51f3b2fd7c0e98f71/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/KingWu/flutter_starter_kit/6b72bc03a21a4802a4583ea51f3b2fd7c0e98f71/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/KingWu/flutter_starter_kit/6b72bc03a21a4802a4583ea51f3b2fd7c0e98f71/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KingWu/flutter_starter_kit/6b72bc03a21a4802a4583ea51f3b2fd7c0e98f71/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KingWu/flutter_starter_kit/6b72bc03a21a4802a4583ea51f3b2fd7c0e98f71/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 | en 7 | CFBundleDisplayName 8 | $(APP_NAME) 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | Flutter Starter 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /ios/Runner/de.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ios/Runner/de.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ios/Runner/ja.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ios/Runner/ja.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ios/Runner/zh-Hant.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ios/Runner/zh-Hant.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ios/Runner/zh.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ios/Runner/zh.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /lib/app/bloc/AppDetailBloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter_starter_kit/app/model/core/AppStoreApplication.dart'; 4 | import 'package:flutter_starter_kit/app/model/pojo/AppContent.dart'; 5 | import 'package:rxdart/rxdart.dart'; 6 | import 'package:rxdart/subjects.dart'; 7 | 8 | class AppDetailBloc{ 9 | final AppStoreApplication _application; 10 | CompositeSubscription _compositeSubscription = CompositeSubscription(); 11 | 12 | final _isShowLoading = BehaviorSubject(); 13 | final _appContent = BehaviorSubject(); 14 | 15 | Stream get isShowLoading => _isShowLoading.stream; 16 | Stream get appContent => _appContent.stream; 17 | 18 | 19 | AppDetailBloc(this._application); 20 | 21 | void dispose() { 22 | _compositeSubscription.clear(); 23 | _isShowLoading.close(); 24 | _appContent.close(); 25 | } 26 | 27 | void loadDetail(String appId){ 28 | _isShowLoading.add(true); 29 | StreamSubscription subscription = _application.appStoreAPIRepository.getAppDetail(appId) 30 | .listen((AppContent appContent){ 31 | _appContent.add(appContent); 32 | _isShowLoading.add(false); 33 | }); 34 | _compositeSubscription.add(subscription); 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /lib/app/bloc/HomeBloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter_starter_kit/app/model/api/AppStoreAPIRepository.dart'; 4 | import 'package:flutter_starter_kit/app/model/core/AppStoreApplication.dart'; 5 | import 'package:flutter_starter_kit/app/model/pojo/AppContent.dart'; 6 | import 'package:flutter_starter_kit/utility/log/Log.dart'; 7 | import 'package:rxdart/rxdart.dart'; 8 | 9 | class HomeBloc{ 10 | 11 | final AppStoreApplication _application; 12 | final _searchText = BehaviorSubject(); 13 | final _feedList = BehaviorSubject>(); 14 | final _isShowLoading = BehaviorSubject(); 15 | final _noticeItemUpdate = BehaviorSubject(); 16 | List appList; 17 | 18 | HomeBloc(this._application){ 19 | _init(); 20 | } 21 | 22 | CompositeSubscription _compositeSubscription = CompositeSubscription(); 23 | Stream get isShowLoading => _isShowLoading.stream; 24 | Stream get searchText => _searchText.stream; 25 | Stream> get feedList => _feedList.stream; 26 | Stream get noticeItemUpdate => _noticeItemUpdate.stream; 27 | 28 | 29 | var loadedMap = {}; 30 | 31 | void _init(){ 32 | // Debounce search text 33 | _searchText.debounceTime(const Duration(milliseconds: 500)) 34 | .listen((String searchText){ 35 | _searchApps(searchText); 36 | }); 37 | } 38 | 39 | void dispose() { 40 | _compositeSubscription.clear(); 41 | _searchText.close(); 42 | _feedList.close(); 43 | _isShowLoading.close(); 44 | _noticeItemUpdate.close(); 45 | } 46 | 47 | void changeSearchText(String searchTxt){ 48 | _searchText.add(searchTxt); 49 | } 50 | 51 | void loadFeedList(){ 52 | _isShowLoading.add(true); 53 | AppStoreAPIRepository apiProvider = _application.appStoreAPIRepository; 54 | 55 | StreamSubscription subscription = Observable.fromFuture(_application.dbAppStoreRepository.deleteAllAppContent()) 56 | .flatMap((_) => apiProvider.getTop10FeatureApp()) 57 | .zipWith(apiProvider.getTop100FreeApp(), (List featureApps, List freeApps){ 58 | return CombinedAppResponse(featureApps, freeApps); 59 | }) 60 | .listen((CombinedAppResponse response){ 61 | if(null != appList){ 62 | appList.clear(); 63 | } 64 | appList = List(); 65 | 66 | appList.add(FeatureListItem(response.featureApps)); 67 | 68 | List entries = response.freeApps; 69 | 70 | for(var i = 0 ; i < entries.length ; i++){ 71 | appList.add(TopAppListItem(entries[i])); 72 | } 73 | _feedList.add(appList); 74 | _isShowLoading.add(false); 75 | }, 76 | onError: (e, s){ 77 | Log.info(e); 78 | _isShowLoading.add(false); 79 | }); 80 | _compositeSubscription.add(subscription); 81 | } 82 | 83 | void _searchApps(String searchKey){ 84 | StreamSubscription subscription = Observable.fromFuture(_application.dbAppStoreRepository.loadFeaturesApp(searchKey)) 85 | .zipWith(Observable.fromFuture(_application.dbAppStoreRepository.loadTopFreeApp(searchKey)), (List featureApps, List freeApps){ 86 | return CombinedAppResponse(featureApps, freeApps); 87 | }) 88 | .listen((CombinedAppResponse response){ 89 | appList.clear(); 90 | 91 | if(response.featureApps.length > 0){ 92 | appList.add(FeatureListItem(response.featureApps)); 93 | } 94 | 95 | if(response.freeApps.length > 0){ 96 | List entries = response.freeApps; 97 | for(var i = 0 ; i < entries.length ; i++){ 98 | appList.add(TopAppListItem(entries[i])); 99 | } 100 | } 101 | _feedList.add(appList); 102 | }, 103 | onError: (e, s){ 104 | Log.info(e); 105 | }); 106 | _compositeSubscription.add(subscription); 107 | } 108 | 109 | void loadDetailInfo(int index){ 110 | if(appList.length == 0 || appList.length <= index){ 111 | return; 112 | } 113 | 114 | HomeListItem listItem = appList[index]; 115 | 116 | if(HomeListType.TYPE_TOP_APP == listItem.type){ 117 | TopAppListItem freeAppListItem = listItem; 118 | 119 | if(null != loadedMap[freeAppListItem.entry.trackId] && loadedMap[freeAppListItem.entry.trackId]){ 120 | return; 121 | } 122 | 123 | loadedMap[freeAppListItem.entry.trackId] = true; 124 | 125 | StreamSubscription subscription = _application.appStoreAPIRepository.getAppDetail(freeAppListItem.entry.trackId.toString()) 126 | .listen((AppContent appContent){ 127 | freeAppListItem.entry = appContent; 128 | _noticeItemUpdate.add(appContent.trackId); 129 | }); 130 | _compositeSubscription.add(subscription); 131 | } 132 | } 133 | } 134 | 135 | class CombinedAppResponse{ 136 | List featureApps; 137 | List freeApps; 138 | 139 | CombinedAppResponse(this.featureApps, this.freeApps); 140 | } 141 | 142 | enum HomeListType { 143 | TYPE_FEATURE, 144 | TYPE_TOP_APP 145 | } 146 | 147 | abstract class HomeListItem { 148 | HomeListType type; 149 | 150 | HomeListItem(this.type); 151 | 152 | num getId(); 153 | } 154 | 155 | class FeatureListItem extends HomeListItem{ 156 | List entryList; 157 | 158 | FeatureListItem(this.entryList) : super(HomeListType.TYPE_FEATURE); 159 | 160 | @override 161 | num getId() { 162 | 163 | if(entryList.length > 0){ 164 | return entryList[0].trackId; 165 | } 166 | return -1; 167 | } 168 | } 169 | 170 | class TopAppListItem extends HomeListItem { 171 | AppContent entry; 172 | TopAppListItem(this.entry) : super(HomeListType.TYPE_TOP_APP); 173 | 174 | @override 175 | num getId() { 176 | return entry.trackId; 177 | } 178 | } -------------------------------------------------------------------------------- /lib/app/model/api/APIProvider.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:dio/dio.dart'; 4 | import 'package:flutter_starter_kit/app/model/pojo/response/LookupResponse.dart'; 5 | import 'package:flutter_starter_kit/app/model/pojo/response/TopAppResponse.dart'; 6 | import 'package:flutter_starter_kit/config/Env.dart'; 7 | import 'package:flutter_starter_kit/utility/http/HttpException.dart'; 8 | import 'package:flutter_starter_kit/utility/log/DioLogger.dart'; 9 | import 'package:flutter_starter_kit/utility/log/Log.dart'; 10 | import 'package:sprintf/sprintf.dart'; 11 | 12 | class APIProvider{ 13 | static const String TAG = 'APIProvider'; 14 | 15 | static const String _baseUrl = 'https://itunes.apple.com/hk'; 16 | static const String _TOP_FREE_APP_API = '/rss/topfreeapplications/limit=%d/json'; 17 | static const String _TOP_FEATURE_APP_API = '/rss/topgrossingapplications/limit=%d/json'; 18 | static const String _APP_DETAIL_API = '/lookup/json'; 19 | 20 | Dio _dio; 21 | 22 | APIProvider(){ 23 | BaseOptions dioOptions = BaseOptions() 24 | ..baseUrl = APIProvider._baseUrl; 25 | 26 | _dio = Dio(dioOptions); 27 | 28 | if(EnvType.DEVELOPMENT == Env.value.environmentType || EnvType.STAGING == Env.value.environmentType){ 29 | 30 | _dio.interceptors.add(InterceptorsWrapper( 31 | onRequest:(RequestOptions options) async{ 32 | DioLogger.onSend(TAG, options); 33 | return options; 34 | }, 35 | onResponse: (Response response){ 36 | DioLogger.onSuccess(TAG, response); 37 | return response; 38 | }, 39 | onError: (DioError error){ 40 | DioLogger.onError(TAG, error); 41 | return error; 42 | } 43 | )); 44 | } 45 | } 46 | 47 | Future getTopFreeApp(int limit) async{ 48 | Response response = await _dio.get(sprintf(APIProvider._TOP_FREE_APP_API, [limit])); 49 | throwIfNoSuccess(response); 50 | return TopAppResponse.fromJson(jsonDecode(response.data)); 51 | } 52 | 53 | Future getTopFeatureApp(int limit) async{ 54 | Response response = await _dio.get(sprintf(APIProvider._TOP_FEATURE_APP_API, [limit])); 55 | throwIfNoSuccess(response); 56 | return TopAppResponse.fromJson(jsonDecode(response.data)); 57 | } 58 | 59 | Future getAppDetail(String id) async{ 60 | Response response = await _dio.get(APIProvider._APP_DETAIL_API, queryParameters:{'id':id}); 61 | throwIfNoSuccess(response); 62 | return LookupResponse.fromJson(jsonDecode(response.data)); 63 | } 64 | 65 | void throwIfNoSuccess(Response response) { 66 | if(response.statusCode < 200 || response.statusCode > 299) { 67 | throw new HttpException(response); 68 | } 69 | } 70 | 71 | } -------------------------------------------------------------------------------- /lib/app/model/api/AppStoreAPIRepository.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_starter_kit/app/model/api/APIProvider.dart'; 2 | import 'package:flutter_starter_kit/app/model/db/DBAppStoreRepository.dart'; 3 | import 'package:flutter_starter_kit/app/model/pojo/AppContent.dart'; 4 | import 'package:flutter_starter_kit/app/model/pojo/Entry.dart'; 5 | import 'package:flutter_starter_kit/app/model/pojo/response/LookupResponse.dart'; 6 | import 'package:flutter_starter_kit/app/model/pojo/response/TopAppResponse.dart'; 7 | import 'package:flutter_starter_kit/utility/log/Log.dart'; 8 | import 'package:rxdart/rxdart.dart'; 9 | 10 | class AppStoreAPIRepository{ 11 | static const int TOP_100 = 100; 12 | static const int TOP_10 = 10; 13 | 14 | APIProvider _apiProvider; 15 | DBAppStoreRepository _dbAppStoreRepository; 16 | 17 | AppStoreAPIRepository(this._apiProvider, this._dbAppStoreRepository); 18 | 19 | 20 | Observable> getTop100FreeApp(){ 21 | return Observable.fromFuture(_apiProvider.getTopFreeApp(TOP_100)) 22 | .flatMap(_convertFromEntry) 23 | .flatMap((List list){ 24 | return Observable.fromFuture(_loadAndSaveTopFreeApp(list, '')); 25 | }); 26 | } 27 | 28 | Observable> getTop10FeatureApp(){ 29 | return Observable.fromFuture(_apiProvider.getTopFeatureApp(TOP_10)) 30 | .flatMap(_convertFromEntry) 31 | .flatMap((List list){ 32 | return Observable.fromFuture(_loadAndSaveFeatureApp(list, '')); 33 | }); 34 | } 35 | 36 | Observable getAppDetail(String id){ 37 | return Observable.fromFuture(_apiProvider.getAppDetail(id)) 38 | .flatMap((LookupResponse response){ 39 | return Observable.just(response.results[0]); 40 | }) 41 | .flatMap((AppContent appContent){ 42 | return Observable.fromFuture(_loadAndSaveAppDetail(appContent)); 43 | }); 44 | } 45 | 46 | 47 | Observable> _convertFromEntry(TopAppResponse response){ 48 | List appContent = []; 49 | for(Entry entry in response.feed.entry){ 50 | appContent.add(AppContent.fromEntry(entry)); 51 | } 52 | return Observable.just(appContent); 53 | } 54 | 55 | Future> _loadAndSaveFeatureApp(List list, String searchKey) async{ 56 | for(var i = 0; i < list.length ; i++){ 57 | AppContent app = list[i]; 58 | app.order = i; 59 | app.isFeatureApp = 1; 60 | await _dbAppStoreRepository.saveOrUpdateFeatureApp(app); 61 | } 62 | List appList = await _dbAppStoreRepository.loadFeaturesApp(searchKey); 63 | return appList; 64 | } 65 | 66 | Future> _loadAndSaveTopFreeApp(List list, String searchKey) async{ 67 | for(var i = 0; i < list.length ; i++){ 68 | AppContent app = list[i]; 69 | app.order = i; 70 | app.isFreeApp = 1; 71 | await _dbAppStoreRepository.saveOrUpdateTopFreeApp(app); 72 | } 73 | List appList = await _dbAppStoreRepository.loadTopFreeApp(searchKey); 74 | return appList; 75 | } 76 | 77 | Future _loadAndSaveAppDetail(AppContent appContent) async{ 78 | await _dbAppStoreRepository.saveOrUpdateDetailApp(appContent); 79 | AppContent appDb = await _dbAppStoreRepository.loadAppDetail(appContent.trackId); 80 | return appDb; 81 | } 82 | 83 | } -------------------------------------------------------------------------------- /lib/app/model/core/AppComponent.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_starter_kit/app/model/core/AppProvider.dart'; 3 | import 'package:flutter_starter_kit/app/model/core/AppStoreApplication.dart'; 4 | import 'package:flutter_starter_kit/config/Env.dart'; 5 | import 'package:flutter_starter_kit/generated/i18n.dart'; 6 | import 'package:flutter_starter_kit/utility/log/Log.dart'; 7 | import 'package:flutter_localizations/flutter_localizations.dart'; 8 | 9 | 10 | class AppComponent extends StatefulWidget { 11 | 12 | final AppStoreApplication _application; 13 | 14 | AppComponent(this._application); 15 | 16 | @override 17 | State createState() { 18 | return new AppComponentState(_application); 19 | } 20 | } 21 | 22 | class AppComponentState extends State { 23 | 24 | final AppStoreApplication _application; 25 | 26 | AppComponentState(this._application); 27 | 28 | @override 29 | void dispose()async{ 30 | Log.info('dispose'); 31 | super.dispose(); 32 | await _application.onTerminate(); 33 | } 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | 38 | final app = new MaterialApp( 39 | title: Env.value.appName, 40 | localizationsDelegates: [ 41 | S.delegate, 42 | GlobalMaterialLocalizations.delegate, 43 | GlobalWidgetsLocalizations.delegate 44 | ], 45 | supportedLocales: S.delegate.supportedLocales, 46 | debugShowCheckedModeBanner: false, 47 | theme: new ThemeData( 48 | primarySwatch: Colors.blue, 49 | ), 50 | onGenerateRoute: _application.router.generator, 51 | ); 52 | print('initial core.route = ${app.initialRoute}'); 53 | 54 | final appProvider = AppProvider(child: app, application: _application); 55 | return appProvider; 56 | } 57 | } -------------------------------------------------------------------------------- /lib/app/model/core/AppProvider.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluro/fluro.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_starter_kit/app/model/core/AppStoreApplication.dart'; 4 | 5 | 6 | class AppProvider extends InheritedWidget { 7 | 8 | final AppStoreApplication application; 9 | 10 | AppProvider({Key key, Widget child, this.application}) 11 | : super(key: key, child: child); 12 | 13 | bool updateShouldNotify(_) => true; 14 | 15 | static AppProvider of(BuildContext context) { 16 | return (context.inheritFromWidgetOfExactType(AppProvider) as AppProvider); 17 | } 18 | 19 | static Router getRouter(BuildContext context) { 20 | return (context.inheritFromWidgetOfExactType(AppProvider) as AppProvider).application.router; 21 | } 22 | 23 | static AppStoreApplication getApplication(BuildContext context) { 24 | return (context.inheritFromWidgetOfExactType(AppProvider) as AppProvider).application; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /lib/app/model/core/AppRoutes.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluro/fluro.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_starter_kit/app/ui/page/AppDetailPage.dart'; 4 | import 'package:flutter_starter_kit/app/ui/page/HomePage.dart'; 5 | 6 | var rootHandler = new Handler( 7 | handlerFunc: (BuildContext context, Map> params) { 8 | return HomePage(); 9 | }); 10 | 11 | var appDetailRouteHandler = new Handler( 12 | handlerFunc: (BuildContext context, Map> params) { 13 | String appId = params['appId']?.first; 14 | String heroTag = params['heroTag']?.first; 15 | String title = params['title']?.first; 16 | String url = params['url']?.first; 17 | String titleTag = params['titleTag']?.first; 18 | 19 | 20 | return new AppDetailPage(appId: num.parse(appId), heroTag:heroTag,title: title, url: url, titleTag: titleTag); 21 | }); 22 | 23 | class AppRoutes { 24 | 25 | static void configureRoutes(Router router) { 26 | router.notFoundHandler = new Handler( 27 | handlerFunc: (BuildContext context, Map> params) { 28 | print('ROUTE WAS NOT FOUND !!!'); 29 | }); 30 | router.define(HomePage.PATH, handler: rootHandler); 31 | router.define(AppDetailPage.PATH, handler: appDetailRouteHandler); 32 | } 33 | } -------------------------------------------------------------------------------- /lib/app/model/core/AppStoreApplication.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluro/fluro.dart'; 2 | import 'package:flutter_starter_kit/app/model/api/APIProvider.dart'; 3 | import 'package:flutter_starter_kit/app/model/api/AppStoreAPIRepository.dart'; 4 | import 'package:flutter_starter_kit/app/model/core/AppRoutes.dart'; 5 | import 'package:flutter_starter_kit/app/model/db/AppDatabaseMigrationListener.dart'; 6 | import 'package:flutter_starter_kit/app/model/db/DBAppStoreRepository.dart'; 7 | import 'package:flutter_starter_kit/config/Env.dart'; 8 | import 'package:flutter_starter_kit/utility/db/DatabaseHelper.dart'; 9 | import 'package:flutter_starter_kit/utility/framework/Application.dart'; 10 | import 'package:flutter_starter_kit/utility/log/Log.dart'; 11 | import 'package:logging/logging.dart'; 12 | 13 | class AppStoreApplication implements Application { 14 | Router router; 15 | DatabaseHelper _db; 16 | DBAppStoreRepository dbAppStoreRepository; 17 | AppStoreAPIRepository appStoreAPIRepository; 18 | 19 | @override 20 | Future onCreate() async { 21 | _initLog(); 22 | _initRouter(); 23 | await _initDB(); 24 | _initDBRepository(); 25 | _initAPIRepository(); 26 | } 27 | 28 | @override 29 | Future onTerminate() async { 30 | await _db.close(); 31 | } 32 | 33 | Future _initDB() async { 34 | AppDatabaseMigrationListener migrationListener = AppDatabaseMigrationListener(); 35 | DatabaseConfig databaseConfig = DatabaseConfig(Env.value.dbVersion, Env.value.dbName, migrationListener); 36 | _db = DatabaseHelper(databaseConfig); 37 | Log.info('DB name : ' + Env.value.dbName); 38 | // await _db.deleteDB(); 39 | await _db.open(); 40 | } 41 | 42 | void _initDBRepository(){ 43 | 44 | dbAppStoreRepository = DBAppStoreRepository(_db.database); 45 | } 46 | 47 | void _initAPIRepository(){ 48 | APIProvider apiProvider = APIProvider(); 49 | appStoreAPIRepository = AppStoreAPIRepository(apiProvider, dbAppStoreRepository); 50 | } 51 | 52 | void _initLog(){ 53 | Log.init(); 54 | 55 | switch(Env.value.environmentType){ 56 | case EnvType.TESTING: 57 | case EnvType.DEVELOPMENT: 58 | case EnvType.STAGING:{ 59 | Log.setLevel(Level.ALL); 60 | break; 61 | } 62 | case EnvType.PRODUCTION:{ 63 | Log.setLevel(Level.INFO); 64 | break; 65 | } 66 | } 67 | } 68 | 69 | void _initRouter(){ 70 | router = new Router(); 71 | AppRoutes.configureRoutes(router); 72 | } 73 | } -------------------------------------------------------------------------------- /lib/app/model/db/AppDatabaseMigrationListener.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_starter_kit/utility/db/DatabaseHelper.dart'; 2 | import 'package:flutter_starter_kit/utility/log/Log.dart'; 3 | import 'package:sqflite/sqflite.dart'; 4 | 5 | class AppDatabaseMigrationListener implements DatabaseMigrationListener{ 6 | 7 | static const int VERSION_1_0_0 = 1; 8 | 9 | @override 10 | void onCreate(Database db, int version) async { 11 | 12 | Log.info('onCreate version : $version'); 13 | await _createDatabase(db, version); 14 | } 15 | 16 | @override 17 | void onUpgrade(Database db, int oldVersion, int newVersion) { 18 | Log.info('oldVersion : $oldVersion'); 19 | Log.info('newVersion : $newVersion'); 20 | } 21 | 22 | Future _createDatabase(Database db, int version) async{ 23 | if(VERSION_1_0_0 == version){ 24 | await db.execute('CREATE TABLE AppContent (trackId INTEGER PRIMARY KEY, _order INTEGER, isFeatureApp INTEGER, ' 25 | 'isFreeApp INTEGER, trackName TEXT, description TEXT, meta TEXT)'); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /lib/app/model/db/DBAppStoreRepository.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter_starter_kit/app/model/pojo/AppContent.dart'; 4 | import 'package:flutter_starter_kit/app/model/pojo/Attribute.dart'; 5 | import 'package:sqflite/sqflite.dart'; 6 | import 'package:flutter_starter_kit/utility/log/Log.dart'; 7 | 8 | class DBAppStoreRepository{ 9 | 10 | Database _database; 11 | 12 | DBAppStoreRepository(this._database); 13 | 14 | 15 | Future saveOrUpdateFeatureApp(AppContent appContent) async{ 16 | 17 | String meta = jsonEncode(appContent.toJson()); 18 | int count = await _database.rawUpdate('UPDATE AppContent SET _order = ?, isFeatureApp = ? , trackName = ? , description = ? , meta = ? WHERE trackId = ?', [appContent.order, appContent.isFeatureApp, appContent.trackName, appContent.description, meta, appContent.trackId]); 19 | if(0 == count){ 20 | await _database.rawInsert('INSERT INTO AppContent (trackId, _order, isFeatureApp, trackName, description, meta) VALUES (?, ?, ?, ?, ?, ?)', 21 | [appContent.trackId, appContent.order, appContent.isFeatureApp, appContent.trackName, appContent.description, meta]); 22 | } 23 | } 24 | 25 | Future saveOrUpdateTopFreeApp(AppContent appContent) async{ 26 | 27 | String meta = jsonEncode(appContent.toJson()); 28 | int count = await _database.rawUpdate('UPDATE AppContent SET _order = ?, isFreeApp = ? , trackName = ? , description = ? , meta = ? WHERE trackId = ?', [appContent.order, appContent.isFeatureApp, appContent.trackName, appContent.description, meta, appContent.trackId]); 29 | if(0 == count){ 30 | await _database.rawInsert('INSERT INTO AppContent (trackId, _order, isFreeApp, trackName, description, meta) VALUES (?, ?, ?, ?, ?, ?)', 31 | [appContent.trackId, appContent.order, appContent.isFreeApp, appContent.trackName, appContent.description, meta]); 32 | } 33 | } 34 | 35 | Future saveOrUpdateDetailApp(AppContent appContent) async{ 36 | String meta = jsonEncode(appContent.toJson()); 37 | await _database.rawUpdate('UPDATE AppContent SET meta = ? WHERE trackId = ?', [meta, appContent.trackId]); 38 | } 39 | 40 | Future deleteAllAppContent() async{ 41 | await _database.rawDelete('DELETE FROM AppContent'); 42 | } 43 | 44 | 45 | 46 | Future> loadFeaturesApp(String searchKey) async{ 47 | String searchSql = ''; 48 | if(searchKey.isNotEmpty){ 49 | searchSql = "AND (`trackName` LIKE '%$searchKey%' OR `description` LIKE '%$searchKey%')"; 50 | } 51 | 52 | List list = await _database.rawQuery("SELECT _order, meta FROM AppContent WHERE `isFeatureApp` = 1 $searchSql ORDER BY `_order` ASC"); 53 | List applist = []; 54 | for(var map in list){ 55 | AppContent appContent = AppContent.fromJson(jsonDecode(map['meta'])); 56 | appContent.order = map['_order']; 57 | applist.add(appContent); 58 | } 59 | return applist; 60 | } 61 | 62 | Future> loadTopFreeApp(String searchKey) async{ 63 | 64 | String searchSql = ''; 65 | if(searchKey.isNotEmpty){ 66 | searchSql = "AND (trackName LIKE '%$searchKey%' OR description LIKE '%$searchKey%')"; 67 | } 68 | 69 | List list = await _database.rawQuery("SELECT _order, meta FROM AppContent WHERE `isFreeApp` = 1 $searchSql ORDER BY `_order` ASC"); 70 | List applist = []; 71 | 72 | for(var map in list){ 73 | AppContent appContent = AppContent.fromJson(jsonDecode(map['meta'])); 74 | appContent.order = map['_order']; 75 | applist.add(appContent); 76 | } 77 | return applist; 78 | } 79 | 80 | Future loadAppDetail(num trackId) async{ 81 | List list = await _database.rawQuery("SELECT meta from AppContent WHERE trackId = $trackId LIMIT 1"); 82 | if(list.length > 0){ 83 | return AppContent.fromJson(jsonDecode(list[0]['meta'])); 84 | } 85 | return null; 86 | } 87 | 88 | } -------------------------------------------------------------------------------- /lib/app/model/pojo/AppContent.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_starter_kit/app/model/pojo/Entry.dart'; 2 | import 'package:flutter_starter_kit/app/model/pojo/Property.dart'; 3 | import 'package:json_annotation/json_annotation.dart'; 4 | 5 | part 'AppContent.g.dart'; 6 | 7 | @JsonSerializable() 8 | class AppContent{ 9 | 10 | // Primary Key 11 | num trackId; 12 | int isFeatureApp; 13 | int isFreeApp; 14 | String trackName; 15 | String description; 16 | int order; 17 | 18 | 19 | bool isGameCenterEnabled; 20 | List screenshotUrls; 21 | List ipadScreenshotUrls; 22 | List appletvScreenshotUrls; 23 | String artworkUrl60; 24 | String artworkUrl512; 25 | String artworkUrl100; 26 | String artistViewUrl; 27 | List supportedDevices; 28 | String kind; 29 | List features; 30 | List advisories; 31 | String contentAdvisoryRating; 32 | String trackViewUrl; 33 | String trackCensoredName; 34 | List languageCodesISO2A; 35 | String fileSizeBytes; 36 | String trackContentRating; 37 | String formattedPrice; 38 | String sellerName; 39 | String currentVersionReleaseDate; 40 | bool isVppDeviceBasedLicensingEnabled; 41 | String releaseNotes; 42 | String releaseDate; 43 | num primaryGenreId; 44 | String currency; 45 | String wrapperType; 46 | String version; 47 | String minimumOsVersion; 48 | String primaryGenreName; 49 | List genreIds; 50 | num artistId; 51 | String artistName; 52 | List genres; 53 | double price; 54 | String bundleId; 55 | double averageUserRating; 56 | num userRatingCount; 57 | 58 | 59 | AppContent(this.isGameCenterEnabled, this.screenshotUrls, 60 | this.ipadScreenshotUrls, this.appletvScreenshotUrls, this.artworkUrl60, 61 | this.artworkUrl512, this.artworkUrl100, this.artistViewUrl, 62 | this.supportedDevices, this.kind, this.features, this.advisories, 63 | this.contentAdvisoryRating, this.trackViewUrl, this.trackCensoredName, 64 | 65 | this.languageCodesISO2A, this.fileSizeBytes, this.trackContentRating, 66 | 67 | this.formattedPrice, this.sellerName, this.currentVersionReleaseDate, 68 | 69 | this.isVppDeviceBasedLicensingEnabled, this.trackId, this.trackName, 70 | this.releaseNotes, this.releaseDate, this.primaryGenreId, this.currency, 71 | this.wrapperType, this.version, this.minimumOsVersion, 72 | this.primaryGenreName, this.genreIds, this.description, this.artistId, 73 | this.artistName, this.genres, this.price, this.bundleId, 74 | this.averageUserRating, this.userRatingCount); 75 | 76 | 77 | AppContent.init(this.trackId, this.trackName, this.artworkUrl100); 78 | 79 | factory AppContent.fromEntry(Entry entry) { 80 | String artworkUrl100; 81 | String artworkUrl60; 82 | for(Property property in entry.imImage){ 83 | if(property.attributes.height.contains("100")){ 84 | artworkUrl100 = property.label; 85 | } 86 | else if(null == artworkUrl60 && (property.attributes.height.contains("53") || property.attributes.height.contains("75"))){ 87 | artworkUrl60 = property.label; 88 | } 89 | } 90 | 91 | String trackName = entry.imName.label; 92 | String trackCensoredName = entry.imName.label; 93 | num trackId = num.parse(entry.id.attributes.imId); 94 | String bundleId = entry.id.attributes.imBundleId; 95 | String description = entry.summary.label; 96 | double price = double.parse(entry.imPrice.attributes.amount); 97 | String currency = entry.imPrice.attributes.currency; 98 | num primaryGenreId = num.parse(entry.category.attributes.imId); // number 99 | String trackViewUrl = entry.link.isNotEmpty ? entry.link[0].attributes.href : null; 100 | String artistName = entry.imArtist.label; 101 | String artistViewUrl = null != entry.imArtist.attributes ? entry.imArtist.attributes.href : null; 102 | List genres = [entry.category.attributes.label]; 103 | String releaseDate = entry.imReleaseDate.label; 104 | return AppContent(null, null, 105 | null, null, artworkUrl60, 106 | null, artworkUrl100, artistViewUrl, 107 | null, null, null, null, 108 | null, trackViewUrl, trackCensoredName, 109 | null, null, null, 110 | null, null, null, 111 | null, trackId, trackName, 112 | null, releaseDate, primaryGenreId, currency, 113 | null, null, null, 114 | null, null, description, null, 115 | artistName, genres, price, bundleId, 116 | null, null); 117 | } 118 | 119 | 120 | factory AppContent.fromJson(Map json) => _$AppContentFromJson(json); 121 | 122 | Map toJson() => _$AppContentToJson(this); 123 | 124 | } -------------------------------------------------------------------------------- /lib/app/model/pojo/AppContent.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'AppContent.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | AppContent _$AppContentFromJson(Map json) { 10 | return AppContent( 11 | json['isGameCenterEnabled'] as bool, 12 | (json['screenshotUrls'] as List)?.map((e) => e as String)?.toList(), 13 | (json['ipadScreenshotUrls'] as List)?.map((e) => e as String)?.toList(), 14 | (json['appletvScreenshotUrls'] as List)?.map((e) => e as String)?.toList(), 15 | json['artworkUrl60'] as String, 16 | json['artworkUrl512'] as String, 17 | json['artworkUrl100'] as String, 18 | json['artistViewUrl'] as String, 19 | (json['supportedDevices'] as List)?.map((e) => e as String)?.toList(), 20 | json['kind'] as String, 21 | (json['features'] as List)?.map((e) => e as String)?.toList(), 22 | (json['advisories'] as List)?.map((e) => e as String)?.toList(), 23 | json['contentAdvisoryRating'] as String, 24 | json['trackViewUrl'] as String, 25 | json['trackCensoredName'] as String, 26 | (json['languageCodesISO2A'] as List)?.map((e) => e as String)?.toList(), 27 | json['fileSizeBytes'] as String, 28 | json['trackContentRating'] as String, 29 | json['formattedPrice'] as String, 30 | json['sellerName'] as String, 31 | json['currentVersionReleaseDate'] as String, 32 | json['isVppDeviceBasedLicensingEnabled'] as bool, 33 | json['trackId'] as num, 34 | json['trackName'] as String, 35 | json['releaseNotes'] as String, 36 | json['releaseDate'] as String, 37 | json['primaryGenreId'] as num, 38 | json['currency'] as String, 39 | json['wrapperType'] as String, 40 | json['version'] as String, 41 | json['minimumOsVersion'] as String, 42 | json['primaryGenreName'] as String, 43 | (json['genreIds'] as List)?.map((e) => e as String)?.toList(), 44 | json['description'] as String, 45 | json['artistId'] as num, 46 | json['artistName'] as String, 47 | (json['genres'] as List)?.map((e) => e as String)?.toList(), 48 | (json['price'] as num)?.toDouble(), 49 | json['bundleId'] as String, 50 | (json['averageUserRating'] as num)?.toDouble(), 51 | json['userRatingCount'] as num, 52 | ) 53 | ..isFeatureApp = json['isFeatureApp'] as int 54 | ..isFreeApp = json['isFreeApp'] as int 55 | ..order = json['order'] as int; 56 | } 57 | 58 | Map _$AppContentToJson(AppContent instance) => 59 | { 60 | 'trackId': instance.trackId, 61 | 'isFeatureApp': instance.isFeatureApp, 62 | 'isFreeApp': instance.isFreeApp, 63 | 'trackName': instance.trackName, 64 | 'description': instance.description, 65 | 'order': instance.order, 66 | 'isGameCenterEnabled': instance.isGameCenterEnabled, 67 | 'screenshotUrls': instance.screenshotUrls, 68 | 'ipadScreenshotUrls': instance.ipadScreenshotUrls, 69 | 'appletvScreenshotUrls': instance.appletvScreenshotUrls, 70 | 'artworkUrl60': instance.artworkUrl60, 71 | 'artworkUrl512': instance.artworkUrl512, 72 | 'artworkUrl100': instance.artworkUrl100, 73 | 'artistViewUrl': instance.artistViewUrl, 74 | 'supportedDevices': instance.supportedDevices, 75 | 'kind': instance.kind, 76 | 'features': instance.features, 77 | 'advisories': instance.advisories, 78 | 'contentAdvisoryRating': instance.contentAdvisoryRating, 79 | 'trackViewUrl': instance.trackViewUrl, 80 | 'trackCensoredName': instance.trackCensoredName, 81 | 'languageCodesISO2A': instance.languageCodesISO2A, 82 | 'fileSizeBytes': instance.fileSizeBytes, 83 | 'trackContentRating': instance.trackContentRating, 84 | 'formattedPrice': instance.formattedPrice, 85 | 'sellerName': instance.sellerName, 86 | 'currentVersionReleaseDate': instance.currentVersionReleaseDate, 87 | 'isVppDeviceBasedLicensingEnabled': 88 | instance.isVppDeviceBasedLicensingEnabled, 89 | 'releaseNotes': instance.releaseNotes, 90 | 'releaseDate': instance.releaseDate, 91 | 'primaryGenreId': instance.primaryGenreId, 92 | 'currency': instance.currency, 93 | 'wrapperType': instance.wrapperType, 94 | 'version': instance.version, 95 | 'minimumOsVersion': instance.minimumOsVersion, 96 | 'primaryGenreName': instance.primaryGenreName, 97 | 'genreIds': instance.genreIds, 98 | 'artistId': instance.artistId, 99 | 'artistName': instance.artistName, 100 | 'genres': instance.genres, 101 | 'price': instance.price, 102 | 'bundleId': instance.bundleId, 103 | 'averageUserRating': instance.averageUserRating, 104 | 'userRatingCount': instance.userRatingCount, 105 | }; 106 | -------------------------------------------------------------------------------- /lib/app/model/pojo/Attribute.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'Attribute.g.dart'; 4 | 5 | @JsonSerializable() 6 | class Attribute{ 7 | 8 | String height; 9 | String amount; 10 | String currency; 11 | String term; 12 | String rel; 13 | String type; 14 | String href; 15 | @JsonKey(name: 'im:id') 16 | String imId; 17 | @JsonKey(name: 'im:bundleId') 18 | String imBundleId; 19 | String scheme; 20 | String label; 21 | 22 | 23 | 24 | 25 | Attribute(this.height, this.amount, this.currency, this.term, this.rel, 26 | this.type, this.href, this.imId, this.imBundleId, this.scheme, 27 | this.label); 28 | 29 | factory Attribute.fromJson(Map json) => _$AttributeFromJson(json); 30 | 31 | Map toJson() => _$AttributeToJson(this); 32 | 33 | } -------------------------------------------------------------------------------- /lib/app/model/pojo/Attribute.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'Attribute.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | Attribute _$AttributeFromJson(Map json) { 10 | return Attribute( 11 | json['height'] as String, 12 | json['amount'] as String, 13 | json['currency'] as String, 14 | json['term'] as String, 15 | json['rel'] as String, 16 | json['type'] as String, 17 | json['href'] as String, 18 | json['im:id'] as String, 19 | json['im:bundleId'] as String, 20 | json['scheme'] as String, 21 | json['label'] as String, 22 | ); 23 | } 24 | 25 | Map _$AttributeToJson(Attribute instance) => { 26 | 'height': instance.height, 27 | 'amount': instance.amount, 28 | 'currency': instance.currency, 29 | 'term': instance.term, 30 | 'rel': instance.rel, 31 | 'type': instance.type, 32 | 'href': instance.href, 33 | 'im:id': instance.imId, 34 | 'im:bundleId': instance.imBundleId, 35 | 'scheme': instance.scheme, 36 | 'label': instance.label, 37 | }; 38 | -------------------------------------------------------------------------------- /lib/app/model/pojo/Entry.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_starter_kit/app/model/pojo/Property.dart'; 2 | import 'package:json_annotation/json_annotation.dart'; 3 | 4 | part 'Entry.g.dart'; 5 | 6 | @JsonSerializable() 7 | class Entry{ 8 | 9 | 10 | @JsonKey(name: 'im:name') 11 | Property imName; 12 | @JsonKey(name: 'im:image') 13 | List imImage; 14 | Property summary; 15 | @JsonKey(name: 'im:price') 16 | Property imPrice; 17 | @JsonKey(name: 'im:contentType') 18 | Property imContentType; 19 | Property title; 20 | List link; 21 | Property id; 22 | @JsonKey(name: 'im:artist') 23 | Property imArtist; 24 | Property category; 25 | @JsonKey(name: 'im:releaseDate') 26 | Property imReleaseDate; 27 | 28 | 29 | Entry(this.imName, this.imImage, this.summary, this.imPrice, 30 | this.imContentType, this.title, this.link, this.id, this.imArtist, 31 | this.category, this.imReleaseDate); 32 | 33 | factory Entry.fromJson(Map json) => _$EntryFromJson(json); 34 | 35 | Map toJson() => _$EntryToJson(this); 36 | 37 | } -------------------------------------------------------------------------------- /lib/app/model/pojo/Entry.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'Entry.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | Entry _$EntryFromJson(Map json) { 10 | return Entry( 11 | json['im:name'] == null 12 | ? null 13 | : Property.fromJson(json['im:name'] as Map), 14 | (json['im:image'] as List) 15 | ?.map((e) => 16 | e == null ? null : Property.fromJson(e as Map)) 17 | ?.toList(), 18 | json['summary'] == null 19 | ? null 20 | : Property.fromJson(json['summary'] as Map), 21 | json['im:price'] == null 22 | ? null 23 | : Property.fromJson(json['im:price'] as Map), 24 | json['im:contentType'] == null 25 | ? null 26 | : Property.fromJson(json['im:contentType'] as Map), 27 | json['title'] == null 28 | ? null 29 | : Property.fromJson(json['title'] as Map), 30 | (json['link'] as List) 31 | ?.map((e) => 32 | e == null ? null : Property.fromJson(e as Map)) 33 | ?.toList(), 34 | json['id'] == null 35 | ? null 36 | : Property.fromJson(json['id'] as Map), 37 | json['im:artist'] == null 38 | ? null 39 | : Property.fromJson(json['im:artist'] as Map), 40 | json['category'] == null 41 | ? null 42 | : Property.fromJson(json['category'] as Map), 43 | json['im:releaseDate'] == null 44 | ? null 45 | : Property.fromJson(json['im:releaseDate'] as Map), 46 | ); 47 | } 48 | 49 | Map _$EntryToJson(Entry instance) => { 50 | 'im:name': instance.imName, 51 | 'im:image': instance.imImage, 52 | 'summary': instance.summary, 53 | 'im:price': instance.imPrice, 54 | 'im:contentType': instance.imContentType, 55 | 'title': instance.title, 56 | 'link': instance.link, 57 | 'id': instance.id, 58 | 'im:artist': instance.imArtist, 59 | 'category': instance.category, 60 | 'im:releaseDate': instance.imReleaseDate, 61 | }; 62 | -------------------------------------------------------------------------------- /lib/app/model/pojo/Property.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_starter_kit/app/model/pojo/Attribute.dart'; 2 | import 'package:json_annotation/json_annotation.dart'; 3 | 4 | part 'Property.g.dart'; 5 | 6 | @JsonSerializable() 7 | class Property{ 8 | 9 | 10 | String label; 11 | Attribute attributes; 12 | 13 | Property(this.label, this.attributes); 14 | 15 | factory Property.fromJson(Map json) => _$PropertyFromJson(json); 16 | 17 | Map toJson() => _$PropertyToJson(this); 18 | 19 | } -------------------------------------------------------------------------------- /lib/app/model/pojo/Property.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'Property.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | Property _$PropertyFromJson(Map json) { 10 | return Property( 11 | json['label'] as String, 12 | json['attributes'] == null 13 | ? null 14 | : Attribute.fromJson(json['attributes'] as Map), 15 | ); 16 | } 17 | 18 | Map _$PropertyToJson(Property instance) => { 19 | 'label': instance.label, 20 | 'attributes': instance.attributes, 21 | }; 22 | -------------------------------------------------------------------------------- /lib/app/model/pojo/response/LookupResponse.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_starter_kit/app/model/pojo/AppContent.dart'; 2 | import 'package:json_annotation/json_annotation.dart'; 3 | 4 | part 'LookupResponse.g.dart'; 5 | 6 | @JsonSerializable() 7 | class LookupResponse{ 8 | 9 | int resultCount; 10 | List results; 11 | 12 | 13 | LookupResponse(this.resultCount, this.results); 14 | 15 | factory LookupResponse.fromJson(Map json) => _$LookupResponseFromJson(json); 16 | 17 | Map toJson() => _$LookupResponseToJson(this); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /lib/app/model/pojo/response/LookupResponse.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'LookupResponse.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | LookupResponse _$LookupResponseFromJson(Map json) { 10 | return LookupResponse( 11 | json['resultCount'] as int, 12 | (json['results'] as List) 13 | ?.map((e) => 14 | e == null ? null : AppContent.fromJson(e as Map)) 15 | ?.toList(), 16 | ); 17 | } 18 | 19 | Map _$LookupResponseToJson(LookupResponse instance) => 20 | { 21 | 'resultCount': instance.resultCount, 22 | 'results': instance.results, 23 | }; 24 | -------------------------------------------------------------------------------- /lib/app/model/pojo/response/TopAppResponse.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_starter_kit/app/model/pojo/Entry.dart'; 2 | import 'package:json_annotation/json_annotation.dart'; 3 | 4 | part 'TopAppResponse.g.dart'; 5 | 6 | @JsonSerializable() 7 | class TopAppResponse{ 8 | 9 | Feed feed; 10 | 11 | TopAppResponse(this.feed); 12 | 13 | factory TopAppResponse.fromJson(Map json) => _$TopAppResponseFromJson(json); 14 | 15 | Map toJson() => _$TopAppResponseToJson(this); 16 | 17 | } 18 | 19 | @JsonSerializable() 20 | class Feed{ 21 | List entry; 22 | 23 | Feed(this.entry); 24 | 25 | factory Feed.fromJson(Map json) => _$FeedFromJson(json); 26 | 27 | Map toJson() => _$FeedToJson(this); 28 | 29 | } -------------------------------------------------------------------------------- /lib/app/model/pojo/response/TopAppResponse.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'TopAppResponse.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | TopAppResponse _$TopAppResponseFromJson(Map json) { 10 | return TopAppResponse( 11 | json['feed'] == null 12 | ? null 13 | : Feed.fromJson(json['feed'] as Map), 14 | ); 15 | } 16 | 17 | Map _$TopAppResponseToJson(TopAppResponse instance) => 18 | { 19 | 'feed': instance.feed, 20 | }; 21 | 22 | Feed _$FeedFromJson(Map json) { 23 | return Feed( 24 | (json['entry'] as List) 25 | ?.map( 26 | (e) => e == null ? null : Entry.fromJson(e as Map)) 27 | ?.toList(), 28 | ); 29 | } 30 | 31 | Map _$FeedToJson(Feed instance) => { 32 | 'entry': instance.entry, 33 | }; 34 | -------------------------------------------------------------------------------- /lib/app/ui/page/AppDetailPage.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_starter_kit/app/bloc/AppDetailBloc.dart'; 4 | import 'package:flutter_starter_kit/app/model/core/AppProvider.dart'; 5 | import 'package:flutter_starter_kit/app/model/pojo/AppContent.dart'; 6 | import 'package:flutter_starter_kit/generated/i18n.dart'; 7 | import 'package:smooth_star_rating/smooth_star_rating.dart'; 8 | 9 | class AppDetailPage extends StatefulWidget { 10 | static const String PATH = '/detail'; 11 | final num appId; 12 | final String title; 13 | final String url; 14 | final String heroTag; 15 | final String titleTag; 16 | 17 | AppDetailPage({Key key, this.appId, this.title, this.url, this.heroTag, this.titleTag}) : super(key: key); 18 | 19 | static String generatePath(num appId, String heroTag, String titleTag, String title, String url){ 20 | Map parma = { 21 | 'appId': appId.toString(), 22 | 'heroTag': heroTag, 23 | 'title': title, 24 | 'url': url, 25 | 'titleTag': titleTag 26 | }; 27 | Uri uri = Uri(path: PATH, queryParameters: parma); 28 | return uri.toString(); 29 | } 30 | 31 | @override 32 | _AppDetailPageState createState() => _AppDetailPageState(); 33 | } 34 | 35 | 36 | class _AppDetailPageState extends State { 37 | 38 | AppContent _initAppContent; 39 | 40 | AppDetailBloc appDetailBloc; 41 | Color greyColor = Color.fromARGB(255, 163, 163, 163); 42 | 43 | @override 44 | void initState() { 45 | super.initState(); 46 | _initAppContent = AppContent.init(widget.appId, widget.title, widget.url); 47 | } 48 | 49 | @override 50 | void dispose() { 51 | super.dispose(); 52 | appDetailBloc.dispose(); 53 | } 54 | 55 | @override 56 | Widget build(BuildContext context) { 57 | _init(context); 58 | 59 | return StreamBuilder( 60 | stream: appDetailBloc.appContent, 61 | initialData: _initAppContent, 62 | builder: (context, snapshot){ 63 | AppContent appContent = snapshot.data; 64 | return Scaffold( 65 | appBar: AppBar( 66 | title: Text(appContent.trackName) 67 | ), 68 | body: SingleChildScrollView( 69 | child: Column( 70 | mainAxisSize: MainAxisSize.min, 71 | crossAxisAlignment: CrossAxisAlignment.stretch, 72 | children: [ 73 | buildAppContent(context, appContent), 74 | Divider(height: 4, color: greyColor), 75 | buildRatingSection(context, appContent), 76 | Divider(height: 4, color: greyColor), 77 | buildGallerySection(context, appContent), 78 | buildSummarySection(context, appContent) 79 | ] 80 | ) 81 | ) 82 | ); 83 | } 84 | ); 85 | } 86 | 87 | void _showLoading(BuildContext context) { 88 | showDialog( 89 | context: context, 90 | barrierDismissible: false, 91 | child: Dialog( 92 | child: Container( 93 | padding: EdgeInsets.all(16), 94 | child: Column( 95 | mainAxisSize: MainAxisSize.min, 96 | children: [ 97 | CircularProgressIndicator(), 98 | Container( 99 | margin: EdgeInsets.only(top: 8), 100 | child: Text(S.of(context).dialogLoading) 101 | ) 102 | ], 103 | ) 104 | ) 105 | ) 106 | ); 107 | } 108 | 109 | void _init(BuildContext context){ 110 | if(null == appDetailBloc){ 111 | appDetailBloc = AppDetailBloc(AppProvider.getApplication(context)); 112 | appDetailBloc.isShowLoading.listen((bool isLoading){ 113 | if(isLoading){ 114 | _showLoading(context); 115 | } 116 | else{ 117 | Navigator.pop(context); 118 | } 119 | }); 120 | appDetailBloc.loadDetail(widget.appId.toString()); 121 | 122 | } 123 | } 124 | 125 | Widget buildAppContent(BuildContext context, AppContent appContent){ 126 | 127 | List category = appContent.genres; 128 | List chips = []; 129 | if(null != category){ 130 | for(String c in category){ 131 | chips.add(Chip( 132 | label: Text(c) 133 | )); 134 | } 135 | } 136 | return Container( 137 | padding: EdgeInsets.all(12), 138 | child: Row( 139 | crossAxisAlignment: CrossAxisAlignment.start, 140 | children: [ 141 | Container( 142 | height: 100, 143 | width: 100, 144 | margin: EdgeInsets.only(right: 12), 145 | child: Hero( 146 | tag: widget.heroTag, 147 | child: buildAppIcon(appContent.artworkUrl100) 148 | ) 149 | ), 150 | Expanded( 151 | child: Column( 152 | crossAxisAlignment: CrossAxisAlignment.start, 153 | children: [ 154 | Hero( 155 | tag: widget.titleTag, 156 | child: Text( 157 | appContent.trackName, 158 | overflow: TextOverflow.ellipsis, 159 | maxLines: 2, 160 | style: Theme.of(context).textTheme.title, 161 | ) 162 | ), 163 | Text( 164 | null != appContent.artistName ? appContent.artistName : '', 165 | overflow: TextOverflow.ellipsis, 166 | maxLines: 1, 167 | style: TextStyle( 168 | color: Colors.green 169 | ), 170 | ), 171 | Wrap( 172 | direction: Axis.horizontal, 173 | spacing: 8, 174 | children: chips 175 | ), 176 | ], 177 | ), 178 | ) 179 | ], 180 | ) 181 | ); 182 | } 183 | 184 | Widget buildRatingSection(BuildContext context, AppContent appContent){ 185 | num userRatingCount = null != appContent.userRatingCount ? appContent.userRatingCount : 0; 186 | num averageUserRating = null != appContent.averageUserRating ? appContent.averageUserRating : 0; 187 | 188 | return Container( 189 | padding: EdgeInsets.symmetric(vertical: 12), 190 | child: Column( 191 | crossAxisAlignment: CrossAxisAlignment.center, 192 | children: [ 193 | Row( 194 | mainAxisSize: MainAxisSize.min, 195 | crossAxisAlignment: CrossAxisAlignment.center, 196 | children: [ 197 | Text( 198 | '$averageUserRating', 199 | style: Theme.of(context).textTheme.display1, 200 | ), 201 | Container( 202 | width: 4, 203 | ), 204 | SmoothStarRating( 205 | starCount: 1, 206 | rating: 1, 207 | size: 35.0, 208 | color: Colors.orange, 209 | borderColor: Colors.orange, 210 | ) 211 | ] 212 | ), 213 | Row( 214 | mainAxisSize: MainAxisSize.min, 215 | crossAxisAlignment: CrossAxisAlignment.center, 216 | children: [ 217 | Text( 218 | '$userRatingCount' 219 | ), 220 | Container( 221 | width: 4, 222 | ), 223 | Text( 224 | S.of(context).detailRate 225 | ) 226 | ], 227 | ) 228 | ], 229 | ) 230 | ); 231 | } 232 | 233 | Widget buildGallerySection(BuildContext context, AppContent appContent){ 234 | int length = null != appContent.screenshotUrls ? appContent.screenshotUrls.length : 0; 235 | 236 | return Container( 237 | height: 300, 238 | margin: EdgeInsets.only(top: 12), 239 | child: ListView.separated( 240 | itemCount: length, 241 | scrollDirection: Axis.horizontal, 242 | shrinkWrap: true, 243 | separatorBuilder: (BuildContext context, int index){ 244 | return Container(width: 12); 245 | }, 246 | itemBuilder: (BuildContext context, int index){ 247 | double left = 0 == index ? 20 : 0; 248 | double right = length - 1 == index ? 20 : 0; 249 | String url = appContent.screenshotUrls[index]; 250 | return Container( 251 | margin: EdgeInsets.only(left: left, right: right), 252 | child: CachedNetworkImage( 253 | imageUrl: url, 254 | fit: BoxFit.fitHeight, 255 | errorWidget: (context, url, error) => new Icon(Icons.error), 256 | fadeOutDuration: new Duration(seconds: 1), 257 | fadeInDuration: new Duration(seconds: 1) 258 | ) 259 | ); 260 | } 261 | ) 262 | ); 263 | } 264 | 265 | Widget buildSummarySection(BuildContext context, AppContent appContent){ 266 | String description = null != appContent.description ? appContent.description : ''; 267 | return Container( 268 | padding: EdgeInsets.all(12), 269 | child: Text( 270 | description 271 | ) 272 | ); 273 | } 274 | 275 | Widget buildAppIcon(String iconUrl){ 276 | 277 | return ClipRRect( 278 | borderRadius: BorderRadius.circular(16.0), 279 | child: CachedNetworkImage( 280 | imageUrl: iconUrl, 281 | errorWidget: (context, url, error) => new Icon(Icons.error), 282 | fadeOutDuration: new Duration(seconds: 1), 283 | fadeInDuration: new Duration(seconds: 1) 284 | ) 285 | ); 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /lib/app/ui/page/HomePage.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:fluro/fluro.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_starter_kit/app/bloc/HomeBloc.dart'; 5 | import 'package:flutter_starter_kit/app/model/core/AppProvider.dart'; 6 | import 'package:flutter_starter_kit/app/model/pojo/AppContent.dart'; 7 | import 'package:flutter_starter_kit/app/ui/page/AppDetailPage.dart'; 8 | import 'package:flutter_starter_kit/generated/i18n.dart'; 9 | import 'package:flutter_starter_kit/utility/widget/StreamListItem.dart'; 10 | import 'package:smooth_star_rating/smooth_star_rating.dart'; 11 | 12 | class HomePage extends StatefulWidget { 13 | static const String PATH = '/'; 14 | 15 | HomePage({Key key}) : super(key: key); 16 | 17 | @override 18 | _HomePageState createState() => _HomePageState(); 19 | } 20 | 21 | class _HomePageState extends State { 22 | 23 | HomeBloc bloc; 24 | final TextEditingController _searchBoxController = new TextEditingController(); 25 | Color greyColor = Color.fromARGB(255, 163, 163, 163); 26 | var _keys = {}; 27 | var listViewKey = UniqueKey(); 28 | 29 | @override 30 | void dispose() { 31 | super.dispose(); 32 | bloc.dispose(); 33 | } 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | _init(); 38 | 39 | return Scaffold( 40 | appBar: AppBar( 41 | title: buildSearchBar() 42 | ), 43 | body: buildFeedList() 44 | ); 45 | } 46 | 47 | Widget buildSearchBar(){ 48 | return StreamBuilder( 49 | stream: bloc.searchText, 50 | builder: (context, snapshot) { 51 | String searchText = snapshot.data; 52 | 53 | return TextField( 54 | controller: _searchBoxController, 55 | onChanged: bloc.changeSearchText, 56 | decoration: InputDecoration( 57 | fillColor: Colors.white70, 58 | filled: true, 59 | hasFloatingPlaceholder: false, 60 | prefixIcon: Icon(Icons.search), 61 | suffixIcon: null != searchText && searchText.isNotEmpty ? IconButton( 62 | icon:Icon(Icons.clear), 63 | onPressed: () { 64 | _searchBoxController.clear(); 65 | bloc.changeSearchText(''); 66 | }, 67 | ) : null, 68 | border: InputBorder.none, 69 | hintText: S.of(context).homeSearchHint 70 | ), 71 | ); 72 | }, 73 | ); 74 | } 75 | 76 | void _init(){ 77 | if(null == bloc){ 78 | bloc = HomeBloc(AppProvider.getApplication(context)); 79 | bloc.isShowLoading.listen((bool isLoading){ 80 | if(isLoading){ 81 | _showLoading(); 82 | } 83 | else{ 84 | Navigator.pop(context); 85 | } 86 | }); 87 | bloc.loadFeedList(); 88 | } 89 | } 90 | 91 | void _showLoading() { 92 | showDialog( 93 | context: context, 94 | barrierDismissible: false, 95 | child: Dialog( 96 | child: Container( 97 | padding: EdgeInsets.all(16), 98 | child: Column( 99 | mainAxisSize: MainAxisSize.min, 100 | children: [ 101 | CircularProgressIndicator(), 102 | Container( 103 | margin: EdgeInsets.only(top: 8), 104 | child: Text(S.of(context).dialogLoading) 105 | ) 106 | ], 107 | ) 108 | ) 109 | ) 110 | ); 111 | } 112 | 113 | Widget buildFeedList(){ 114 | return StreamBuilder( 115 | stream: bloc.feedList, 116 | builder: (context, snapshot) { 117 | 118 | 119 | switch(snapshot.connectionState){ 120 | case ConnectionState.none: 121 | case ConnectionState.waiting:{ 122 | return Center( 123 | child: Text(S.of(context).dialogLoading) 124 | ); 125 | } 126 | case ConnectionState.done: 127 | case ConnectionState.active:{ 128 | 129 | List feedList = snapshot.data; 130 | if(0 == feedList.length){ 131 | return Center( 132 | child: Text(S.of(context).homeEmptyList) 133 | ); 134 | } 135 | 136 | return ListView.builder( 137 | key: listViewKey, 138 | scrollDirection: Axis.vertical, 139 | itemCount: null != feedList ? feedList.length : 0, 140 | itemBuilder: (context, index) { 141 | // Log.info('index : $index'); 142 | HomeListItem listItem = feedList[index]; 143 | 144 | if(null == _keys[listItem.getId()]){ 145 | _keys[listItem.getId()] = ValueKey(listItem.getId()); 146 | } 147 | 148 | var key = _keys[listItem.getId()]; 149 | 150 | 151 | if(HomeListType.TYPE_FEATURE == listItem.type){ 152 | return Container( 153 | key: key, 154 | child: buildFeatureListItem(listItem), 155 | ); 156 | } 157 | 158 | bool isFeatureListItemExist = HomeListType.TYPE_FEATURE == feedList[0].type; 159 | 160 | return StreamListItem( 161 | key: key, 162 | initialData: listItem, 163 | stream: bloc.noticeItemUpdate, 164 | comparator: (HomeListItem listItem, num appId){ 165 | if(HomeListType.TYPE_TOP_APP == listItem.type){ 166 | TopAppListItem topAppListItem = listItem; 167 | return topAppListItem.entry.trackId == appId; 168 | } 169 | 170 | return false; 171 | }, 172 | builder: (BuildContext context, HomeListItem listItem){ 173 | // Log.info('Updated : $index'); 174 | return buildTopAppListItem(listItem, index, isFeatureListItemExist); 175 | } 176 | ); 177 | } 178 | ); 179 | } 180 | } 181 | } 182 | ); 183 | } 184 | 185 | Widget buildFeatureListItem(FeatureListItem featureListItem){ 186 | return Column( 187 | crossAxisAlignment: CrossAxisAlignment.start, 188 | children: [ 189 | Container( 190 | margin: EdgeInsets.only(left: 20, top: 12, bottom: 16), 191 | child: Text( 192 | S.of(context).homeRecommend, 193 | style: Theme.of(context).textTheme.title, 194 | ), 195 | ), 196 | Container( 197 | height: 160, 198 | child: ListView.builder( 199 | scrollDirection: Axis.horizontal, 200 | itemCount: featureListItem.entryList.length, 201 | itemBuilder: (context, index) { 202 | AppContent app = featureListItem.entryList[index]; 203 | String name = app.trackName; 204 | String category = app.genres[0]; 205 | String heroTag = 'featureAppIcon_$index'; 206 | String titleTag = 'featureAppTitle_$index'; 207 | 208 | return Container( 209 | width: 85, 210 | margin: EdgeInsets.only(left: 16, right: index == featureListItem.entryList.length - 1 ? 16 : 0), 211 | child: InkWell( 212 | onTap: (){ 213 | AppProvider.getRouter(context).navigateTo(context, AppDetailPage.generatePath(app.trackId, heroTag, titleTag, name, app.artworkUrl100), transition: TransitionType.fadeIn); 214 | }, 215 | child: Column( 216 | mainAxisSize: MainAxisSize.max, 217 | crossAxisAlignment: CrossAxisAlignment.start, 218 | children: [ 219 | Container( 220 | height: 85, 221 | margin: EdgeInsets.only(bottom: 8), 222 | child: Hero( 223 | tag: 'featureAppIcon_$index', 224 | child: ClipRRect( 225 | borderRadius: BorderRadius.circular(16.0), 226 | child: CachedNetworkImage( 227 | imageUrl: app.artworkUrl100, 228 | errorWidget: (context, url, error) => new Icon(Icons.error), 229 | fadeOutDuration: new Duration(seconds: 1), 230 | fadeInDuration: new Duration(seconds: 1) 231 | ) 232 | ), 233 | ) 234 | ), 235 | Hero( 236 | tag: titleTag, 237 | child: Text( 238 | name, 239 | overflow: TextOverflow.ellipsis, 240 | maxLines: 2, 241 | ) 242 | ), 243 | Text( 244 | category, 245 | maxLines: 1, 246 | style: TextStyle( 247 | color: greyColor 248 | ), 249 | ), 250 | ], 251 | ) 252 | ) 253 | ); 254 | } 255 | ) 256 | ), 257 | Divider(height: 4, color: greyColor) 258 | ], 259 | ); 260 | } 261 | 262 | Widget buildTopAppListItem(TopAppListItem listItem, int index, bool isFeatureListItemExist){ 263 | AppContent app = listItem.entry; 264 | String name = app.trackName; 265 | String category = app.genres[0]; 266 | double rating = null != app.averageUserRating ? app.averageUserRating : 0.0; 267 | num userCount = null != app.userRatingCount ? app.userRatingCount : 0; 268 | 269 | int order = index + 1; 270 | 271 | if(isFeatureListItemExist){ 272 | order--; 273 | } 274 | 275 | String heroTag = 'freeAppIcon_$order'; 276 | String titleTag = 'freeAppTitle_$order'; 277 | 278 | // Trigger to load app detail info for visible item 279 | bloc.loadDetailInfo(index); 280 | 281 | return Column( 282 | children: [ 283 | Container( 284 | margin: EdgeInsets.symmetric(vertical: 8), 285 | child: InkWell( 286 | onTap: (){ 287 | AppProvider.getRouter(context).navigateTo(context, AppDetailPage.generatePath(app.trackId, heroTag, titleTag, name, app.artworkUrl100), transition: TransitionType.fadeIn); 288 | }, 289 | child: Row( 290 | mainAxisSize: MainAxisSize.max, 291 | crossAxisAlignment: CrossAxisAlignment.center, 292 | children: [ 293 | Container( 294 | margin: EdgeInsets.only(left: 20, right: 5), 295 | width: 40, 296 | child: Text( 297 | "$order", 298 | style: Theme.of(context).textTheme.title, 299 | ) 300 | ), 301 | Container( 302 | height: 70, 303 | width: 70, 304 | child: Hero( 305 | tag: heroTag, 306 | child: buildAppIcon(index, app.artworkUrl100) 307 | ) 308 | ), 309 | Expanded( 310 | child: Container( 311 | margin: EdgeInsets.only(left: 12, right: 12), 312 | child: Column( 313 | crossAxisAlignment: CrossAxisAlignment.start, 314 | children: [ 315 | Hero( 316 | tag: titleTag, 317 | child: Text( 318 | name, 319 | overflow: TextOverflow.ellipsis, 320 | maxLines: 1 321 | ), 322 | ), 323 | Text( 324 | category, 325 | maxLines: 1, 326 | style: TextStyle( 327 | color: greyColor 328 | ), 329 | ), 330 | Row( 331 | children: [ 332 | SmoothStarRating( 333 | allowHalfRating: false, 334 | starCount: 5, 335 | rating: rating, 336 | size: 15.0, 337 | color: Colors.orange, 338 | borderColor: Colors.orange, 339 | ), 340 | Text( 341 | "($userCount)" 342 | ) 343 | ], 344 | ) 345 | 346 | ], 347 | ) 348 | ) 349 | ) 350 | 351 | ], 352 | ) 353 | ) 354 | ), 355 | Container( 356 | margin: EdgeInsets.only(left: 20), 357 | child: Divider(height: 4, color: greyColor), 358 | ) 359 | ], 360 | ); 361 | 362 | } 363 | 364 | Widget buildAppIcon(int index, String iconUrl){ 365 | BorderRadius radius; 366 | if(0 == index % 2){ 367 | // Rounded 368 | radius = BorderRadius.circular(16.0); 369 | } 370 | else{ 371 | // Circle 372 | radius = BorderRadius.circular(35.0); 373 | } 374 | 375 | return ClipRRect( 376 | borderRadius: radius, 377 | child: CachedNetworkImage( 378 | imageUrl: iconUrl, 379 | errorWidget: (context, url, error) => new Icon(Icons.error), 380 | fadeOutDuration: new Duration(seconds: 1), 381 | fadeInDuration: new Duration(seconds: 1) 382 | ) 383 | ); 384 | } 385 | 386 | 387 | } -------------------------------------------------------------------------------- /lib/config/Env.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_starter_kit/app/model/core/AppComponent.dart'; 3 | import 'package:flutter_starter_kit/app/model/core/AppStoreApplication.dart'; 4 | import 'package:flutter_stetho/flutter_stetho.dart'; 5 | 6 | enum EnvType { 7 | DEVELOPMENT, 8 | STAGING, 9 | PRODUCTION, 10 | TESTING 11 | } 12 | 13 | class Env { 14 | 15 | static Env value; 16 | 17 | String appName; 18 | String baseUrl; 19 | Color primarySwatch; 20 | EnvType environmentType = EnvType.DEVELOPMENT; 21 | 22 | // Database Config 23 | int dbVersion = 1; 24 | String dbName; 25 | 26 | 27 | Env() { 28 | value = this; 29 | _init(); 30 | } 31 | 32 | void _init() async{ 33 | WidgetsFlutterBinding.ensureInitialized(); 34 | 35 | if(EnvType.DEVELOPMENT == environmentType || EnvType.STAGING == environmentType){ 36 | Stetho.initialize(); 37 | } 38 | 39 | var application = AppStoreApplication(); 40 | await application.onCreate(); 41 | runApp(AppComponent(application)); 42 | } 43 | } -------------------------------------------------------------------------------- /lib/config/main_development.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_starter_kit/config/Env.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | void main() => Development(); 5 | 6 | class Development extends Env { 7 | final String appName = "Flutter Starter Dev"; 8 | final String baseUrl = 'https://api.dev.website.org'; 9 | final Color primarySwatch = Colors.pink; 10 | EnvType environmentType = EnvType.DEVELOPMENT; 11 | 12 | final String dbName = 'flutterStarter-Dev.db'; 13 | 14 | } -------------------------------------------------------------------------------- /lib/config/main_production.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_starter_kit/config/Env.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | void main() => Production(); 5 | 6 | class Production extends Env { 7 | final String appName = "Flutter Starter"; 8 | 9 | final String baseUrl = 'https://api.website.org'; 10 | final Color primarySwatch = Colors.teal; 11 | EnvType environmentType = EnvType.PRODUCTION; 12 | 13 | final String dbName = 'flutterStarter.db'; 14 | 15 | } -------------------------------------------------------------------------------- /lib/config/main_staging.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_starter_kit/config/Env.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | void main() => Staging(); 5 | 6 | class Staging extends Env { 7 | final String appName = "Flutter Starter Staging"; 8 | 9 | final String baseUrl = 'https://api.staging.website.org'; 10 | final Color primarySwatch = Colors.amber; 11 | EnvType environmentType = EnvType.STAGING; 12 | 13 | final String dbName = 'flutterStarter-Stg.db'; 14 | } 15 | -------------------------------------------------------------------------------- /lib/generated/i18n.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:gen_lang/generate.dart 2 | 3 | import 'dart:async'; 4 | 5 | import 'package:intl/intl.dart'; 6 | import 'package:flutter/material.dart'; 7 | 8 | import 'messages_all.dart'; 9 | 10 | class S { 11 | 12 | static const GeneratedLocalizationsDelegate delegate = GeneratedLocalizationsDelegate(); 13 | 14 | static S of(BuildContext context) { 15 | return Localizations.of(context, S); 16 | } 17 | 18 | static Future load(Locale locale) { 19 | final String name = locale.countryCode == null ? locale.languageCode : locale.toString(); 20 | 21 | final String localeName = Intl.canonicalizedLocale(name); 22 | 23 | return initializeMessages(localeName).then((bool _) { 24 | Intl.defaultLocale = localeName; 25 | return new S(); 26 | }); 27 | } 28 | 29 | String get hello { 30 | return Intl.message('Hello', name: 'hello'); 31 | } 32 | 33 | String get title { 34 | return Intl.message('Hello world App', name: 'title'); 35 | } 36 | 37 | String get dialogLoading { 38 | return Intl.message('Loading ...', name: 'dialogLoading'); 39 | } 40 | 41 | String get homeEmptyList { 42 | return Intl.message('No results', name: 'homeEmptyList'); 43 | } 44 | 45 | String get homeSearchHint { 46 | return Intl.message('Search ...', name: 'homeSearchHint'); 47 | } 48 | 49 | String get homeRecommend { 50 | return Intl.message('Recommend', name: 'homeRecommend'); 51 | } 52 | 53 | String get detailRate { 54 | return Intl.message('Comments', name: 'detailRate'); 55 | } 56 | 57 | 58 | } 59 | 60 | class GeneratedLocalizationsDelegate extends LocalizationsDelegate { 61 | const GeneratedLocalizationsDelegate(); 62 | 63 | List get supportedLocales { 64 | return const [ 65 | Locale("en", ""), 66 | Locale("ja", ""), 67 | Locale("zh", "TW"), 68 | Locale("de", ""), 69 | 70 | ]; 71 | } 72 | 73 | LocaleListResolutionCallback listResolution({Locale fallback}) { 74 | return (List locales, Iterable supported) { 75 | if (locales == null || locales.isEmpty) { 76 | return fallback ?? supported.first; 77 | } else { 78 | return _resolve(locales.first, fallback, supported); 79 | } 80 | }; 81 | } 82 | 83 | LocaleResolutionCallback resolution({Locale fallback}) { 84 | return (Locale locale, Iterable supported) { 85 | return _resolve(locale, fallback, supported); 86 | }; 87 | } 88 | 89 | Locale _resolve(Locale locale, Locale fallback, Iterable supported) { 90 | if (locale == null || !isSupported(locale)) { 91 | return fallback ?? supported.first; 92 | } 93 | 94 | final Locale languageLocale = Locale(locale.languageCode, ""); 95 | if (supported.contains(locale)) { 96 | return locale; 97 | } else if (supported.contains(languageLocale)) { 98 | return languageLocale; 99 | } else { 100 | final Locale fallbackLocale = fallback ?? supported.first; 101 | return fallbackLocale; 102 | } 103 | } 104 | 105 | @override 106 | Future load(Locale locale) { 107 | return S.load(locale); 108 | } 109 | 110 | @override 111 | bool isSupported(Locale locale) => 112 | locale != null && supportedLocales.contains(locale); 113 | 114 | @override 115 | bool shouldReload(GeneratedLocalizationsDelegate old) => false; 116 | } 117 | -------------------------------------------------------------------------------- /lib/generated/messages_all.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:gen_lang/generate.dart 2 | 3 | import 'dart:async'; 4 | 5 | import 'package:intl/intl.dart'; 6 | import 'package:intl/message_lookup_by_library.dart'; 7 | // ignore: implementation_imports 8 | import 'package:intl/src/intl_helpers.dart'; 9 | 10 | final _$ja = $ja(); 11 | 12 | class $ja extends MessageLookupByLibrary { 13 | get localeName => 'ja'; 14 | 15 | final messages = { 16 | "hello" : MessageLookupByLibrary.simpleMessage("Hello"), 17 | "title" : MessageLookupByLibrary.simpleMessage("Hello world App"), 18 | "dialogLoading" : MessageLookupByLibrary.simpleMessage("読み込み中 ..."), 19 | "homeEmptyList" : MessageLookupByLibrary.simpleMessage("結果なし"), 20 | "homeSearchHint" : MessageLookupByLibrary.simpleMessage("検索 ..."), 21 | "homeRecommend" : MessageLookupByLibrary.simpleMessage("おすすめ"), 22 | "detailRate" : MessageLookupByLibrary.simpleMessage("コメント"), 23 | 24 | }; 25 | } 26 | 27 | final _$zh_TW = $zh_TW(); 28 | 29 | class $zh_TW extends MessageLookupByLibrary { 30 | get localeName => 'zh_TW'; 31 | 32 | final messages = { 33 | "hello" : MessageLookupByLibrary.simpleMessage("Hello"), 34 | "title" : MessageLookupByLibrary.simpleMessage("Hello world App"), 35 | "dialogLoading" : MessageLookupByLibrary.simpleMessage("載入中 ..."), 36 | "homeEmptyList" : MessageLookupByLibrary.simpleMessage("沒有結果"), 37 | "homeSearchHint" : MessageLookupByLibrary.simpleMessage("搜索 ..."), 38 | "homeRecommend" : MessageLookupByLibrary.simpleMessage("推介"), 39 | "detailRate" : MessageLookupByLibrary.simpleMessage("評論"), 40 | 41 | }; 42 | } 43 | 44 | final _$en = $en(); 45 | 46 | class $en extends MessageLookupByLibrary { 47 | get localeName => 'en'; 48 | 49 | final messages = { 50 | "hello" : MessageLookupByLibrary.simpleMessage("Hello"), 51 | "title" : MessageLookupByLibrary.simpleMessage("Hello world App"), 52 | "dialogLoading" : MessageLookupByLibrary.simpleMessage("Loading ..."), 53 | "homeEmptyList" : MessageLookupByLibrary.simpleMessage("No results"), 54 | "homeSearchHint" : MessageLookupByLibrary.simpleMessage("Search ..."), 55 | "homeRecommend" : MessageLookupByLibrary.simpleMessage("Recommend"), 56 | "detailRate" : MessageLookupByLibrary.simpleMessage("Comments"), 57 | 58 | }; 59 | } 60 | 61 | final _$de = $de(); 62 | 63 | class $de extends MessageLookupByLibrary { 64 | get localeName => 'de'; 65 | 66 | final messages = { 67 | "hello" : MessageLookupByLibrary.simpleMessage("Hello"), 68 | "title" : MessageLookupByLibrary.simpleMessage("Hello world App"), 69 | "dialogLoading" : MessageLookupByLibrary.simpleMessage("Wird geladen ..."), 70 | "homeEmptyList" : MessageLookupByLibrary.simpleMessage("Keine Ergebnisse"), 71 | "homeSearchHint" : MessageLookupByLibrary.simpleMessage("Suche ..."), 72 | "homeRecommend" : MessageLookupByLibrary.simpleMessage("Empfehlen"), 73 | "detailRate" : MessageLookupByLibrary.simpleMessage("Bemerkungen"), 74 | 75 | }; 76 | } 77 | 78 | 79 | 80 | typedef Future LibraryLoader(); 81 | Map _deferredLibraries = { 82 | "ja": () => Future.value(null), 83 | "zh_TW": () => Future.value(null), 84 | "en": () => Future.value(null), 85 | "de": () => Future.value(null), 86 | 87 | }; 88 | 89 | MessageLookupByLibrary _findExact(localeName) { 90 | switch (localeName) { 91 | case "ja": 92 | return _$ja; 93 | case "zh_TW": 94 | return _$zh_TW; 95 | case "en": 96 | return _$en; 97 | case "de": 98 | return _$de; 99 | 100 | default: 101 | return null; 102 | } 103 | } 104 | 105 | /// User programs should call this before using [localeName] for messages. 106 | Future initializeMessages(String localeName) async { 107 | var availableLocale = Intl.verifiedLocale( 108 | localeName, 109 | (locale) => _deferredLibraries[locale] != null, 110 | onFailure: (_) => null); 111 | if (availableLocale == null) { 112 | return Future.value(false); 113 | } 114 | var lib = _deferredLibraries[availableLocale]; 115 | await (lib == null ? Future.value(false) : lib()); 116 | 117 | initializeInternalMessageLookup(() => CompositeMessageLookup()); 118 | messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor); 119 | 120 | return Future.value(true); 121 | } 122 | 123 | bool _messagesExistFor(String locale) { 124 | try { 125 | return _findExact(locale) != null; 126 | } catch (e) { 127 | return false; 128 | } 129 | } 130 | 131 | MessageLookupByLibrary _findGeneratedMessagesFor(locale) { 132 | var actualLocale = Intl.verifiedLocale(locale, _messagesExistFor, 133 | onFailure: (_) => null); 134 | if (actualLocale == null) return null; 135 | return _findExact(actualLocale); 136 | } 137 | -------------------------------------------------------------------------------- /lib/utility/db/DatabaseHelper.dart: -------------------------------------------------------------------------------- 1 | import 'package:path/path.dart'; 2 | import 'package:sqflite/sqflite.dart'; 3 | 4 | class DatabaseHelper { 5 | 6 | Database _database; 7 | DatabaseConfig databaseConfig; 8 | 9 | 10 | DatabaseHelper(this.databaseConfig){ 11 | if(null == databaseConfig){ 12 | throw NullThrownError; 13 | } 14 | } 15 | 16 | Future open() async { 17 | var path = await _getDBPath(); 18 | 19 | _database = await openDatabase(path, version: databaseConfig.version, 20 | onCreate: null != databaseConfig.databaseMigrationListener ? databaseConfig.databaseMigrationListener.onCreate : null, 21 | onUpgrade: null != databaseConfig.databaseMigrationListener ? databaseConfig.databaseMigrationListener.onUpgrade : null 22 | ); 23 | } 24 | 25 | Future deleteDB() async{ 26 | var path = await _getDBPath(); 27 | await deleteDatabase(path); 28 | } 29 | 30 | Future close() async{ 31 | if(null != _database){ 32 | await _database.close(); 33 | } 34 | } 35 | 36 | Future _getDBPath() async{ 37 | var databasesPath = await getDatabasesPath(); 38 | return join(databasesPath, databaseConfig.dbName); 39 | } 40 | 41 | Database get database => _database; 42 | 43 | } 44 | 45 | class DatabaseConfig{ 46 | int version; 47 | String dbName; 48 | DatabaseMigrationListener databaseMigrationListener; 49 | 50 | DatabaseConfig(this.version, this.dbName, this.databaseMigrationListener); 51 | } 52 | 53 | abstract class DatabaseMigrationListener{ 54 | void onCreate(Database db, int version); 55 | void onUpgrade(Database db, int oldVersion, int newVersion); 56 | } -------------------------------------------------------------------------------- /lib/utility/framework/Application.dart: -------------------------------------------------------------------------------- 1 | abstract class Application{ 2 | void onCreate(); 3 | void onTerminate(); 4 | } -------------------------------------------------------------------------------- /lib/utility/http/HttpException.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | 3 | class HttpException implements Exception{ 4 | Response response; 5 | 6 | HttpException(this.response); 7 | } -------------------------------------------------------------------------------- /lib/utility/log/DioLogger.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:flutter_starter_kit/utility/log/Log.dart'; 3 | 4 | class DioLogger{ 5 | static void onSend(String tag, RequestOptions options){ 6 | Log.info('$tag - Request Path : [${options.method}] ${options.baseUrl}${options.path}'); 7 | Log.info('$tag - Request Data : ${options.data.toString()}'); 8 | } 9 | 10 | static void onSuccess(String tag, Response response){ 11 | Log.info('$tag - Response Path : [${response.request.method}] ${response.request.baseUrl}${response.request.path} Request Data : ${response.request.data.toString()}'); 12 | Log.info('$tag - Response statusCode : ${response.statusCode}'); 13 | Log.info('$tag - Response data : ${response.data.toString()}'); 14 | } 15 | 16 | static void onError(String tag, DioError error){ 17 | if(null != error.response){ 18 | Log.info('$tag - Error Path : [${error.response.request.method}] ${error.response.request.baseUrl}${error.response.request.path} Request Data : ${error.response.request.data.toString()}'); 19 | Log.info('$tag - Error statusCode : ${error.response.statusCode}'); 20 | Log.info('$tag - Error data : ${null != error.response.data ? error.response.data.toString() : ''}'); 21 | } 22 | Log.info('$tag - Error Message : ${error.message}'); 23 | } 24 | } -------------------------------------------------------------------------------- /lib/utility/log/Log.dart: -------------------------------------------------------------------------------- 1 | import 'package:logging/logging.dart'; 2 | 3 | class Log{ 4 | static const String _NAME = 'Logger'; 5 | static Logger _instance; 6 | 7 | static void init(){ 8 | Logger.root.onRecord.listen((record) { 9 | print('${record.level.name}: ${record.time}: ${record.message}'); 10 | }); 11 | _instance = Logger(_NAME); 12 | } 13 | 14 | static void setLevel(Level level){ 15 | Logger.root.level = level; 16 | } 17 | 18 | static void info(message, [Object error, StackTrace stackTrace]){ 19 | _instance.info(message, error, stackTrace); 20 | } 21 | 22 | static void warning(message, [Object error, StackTrace stackTrace]){ 23 | _instance.warning(message, error, stackTrace); 24 | } 25 | 26 | static void config(message, [Object error, StackTrace stackTrace]){ 27 | _instance.config(message, error, stackTrace); 28 | } 29 | 30 | static void fine(message, [Object error, StackTrace stackTrace]){ 31 | _instance.fine(message, error, stackTrace); 32 | } 33 | 34 | static void finer(message, [Object error, StackTrace stackTrace]){ 35 | _instance.finer(message, error, stackTrace); 36 | } 37 | 38 | static void finest(message, [Object error, StackTrace stackTrace]){ 39 | _instance.finest(message, error, stackTrace); 40 | } 41 | 42 | static void severe(message, [Object error, StackTrace stackTrace]){ 43 | _instance.severe(message, error, stackTrace); 44 | } 45 | 46 | static void shout(message, [Object error, StackTrace stackTrace]){ 47 | _instance.shout(message, error, stackTrace); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/utility/widget/StreamListItem.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/widgets.dart'; 4 | 5 | class StreamListItem extends StatefulWidget{ 6 | 7 | final T initialData; 8 | final StreamListItemComparator comparator; 9 | final StreamListItemBuilder builder; 10 | final Stream stream; 11 | 12 | StreamListItem({Key key, this.initialData, this.comparator, this.builder, this.stream}) : super(key: key); 13 | 14 | @override 15 | State createState() { 16 | return _StreamListItemState(); 17 | } 18 | } 19 | 20 | 21 | typedef StreamListItemBuilder = Widget Function(BuildContext context, T data); 22 | typedef StreamListItemComparator = bool Function(T data, S changedObject); 23 | 24 | class _StreamListItemState extends State> { 25 | 26 | T _data; 27 | StreamSubscription _subscription; 28 | 29 | @override 30 | void initState() { 31 | super.initState(); 32 | this._data = widget.initialData; 33 | _subscribe(); 34 | } 35 | 36 | @override 37 | void didUpdateWidget(StreamListItem oldWidget) { 38 | super.didUpdateWidget(oldWidget); 39 | if (oldWidget.stream != widget.stream) { 40 | if (_subscription != null) { 41 | _unsubscribe(); 42 | } 43 | _subscribe(); 44 | } 45 | } 46 | 47 | @override 48 | void dispose() { 49 | super.dispose(); 50 | _unsubscribe(); 51 | } 52 | 53 | @override 54 | Widget build(BuildContext context) { 55 | return widget.builder(context, _data); 56 | } 57 | 58 | void _subscribe(){ 59 | _subscription = widget.stream.listen((idObj){ 60 | bool isSameObjId = widget.comparator(_data, idObj); 61 | if(isSameObjId){ 62 | setState((){}); // Force update 63 | } 64 | }); 65 | } 66 | 67 | void _unsubscribe() { 68 | if (_subscription != null) { 69 | _subscription.cancel(); 70 | _subscription = null; 71 | } 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_starter_kit 2 | description: Flutter Starter Kit with Bloc pattern and Rxdart 3 | authors: 4 | - King Wu 5 | homepage: https://github.com/KingWu/flutter_starter_kit 6 | maintainer: King Wu (@kw101) 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # Read more about versioning at semver.org. 14 | version: 1.4.1 15 | 16 | environment: 17 | sdk: ">=2.0.0-dev.68.0 <3.0.0" 18 | 19 | dependencies: 20 | flutter: 21 | sdk: flutter 22 | flutter_localizations: 23 | sdk: flutter 24 | fluro: 1.6.3 25 | dio: 3.0.9 26 | logging: 0.11.4 27 | json_annotation: 3.0.1 28 | sprintf: 4.0.2 29 | rxdart: 0.22.6 30 | cached_network_image: 2.0.0 31 | sqflite: 1.3.0+2 32 | smooth_star_rating: 1.1.1 33 | flutter_stetho: 0.5.2 34 | analyzer: 0.39.10 35 | 36 | # The following adds the Cupertino Icons font to your application. 37 | # Use with the CupertinoIcons class for iOS style icons. 38 | cupertino_icons: 0.1.3 39 | flutter_platform_widgets: 0.50.0 40 | 41 | dev_dependencies: 42 | flutter_test: 43 | sdk: flutter 44 | build_runner: 1.10.0 45 | json_serializable: 3.3.0 46 | gen_lang: 0.1.3 47 | lang_table: 0.2.0 48 | 49 | 50 | # For information on the generic Dart part of this file, see the 51 | # following page: https://www.dartlang.org/tools/pub/pubspec 52 | 53 | # The following section is specific to Flutter. 54 | flutter: 55 | 56 | # The following line ensures that the Material Icons font isSupported 57 | # included with your application, so that you can use the icons in 58 | # the material Icons class. 59 | uses-material-design: true 60 | 61 | # To add assets to your application, add an assets section, like this: 62 | assets: 63 | - images/ 64 | 65 | # An image asset can refer to one or more resolution-specific "variants", see 66 | # https://flutter.io/assets-and-images/#resolution-aware. 67 | 68 | # For details regarding adding assets from package dependencies, see 69 | # https://flutter.io/assets-and-images/#from-packages 70 | 71 | # To add custom fonts to your application, add a fonts section here, 72 | # in this "flutter" section. Each entry in this list should have a 73 | # "family" key with the font family name, and a "fonts" key with a 74 | # list giving the asset and other descriptors for the font. For 75 | # example: 76 | # fonts: 77 | # - family: Schyler 78 | # fonts: 79 | # - asset: fonts/Schyler-Regular.ttf 80 | # - asset: fonts/Schyler-Italic.ttf 81 | # style: italic 82 | # - family: Trajan Pro 83 | # fonts: 84 | # - asset: fonts/TrajanPro.ttf 85 | # - asset: fonts/TrajanPro_Bold.ttf 86 | # weight: 700 87 | # 88 | # For details regarding fonts from package dependencies, 89 | # see https://flutter.io/custom-fonts/#from-packages 90 | -------------------------------------------------------------------------------- /res/string/string_de.json: -------------------------------------------------------------------------------- 1 | { 2 | "hello":"Hello", 3 | "title":"Hello world App", 4 | "dialogLoading":"Wird geladen ...", 5 | "homeEmptyList":"Keine Ergebnisse", 6 | "homeSearchHint":"Suche ...", 7 | "homeRecommend":"Empfehlen", 8 | "detailRate":"Bemerkungen" 9 | } -------------------------------------------------------------------------------- /res/string/string_en.json: -------------------------------------------------------------------------------- 1 | { 2 | "hello":"Hello", 3 | "title":"Hello world App", 4 | "dialogLoading":"Loading ...", 5 | "homeEmptyList":"No results", 6 | "homeSearchHint":"Search ...", 7 | "homeRecommend":"Recommend", 8 | "detailRate":"Comments" 9 | } -------------------------------------------------------------------------------- /res/string/string_ja.json: -------------------------------------------------------------------------------- 1 | { 2 | "hello":"Hello", 3 | "title":"Hello world App", 4 | "dialogLoading":"読み込み中 ...", 5 | "homeEmptyList":"結果なし", 6 | "homeSearchHint":"検索 ...", 7 | "homeRecommend":"おすすめ", 8 | "detailRate":"コメント" 9 | } -------------------------------------------------------------------------------- /res/string/string_zh_TW.json: -------------------------------------------------------------------------------- 1 | { 2 | "hello":"Hello", 3 | "title":"Hello world App", 4 | "dialogLoading":"載入中 ...", 5 | "homeEmptyList":"沒有結果", 6 | "homeSearchHint":"搜索 ...", 7 | "homeRecommend":"推介", 8 | "detailRate":"評論" 9 | } -------------------------------------------------------------------------------- /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_test/flutter_test.dart'; 9 | 10 | //void main() { 11 | // testWidgets('Counter increments smoke test', (WidgetTester tester) async { 12 | // // Build our app and trigger a frame. 13 | // await tester.pumpWidget(AppComponent()); 14 | // 15 | // // Verify that our counter starts at 0. 16 | // expect(find.text('0'), findsOneWidget); 17 | // expect(find.text('1'), findsNothing); 18 | // 19 | // // Tap the '+' icon and trigger a frame. 20 | // await tester.tap(find.byIcon(Icons.add)); 21 | // await tester.pump(); 22 | // 23 | // // Verify that our counter has incremented. 24 | // expect(find.text('0'), findsNothing); 25 | // expect(find.text('1'), findsOneWidget); 26 | // }); 27 | //} 28 | --------------------------------------------------------------------------------