├── .gitignore ├── .metadata ├── README.md ├── android ├── app │ ├── build.gradle │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── club │ │ │ └── flutterchina │ │ │ └── flutterinaction │ │ │ └── MainActivity.java │ │ └── 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 │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── fonts └── iconfont.ttf ├── github_client_app ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ │ └── club │ │ │ │ │ └── flutteraction │ │ │ │ │ └── github_client_app │ │ │ │ │ └── MainActivity.java │ │ │ └── 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 │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── fonts │ └── iconfont.ttf ├── i10n-arb │ ├── intl_messages.arb │ └── intl_zh_CN.arb ├── imgs │ ├── avatar-default.png │ ├── logo_dark.png │ └── logo_light.png ├── intl.sh ├── ios │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── flutter_export_environment.sh │ ├── Podfile │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ └── contents.xcworkspacedata │ └── Runner │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── main.m ├── jsons │ ├── cacheConfig.json │ ├── profile.json │ ├── repo.json │ └── user.json ├── lib │ ├── common │ │ ├── funs.dart │ │ ├── git_api.dart │ │ ├── global.dart │ │ ├── icons.dart │ │ ├── index.dart │ │ └── net_cache.dart │ ├── index.dart │ ├── l10n │ │ ├── localization_intl.dart │ │ ├── messages_all.dart │ │ ├── messages_messages.dart │ │ └── messages_zh_CN.dart │ ├── main.dart │ ├── models │ │ ├── cacheConfig.dart │ │ ├── cacheConfig.g.dart │ │ ├── index.dart │ │ ├── profile.dart │ │ ├── profile.g.dart │ │ ├── repo.dart │ │ ├── repo.g.dart │ │ ├── user.dart │ │ └── user.g.dart │ ├── routes │ │ ├── home_page.dart │ │ ├── index.dart │ │ ├── language.dart │ │ ├── login.dart │ │ └── theme_change.dart │ ├── states │ │ ├── index.dart │ │ └── profile_change_notifier.dart │ └── widgets │ │ ├── index.dart │ │ └── repo_item.dart ├── pubspec.yaml └── test │ └── widget_test.dart ├── home.png ├── img_des.txt ├── imgs └── avatar.png ├── intl.sh ├── ios ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ ├── Flutter.podspec │ ├── Release.xcconfig │ └── flutter_export_environment.sh ├── Podfile ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── Runner │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── main.m ├── l10n-arb ├── intl_messages.arb └── intl_zh_CN.arb ├── lib ├── common.dart ├── event_bus.dart ├── http.dart ├── i10n │ ├── _localization.dart │ ├── localization_intl.dart │ ├── messages_all.dart │ ├── messages_messages.dart │ └── messages_zh_CN.dart ├── main.dart ├── provider.dart ├── routes │ ├── align.dart │ ├── animated_switcher_counter.dart │ ├── animated_widgets.dart │ ├── animation_switcher.dart │ ├── button.dart │ ├── camera.dart │ ├── clip.dart │ ├── color.dart │ ├── context.dart │ ├── custom_paint.dart │ ├── custom_ui_framework.dart │ ├── decoratedbox.dart │ ├── dialog.dart │ ├── future_and_stream_builder.dart │ ├── gradient_button.dart │ ├── gradient_circular_progress.dart │ ├── grow_transition.dart │ ├── hero_animation.dart │ ├── image_icon.dart │ ├── image_internal.dart │ ├── index.dart │ ├── inheritedwidget.dart │ ├── notification.dart │ ├── padding.dart │ ├── platform_view.dart │ ├── pointer.dart │ ├── progress.dart │ ├── provider.dart │ ├── router.dart │ ├── row_column.dart │ ├── scaffold.dart │ ├── scale_animation.dart │ ├── scale_animation_animatedbuilder.dart │ ├── scale_animation_animatedwidget.dart │ ├── size_constraints.dart │ ├── stagger_animation.dart │ ├── state.dart │ ├── switch_checkbox.dart │ ├── table.dart │ ├── test.dart │ ├── text.dart │ ├── textfield.dart │ ├── theme.dart │ └── turn_box.dart └── widgets │ ├── gradient_button.dart │ ├── gradient_circular_progress_indicator.dart │ ├── index.dart │ ├── page_scaffold.dart │ └── turn_box.dart ├── pubspec.yaml └── 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 | 31 | # Android related 32 | **/android/**/gradle-wrapper.jar 33 | **/android/.gradle 34 | **/android/captures/ 35 | **/android/gradlew 36 | **/android/gradlew.bat 37 | **/android/local.properties 38 | **/android/**/GeneratedPluginRegistrant.java 39 | 40 | # iOS/XCode related 41 | **/ios/**/*.mode1v3 42 | **/ios/**/*.mode2v3 43 | **/ios/**/*.moved-aside 44 | **/ios/**/*.pbxuser 45 | **/ios/**/*.perspectivev3 46 | **/ios/**/*sync/ 47 | **/ios/**/.sconsign.dblite 48 | **/ios/**/.tags* 49 | **/ios/**/.vagrant/ 50 | **/ios/**/DerivedData/ 51 | **/ios/**/Icon? 52 | **/ios/**/Pods/ 53 | **/ios/**/.symlinks/ 54 | **/ios/**/profile 55 | **/ios/**/xcuserdata 56 | **/ios/.generated/ 57 | **/ios/Flutter/App.framework 58 | **/ios/Flutter/Flutter.framework 59 | **/ios/Flutter/Generated.xcconfig 60 | **/ios/Flutter/app.flx 61 | **/ios/Flutter/app.zip 62 | **/ios/Flutter/flutter_assets/ 63 | **/ios/ServiceDefinitions.json 64 | **/ios/Runner/GeneratedPluginRegistrant.* 65 | 66 | # Exceptions to above rules. 67 | !**/ios/**/default.mode1v3 68 | !**/ios/**/default.mode2v3 69 | !**/ios/**/default.pbxuser 70 | !**/ios/**/default.perspectivev3 71 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 72 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 《Flutter实战》随书源码 2 | 3 | 本项目为[《Flutter实战》电子书](https://github.com/flutterchina/flutter-in-action)随书源码,本书实体书名待定。 4 | 5 | ## 说明 6 | 7 | 1. 示例文件列表可以参照main.dart。 8 | 2. 并不是《Flutter实战》所有示例都有源码。如有必要,欢迎读者补充其它示例(提PR). 9 | 3. ”实例篇“源码位于 "github_client_app"子目录下,需要用IDE单独打开 10 | 11 | ## 运行效果 12 | 13 | ![运行界面](home.png) 14 | 15 | ## 打赏 16 | 17 | 对我有帮助?请作者喝杯咖啡 18 | 19 | ![](https://cdn.jsdelivr.net/gh/flutterchina/flutter-in-action@1.0.3/docs/imgs/pay.jpeg) 20 | -------------------------------------------------------------------------------- /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 from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 28 29 | 30 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | 34 | defaultConfig { 35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 36 | applicationId "club.flutterchina.flutterinaction" 37 | minSdkVersion 21 38 | targetSdkVersion 28 39 | versionCode flutterVersionCode.toInteger() 40 | versionName flutterVersionName 41 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | testImplementation 'junit:junit:4.12' 59 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 60 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 61 | } 62 | -------------------------------------------------------------------------------- /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/java/club/flutterchina/flutterinaction/MainActivity.java: -------------------------------------------------------------------------------- 1 | package club.flutterchina.flutterinaction; 2 | 3 | import android.os.Bundle; 4 | import io.flutter.app.FlutterActivity; 5 | import io.flutter.plugins.GeneratedPluginRegistrant; 6 | 7 | public class MainActivity extends FlutterActivity { 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | GeneratedPluginRegistrant.registerWith(this); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /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/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.2.1' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | -------------------------------------------------------------------------------- /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-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /fonts/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/fonts/iconfont.ttf -------------------------------------------------------------------------------- /github_client_app/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | /build/ 31 | 32 | # Android related 33 | **/android/**/gradle-wrapper.jar 34 | **/android/.gradle 35 | **/android/captures/ 36 | **/android/gradlew 37 | **/android/gradlew.bat 38 | **/android/local.properties 39 | **/android/**/GeneratedPluginRegistrant.java 40 | 41 | # iOS/XCode related 42 | **/ios/**/*.mode1v3 43 | **/ios/**/*.mode2v3 44 | **/ios/**/*.moved-aside 45 | **/ios/**/*.pbxuser 46 | **/ios/**/*.perspectivev3 47 | **/ios/**/*sync/ 48 | **/ios/**/.sconsign.dblite 49 | **/ios/**/.tags* 50 | **/ios/**/.vagrant/ 51 | **/ios/**/DerivedData/ 52 | **/ios/**/Icon? 53 | **/ios/**/Pods/ 54 | **/ios/**/.symlinks/ 55 | **/ios/**/profile 56 | **/ios/**/xcuserdata 57 | **/ios/.generated/ 58 | **/ios/Flutter/App.framework 59 | **/ios/Flutter/Flutter.framework 60 | **/ios/Flutter/Generated.xcconfig 61 | **/ios/Flutter/app.flx 62 | **/ios/Flutter/app.zip 63 | **/ios/Flutter/flutter_assets/ 64 | **/ios/ServiceDefinitions.json 65 | **/ios/Runner/GeneratedPluginRegistrant.* 66 | 67 | # Exceptions to above rules. 68 | !**/ios/**/default.mode1v3 69 | !**/ios/**/default.mode2v3 70 | !**/ios/**/default.pbxuser 71 | !**/ios/**/default.perspectivev3 72 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 73 | -------------------------------------------------------------------------------- /github_client_app/.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: 67237b960ceeb2bd3d3a646de126c883fabae6d5 8 | channel: master 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /github_client_app/README.md: -------------------------------------------------------------------------------- 1 | # github_client_app 2 | 3 | A Github client APP built with Flutter 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /github_client_app/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 from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 28 29 | 30 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | 34 | defaultConfig { 35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 36 | applicationId "club.flutteraction.github_client_app" 37 | minSdkVersion 16 38 | targetSdkVersion 28 39 | versionCode flutterVersionCode.toInteger() 40 | versionName flutterVersionName 41 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | testImplementation 'junit:junit:4.12' 59 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 60 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 61 | } 62 | -------------------------------------------------------------------------------- /github_client_app/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /github_client_app/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 13 | 20 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /github_client_app/android/app/src/main/java/club/flutteraction/github_client_app/MainActivity.java: -------------------------------------------------------------------------------- 1 | package club.flutteraction.github_client_app; 2 | 3 | import android.os.Bundle; 4 | import io.flutter.app.FlutterActivity; 5 | import io.flutter.plugins.GeneratedPluginRegistrant; 6 | 7 | public class MainActivity extends FlutterActivity { 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | GeneratedPluginRegistrant.registerWith(this); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /github_client_app/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /github_client_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/github_client_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /github_client_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/github_client_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /github_client_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/github_client_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /github_client_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/github_client_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /github_client_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/github_client_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /github_client_app/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /github_client_app/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /github_client_app/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.2.1' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /github_client_app/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | 3 | android.enableR8=true 4 | -------------------------------------------------------------------------------- /github_client_app/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-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /github_client_app/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 | -------------------------------------------------------------------------------- /github_client_app/fonts/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/github_client_app/fonts/iconfont.ttf -------------------------------------------------------------------------------- /github_client_app/i10n-arb/intl_messages.arb: -------------------------------------------------------------------------------- 1 | { 2 | "@@last_modified": "2019-08-11T18:39:59.060353", 3 | "title": "Flutter APP", 4 | "@title": { 5 | "description": "Title for the Demo application", 6 | "type": "text", 7 | "placeholders": {} 8 | }, 9 | "home": "Github", 10 | "@home": { 11 | "type": "text", 12 | "placeholders": {} 13 | }, 14 | "language": "Language", 15 | "@language": { 16 | "type": "text", 17 | "placeholders": {} 18 | }, 19 | "login": "Login", 20 | "@login": { 21 | "type": "text", 22 | "placeholders": {} 23 | }, 24 | "auto": "Auto", 25 | "@auto": { 26 | "type": "text", 27 | "placeholders": {} 28 | }, 29 | "setting": "Setting", 30 | "@setting": { 31 | "type": "text", 32 | "placeholders": {} 33 | }, 34 | "theme": "Theme", 35 | "@theme": { 36 | "type": "text", 37 | "placeholders": {} 38 | }, 39 | "noDescription": "No description yet !", 40 | "@noDescription": { 41 | "type": "text", 42 | "placeholders": {} 43 | }, 44 | "userName": "User Name", 45 | "@userName": { 46 | "type": "text", 47 | "placeholders": {} 48 | }, 49 | "userNameRequired": "User name required!", 50 | "@userNameRequired": { 51 | "type": "text", 52 | "placeholders": {} 53 | }, 54 | "password": "Password", 55 | "@password": { 56 | "type": "text", 57 | "placeholders": {} 58 | }, 59 | "passwordRequired": "Password required!", 60 | "@passwordRequired": { 61 | "type": "text", 62 | "placeholders": {} 63 | }, 64 | "userNameOrPasswordWrong": "User name or password is not correct!", 65 | "@userNameOrPasswordWrong": { 66 | "type": "text", 67 | "placeholders": {} 68 | }, 69 | "logout": "logout", 70 | "@logout": { 71 | "type": "text", 72 | "placeholders": {} 73 | }, 74 | "logoutTip": "Are you sure you want to quit your current account?", 75 | "@logoutTip": { 76 | "type": "text", 77 | "placeholders": {} 78 | }, 79 | "yes": "yes", 80 | "@yes": { 81 | "type": "text", 82 | "placeholders": {} 83 | }, 84 | "cancel": "cancel", 85 | "@cancel": { 86 | "type": "text", 87 | "placeholders": {} 88 | } 89 | } -------------------------------------------------------------------------------- /github_client_app/i10n-arb/intl_zh_CN.arb: -------------------------------------------------------------------------------- 1 | { 2 | "@@last_modified": "2019-04-30T15:02:44.753296", 3 | "title": "Github客户端", 4 | "@title": { 5 | "description": "Title for the Demo application", 6 | "type": "text", 7 | "placeholders": {} 8 | }, 9 | "home": "Github客户端", 10 | "@home": { 11 | "type": "text", 12 | "placeholders": {} 13 | }, 14 | "language": "语言", 15 | "@language": { 16 | "type": "text", 17 | "placeholders": {} 18 | }, 19 | "login": "登录", 20 | "@login": { 21 | "type": "text", 22 | "placeholders": {} 23 | }, 24 | "auto": "跟随系统", 25 | "@auto": { 26 | "type": "text", 27 | "placeholders": {} 28 | }, 29 | "setting": "设置", 30 | "@setting": { 31 | "type": "text", 32 | "placeholders": {} 33 | }, 34 | "theme": "换肤", 35 | "@theme": { 36 | "type": "text", 37 | "placeholders": {} 38 | }, 39 | "noDescription": "暂无描述!", 40 | "@noDescription": { 41 | "type": "text", 42 | "placeholders": {} 43 | }, 44 | "userName": "用户名", 45 | "@userName": { 46 | "type": "text", 47 | "placeholders": {} 48 | }, 49 | "userNameRequired": "用户名不能为空", 50 | "@userNameRequired": { 51 | "type": "text", 52 | "placeholders": {} 53 | }, 54 | "password": "密码", 55 | "@password": { 56 | "type": "text", 57 | "placeholders": {} 58 | }, 59 | "passwordRequired": "密码不能为空", 60 | "@passwordRequired": { 61 | "type": "text", 62 | "placeholders": {} 63 | }, 64 | "userNameOrPasswordWrong": "用户名或密码不正确", 65 | "@userNameOrPasswordWrong": { 66 | "type": "text", 67 | "placeholders": {} 68 | }, 69 | "logout": "注销", 70 | "@logout": { 71 | "type": "text", 72 | "placeholders": {} 73 | }, 74 | "logoutTip": "确定要退出当前账号吗?", 75 | "@logoutTip": { 76 | "type": "text", 77 | "placeholders": {} 78 | }, 79 | "yes": "确定", 80 | "@yes": { 81 | "type": "text", 82 | "placeholders": {} 83 | }, 84 | "cancel": "取消", 85 | "@cancel": { 86 | "type": "text", 87 | "placeholders": {} 88 | } 89 | } -------------------------------------------------------------------------------- /github_client_app/imgs/avatar-default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/github_client_app/imgs/avatar-default.png -------------------------------------------------------------------------------- /github_client_app/imgs/logo_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/github_client_app/imgs/logo_dark.png -------------------------------------------------------------------------------- /github_client_app/imgs/logo_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/github_client_app/imgs/logo_light.png -------------------------------------------------------------------------------- /github_client_app/intl.sh: -------------------------------------------------------------------------------- 1 | flutter pub pub run intl_translation:extract_to_arb --output-dir=l10n-arb lib/l10n/localization_intl.dart 2 | flutter pub pub run intl_translation:generate_from_arb --output-dir=lib/l10n --no-use-deferred-loading lib/l10n/localization_intl.dart l10n-arb/intl_*.arb -------------------------------------------------------------------------------- /github_client_app/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /github_client_app/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /github_client_app/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /github_client_app/ios/Flutter/flutter_export_environment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This is a generated file; do not edit or check into version control. 3 | export "FLUTTER_ROOT=/Users/duwen/Documents/flutter" 4 | export "FLUTTER_APPLICATION_PATH=/Users/duwen/Documents/code/flutter_in_action/github_client_app" 5 | export "FLUTTER_TARGET=/Users/duwen/Documents/code/flutter_in_action/github_client_app/lib/main.dart" 6 | export "FLUTTER_BUILD_DIR=build" 7 | export "SYMROOT=${SOURCE_ROOT}/../build/ios" 8 | export "OTHER_LDFLAGS=$(inherited) -framework Flutter" 9 | export "FLUTTER_FRAMEWORK_DIR=/Users/duwen/Documents/flutter/bin/cache/artifacts/engine/ios" 10 | export "FLUTTER_BUILD_NAME=1.0.0" 11 | export "FLUTTER_BUILD_NUMBER=1" 12 | export "TRACK_WIDGET_CREATION=true" 13 | -------------------------------------------------------------------------------- /github_client_app/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 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 37 | # referring to absolute paths on developers' machines. 38 | system('rm -rf .symlinks') 39 | system('mkdir -p .symlinks/plugins') 40 | 41 | # Flutter Pods 42 | generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') 43 | if generated_xcode_build_settings.empty? 44 | puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first." 45 | end 46 | generated_xcode_build_settings.map { |p| 47 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR' 48 | symlink = File.join('.symlinks', 'flutter') 49 | File.symlink(File.dirname(p[:path]), symlink) 50 | pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) 51 | end 52 | } 53 | 54 | # Plugin Pods 55 | plugin_pods = parse_KV_file('../.flutter-plugins') 56 | plugin_pods.map { |p| 57 | symlink = File.join('.symlinks', 'plugins', p[:name]) 58 | File.symlink(p[:path], symlink) 59 | pod p[:name], :path => File.join(symlink, 'ios') 60 | } 61 | end 62 | 63 | # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. 64 | install! 'cocoapods', :disable_input_output_paths => true 65 | 66 | post_install do |installer| 67 | installer.pods_project.targets.each do |target| 68 | target.build_configurations.each do |config| 69 | config.build_settings['ENABLE_BITCODE'] = 'NO' 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /github_client_app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /github_client_app/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /github_client_app/ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /github_client_app/ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /github_client_app/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 | -------------------------------------------------------------------------------- /github_client_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/github_client_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /github_client_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/github_client_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /github_client_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/github_client_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /github_client_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/github_client_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /github_client_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/github_client_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /github_client_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/github_client_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /github_client_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/github_client_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /github_client_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/github_client_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /github_client_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/github_client_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /github_client_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/github_client_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /github_client_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/github_client_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /github_client_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/github_client_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /github_client_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/github_client_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /github_client_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/github_client_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /github_client_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/github_client_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /github_client_app/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 | -------------------------------------------------------------------------------- /github_client_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/github_client_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /github_client_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/github_client_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /github_client_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/github_client_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /github_client_app/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. -------------------------------------------------------------------------------- /github_client_app/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 | -------------------------------------------------------------------------------- /github_client_app/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 | -------------------------------------------------------------------------------- /github_client_app/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | github_client_app 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /github_client_app/ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /github_client_app/jsons/cacheConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "enable":true, 3 | "maxAge":1000, 4 | "maxCount":100 5 | } -------------------------------------------------------------------------------- /github_client_app/jsons/profile.json: -------------------------------------------------------------------------------- 1 | { 2 | "user":"$user", 3 | "token":"", 4 | "theme":5678, 5 | "cache":"$cacheConfig", 6 | "lastLogin":"", 7 | "locale":"en" 8 | } -------------------------------------------------------------------------------- /github_client_app/jsons/repo.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1296269, 3 | "name": "Hello-World", 4 | "full_name": "octocat/Hello-World", 5 | "owner": "$user", 6 | "parent":"$repo", 7 | "private": false, 8 | "html_url": "https://github.com/octocat/Hello-World", 9 | "description": "This your first repo!", 10 | "fork": false, 11 | "homepage": "https://github.com", 12 | "language": "JavaScript", 13 | "forks_count": 9, 14 | "stargazers_count": 80, 15 | "watchers_count": 80, 16 | "size": 108, 17 | "default_branch": "master", 18 | "open_issues_count": 0, 19 | "topics": [ 20 | "octocat", 21 | "atom", 22 | "electron", 23 | "API" 24 | ], 25 | "has_issues": true, 26 | "has_projects": true, 27 | "has_wiki": true, 28 | "has_pages": false, 29 | "has_downloads": true, 30 | "pushed_at": "2011-01-26T19:06:43Z", 31 | "created_at": "2011-01-26T19:01:12Z", 32 | "updated_at": "2011-01-26T19:14:43Z", 33 | "permissions": { 34 | "admin": false, 35 | "push": false, 36 | "pull": true 37 | }, 38 | "subscribers_count": 42, 39 | "license": { 40 | "key": "mit", 41 | "name": "MIT License", 42 | "spdx_id": "MIT", 43 | "url": "https://api.github.com/licenses/mit", 44 | "node_id": "MDc6TGljZW5zZW1pdA==" 45 | } 46 | } -------------------------------------------------------------------------------- /github_client_app/jsons/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "login": "octocat", 3 | "id": 1, 4 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 5 | "url": "https://api.github.com/users/octocat", 6 | "type": "User", 7 | "site_admin": false, 8 | "name": "monalisa octocat", 9 | "company": "GitHub", 10 | "blog": "https://github.com/blog", 11 | "location": "San Francisco", 12 | "email": "octocat@github.com", 13 | "hireable": false, 14 | "bio": "There once was...", 15 | "public_repos": 2, 16 | "public_gists": 1, 17 | "followers": 20, 18 | "following": 0, 19 | "created_at": "2008-01-14T04:33:35Z", 20 | "updated_at": "2008-01-14T04:33:35Z", 21 | "total_private_repos": 100, 22 | "owned_private_repos": 100 23 | } -------------------------------------------------------------------------------- /github_client_app/lib/common/funs.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:cached_network_image/cached_network_image.dart'; 3 | import 'package:fluttertoast/fluttertoast.dart'; 4 | 5 | Widget gmAvatar(String url, { 6 | double width = 30, 7 | double height, 8 | BoxFit fit, 9 | BorderRadius borderRadius, 10 | }) { 11 | var placeholder = Image.asset( 12 | "imgs/avatar-default.png", //头像默认值 13 | width: width, 14 | height: height 15 | ); 16 | return ClipRRect( 17 | borderRadius: borderRadius ?? BorderRadius.circular(2), 18 | child: CachedNetworkImage( 19 | imageUrl: url, 20 | width: width, 21 | height: height, 22 | fit: fit, 23 | placeholder: (context, url) =>placeholder, 24 | errorWidget: (context, url, error) =>placeholder, 25 | ), 26 | ); 27 | } 28 | 29 | void showToast(String text, { 30 | gravity: ToastGravity.CENTER, 31 | toastLength: Toast.LENGTH_SHORT, 32 | }) { 33 | Fluttertoast.showToast( 34 | msg: text, 35 | toastLength: Toast.LENGTH_SHORT, 36 | gravity: ToastGravity.BOTTOM, 37 | timeInSecForIos: 1, 38 | backgroundColor: Colors.grey[600], 39 | fontSize: 16.0, 40 | ); 41 | } 42 | 43 | void showLoading(context, [String text]) { 44 | text = text ?? "Loading..."; 45 | showDialog( 46 | barrierDismissible: false, 47 | context: context, 48 | builder: (context) { 49 | return Center( 50 | child: Container( 51 | decoration: BoxDecoration( 52 | color: Colors.white, 53 | borderRadius: BorderRadius.circular(3.0), 54 | boxShadow: [ 55 | //阴影 56 | BoxShadow( 57 | color: Colors.black12, 58 | //offset: Offset(2.0,2.0), 59 | blurRadius: 10.0, 60 | ) 61 | ]), 62 | padding: EdgeInsets.all(16), 63 | margin: EdgeInsets.all(16), 64 | constraints: BoxConstraints(minHeight: 120, minWidth: 180), 65 | child: Column( 66 | mainAxisSize: MainAxisSize.min, 67 | mainAxisAlignment: MainAxisAlignment.center, 68 | children: [ 69 | SizedBox( 70 | height: 30, 71 | width: 30, 72 | child: CircularProgressIndicator( 73 | strokeWidth: 3, 74 | ), 75 | ), 76 | Padding( 77 | padding: const EdgeInsets.only(top: 20.0), 78 | child: Text( 79 | text, 80 | style: Theme 81 | .of(context) 82 | .textTheme 83 | .body2, 84 | ), 85 | ), 86 | ], 87 | ), 88 | ), 89 | ); 90 | }); 91 | } -------------------------------------------------------------------------------- /github_client_app/lib/common/git_api.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'dart:io'; 4 | import 'package:dio/adapter.dart'; 5 | import 'package:dio/dio.dart'; 6 | import 'package:flutter/material.dart'; 7 | import '../index.dart'; 8 | 9 | class Git { 10 | // 在网络请求过程中可能会需要使用当前的context信息,比如在请求失败时 11 | // 打开一个新路由,而打开新路由需要context信息。 12 | Git([this.context]) { 13 | _options = Options(extra: {"context": context}); 14 | } 15 | 16 | BuildContext context; 17 | Options _options; 18 | static Dio dio = new Dio(BaseOptions( 19 | baseUrl: 'https://api.github.com/', 20 | headers: { 21 | HttpHeaders.acceptHeader: "application/vnd.github.squirrel-girl-preview," 22 | "application/vnd.github.symmetra-preview+json", 23 | }, 24 | )); 25 | 26 | static void init() { 27 | // 添加缓存插件 28 | dio.interceptors.add(Global.netCache); 29 | // 设置用户token(可能为null,代表未登录) 30 | dio.options.headers[HttpHeaders.authorizationHeader] = Global.profile.token; 31 | 32 | // 在调试模式下需要抓包调试,所以我们使用代理,并禁用HTTPS证书校验 33 | if (!Global.isRelease) { 34 | (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = 35 | (client) { 36 | client.findProxy = (uri) { 37 | return "PROXY 10.95.249.53:8888"; 38 | }; 39 | //代理工具会提供一个抓包的自签名证书,会通不过证书校验,所以我们禁用证书校验 40 | client.badCertificateCallback = 41 | (X509Certificate cert, String host, int port) => true; 42 | }; 43 | } 44 | } 45 | 46 | // 登录接口,登录成功后返回用户信息 47 | Future login(String login, String pwd) async { 48 | String basic = 'Basic ' + base64.encode(utf8.encode('$login:$pwd')); 49 | var r = await dio.get( 50 | "/users/$login", 51 | options: _options.merge(headers: { 52 | HttpHeaders.authorizationHeader: basic 53 | }, extra: { 54 | "noCache": true, //本接口禁用缓存 55 | }), 56 | ); 57 | //登录成功后更新公共头(authorization),此后的所有请求都会带上用户身份信息 58 | dio.options.headers[HttpHeaders.authorizationHeader] = basic; 59 | //清空所有缓存 60 | Global.netCache.cache.clear(); 61 | //更新profile中的token信息 62 | Global.profile.token = basic; 63 | return User.fromJson(r.data); 64 | } 65 | 66 | //获取用户项目列表 67 | Future> getRepos( 68 | {Map queryParameters, //query参数,用于接收分页信息 69 | refresh = false}) async { 70 | if (refresh) { 71 | // 列表下拉刷新,需要删除缓存(拦截器中会读取这些信息) 72 | _options.extra.addAll({"refresh": true, "list": true}); 73 | } 74 | var r = await dio.get( 75 | "user/repos", 76 | queryParameters: queryParameters, 77 | options: _options, 78 | ); 79 | return r.data.map((e) => Repo.fromJson(e)).toList(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /github_client_app/lib/common/global.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:shared_preferences/shared_preferences.dart'; 4 | import '../index.dart'; 5 | 6 | // 提供四套可选主题色 7 | const _themes = [ 8 | Colors.blue, 9 | Colors.cyan, 10 | Colors.teal, 11 | Colors.green, 12 | Colors.red, 13 | ]; 14 | 15 | class Global { 16 | static SharedPreferences _prefs; 17 | static Profile profile = Profile(); 18 | // 网络缓存对象 19 | static NetCache netCache = NetCache(); 20 | 21 | // 可选的主题列表 22 | static List get themes => _themes; 23 | 24 | // 是否为release版 25 | static bool get isRelease => bool.fromEnvironment("dart.vm.product"); 26 | 27 | //初始化全局信息 28 | static Future init() async { 29 | WidgetsFlutterBinding.ensureInitialized(); 30 | _prefs = await SharedPreferences.getInstance(); 31 | var _profile = _prefs.getString("profile"); 32 | if (_profile != null) { 33 | try { 34 | profile = Profile.fromJson(jsonDecode(_profile)); 35 | } catch (e) { 36 | print(e); 37 | } 38 | } 39 | 40 | // 如果没有缓存策略,设置默认缓存策略 41 | profile.cache = profile.cache ?? CacheConfig() 42 | ..enable = true 43 | ..maxAge = 3600 44 | ..maxCount = 100; 45 | 46 | //初始化网络请求相关配置 47 | Git.init(); 48 | } 49 | 50 | // 持久化Profile信息 51 | static saveProfile() => 52 | _prefs.setString("profile", jsonEncode(profile.toJson())); 53 | } 54 | -------------------------------------------------------------------------------- /github_client_app/lib/common/icons.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class MyIcons { 4 | static const IconData github_outline = IconData(0xe799, fontFamily: 'myIcon'); 5 | static const IconData github = IconData(0xe7ab, fontFamily: 'myIcon'); 6 | static const IconData fork = IconData(0xe65b, fontFamily: 'myIcon'); 7 | static const IconData pr = IconData(0xe8b8, fontFamily: 'myIcon'); 8 | static const IconData code = IconData(0xe646, fontFamily: 'myIcon'); 9 | } -------------------------------------------------------------------------------- /github_client_app/lib/common/index.dart: -------------------------------------------------------------------------------- 1 | export 'git_api.dart'; 2 | export 'global.dart'; 3 | export 'net_cache.dart'; 4 | export 'icons.dart'; 5 | export 'funs.dart'; -------------------------------------------------------------------------------- /github_client_app/lib/common/net_cache.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | import 'package:dio/dio.dart'; 3 | import '../index.dart'; 4 | 5 | class CacheObject { 6 | CacheObject(this.response) 7 | : timeStamp = DateTime.now().millisecondsSinceEpoch; 8 | Response response; 9 | int timeStamp; 10 | 11 | @override 12 | bool operator ==(other) { 13 | return response.hashCode == other.hashCode; 14 | } 15 | 16 | @override 17 | int get hashCode => response.realUri.hashCode; 18 | } 19 | 20 | class NetCache extends Interceptor { 21 | // 为确保迭代器顺序和对象插入时间一致顺序一致,我们使用LinkedHashMap 22 | var cache = LinkedHashMap(); 23 | 24 | @override 25 | onRequest(RequestOptions options) async { 26 | if (!Global.profile.cache.enable) return options; 27 | // refresh标记是否是"下拉刷新" 28 | bool refresh = options.extra["refresh"] == true; 29 | //如果是下拉刷新,先删除相关缓存 30 | if (refresh) { 31 | if (options.extra["list"] == true) { 32 | //若是列表,则只要url中包含当前path的缓存全部删除(简单实现,并不精准) 33 | cache.removeWhere((key, v) => key.contains(options.path)); 34 | } else { 35 | // 如果不是列表,则只删除uri相同的缓存 36 | delete(options.uri.toString()); 37 | } 38 | return options; 39 | } 40 | if (options.extra["noCache"] != true && 41 | options.method.toLowerCase() == 'get') { 42 | String key = options.extra["cacheKey"] ?? options.uri.toString(); 43 | var ob = cache[key]; 44 | if (ob != null) { 45 | //若缓存未过期,则返回缓存内容 46 | if ((DateTime.now().millisecondsSinceEpoch - ob.timeStamp) / 1000 < 47 | Global.profile.cache.maxAge) { 48 | return cache[key].response; 49 | } else { 50 | //若已过期则删除缓存,继续向服务器请求 51 | cache.remove(key); 52 | } 53 | } 54 | } 55 | } 56 | 57 | 58 | @override 59 | onResponse(Response response) async { 60 | // 如果启用缓存,将返回结果保存到缓存 61 | if (Global.profile.cache.enable) { 62 | _saveCache(response); 63 | } 64 | } 65 | 66 | _saveCache(Response object) { 67 | RequestOptions options = object.request; 68 | if (options.extra["noCache"] != true && 69 | options.method.toLowerCase() == "get") { 70 | // 如果缓存数量超过最大数量限制,则先移除最早的一条记录 71 | if (cache.length == Global.profile.cache.maxCount) { 72 | cache.remove(cache[cache.keys.first]); 73 | } 74 | String key = options.extra["cacheKey"] ?? options.uri.toString(); 75 | cache[key] = CacheObject(object); 76 | } 77 | } 78 | 79 | void delete(String key) { 80 | cache.remove(key); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /github_client_app/lib/index.dart: -------------------------------------------------------------------------------- 1 | export 'package:github_client_app/common/global.dart'; 2 | export 'models/index.dart'; 3 | export 'states/index.dart'; 4 | export 'routes/index.dart'; 5 | export 'widgets/index.dart'; 6 | export 'l10n/localization_intl.dart'; 7 | export 'package:provider/provider.dart'; 8 | export 'common/index.dart'; 9 | export 'package:flutter/material.dart'; 10 | 11 | 12 | -------------------------------------------------------------------------------- /github_client_app/lib/l10n/localization_intl.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:intl/intl.dart'; 3 | import 'messages_all.dart'; //1 4 | 5 | class GmLocalizations { 6 | static Future load(Locale locale) { 7 | final String name = 8 | locale.countryCode.isEmpty ? locale.languageCode : locale.toString(); 9 | final String localeName = Intl.canonicalizedLocale(name); 10 | //2 11 | return initializeMessages(localeName).then((b) { 12 | Intl.defaultLocale = localeName; 13 | return new GmLocalizations(); 14 | }); 15 | } 16 | 17 | static GmLocalizations of(BuildContext context) { 18 | return Localizations.of(context, GmLocalizations); 19 | } 20 | 21 | String get title { 22 | return Intl.message( 23 | 'Flutter APP', 24 | name: 'title', 25 | desc: 'Title for the Demo application', 26 | ); 27 | } 28 | 29 | String get home => Intl.message('Github', name: 'home'); 30 | 31 | String get language => Intl.message('Language', name: 'language'); 32 | 33 | String get login => Intl.message('Login', name: 'login'); 34 | 35 | String get auto => Intl.message('Auto', name: 'auto'); 36 | 37 | String get setting => Intl.message('Setting', name: 'setting'); 38 | 39 | String get theme => Intl.message('Theme', name: 'theme'); 40 | 41 | String get noDescription => 42 | Intl.message('No description yet !', name: 'noDescription'); 43 | 44 | String get userName => Intl.message('User Name', name: 'userName'); 45 | String get userNameRequired => Intl.message("User name required!" , name: 'userNameRequired'); 46 | String get password => Intl.message('Password', name: 'password'); 47 | String get passwordRequired => Intl.message('Password required!', name: 'passwordRequired'); 48 | String get userNameOrPasswordWrong=>Intl.message('User name or password is not correct!', name: 'userNameOrPasswordWrong'); 49 | String get logout => Intl.message('logout', name: 'logout'); 50 | String get logoutTip => Intl.message('Are you sure you want to quit your current account?', name: 'logoutTip'); 51 | String get yes => Intl.message('yes', name: 'yes'); 52 | String get cancel => Intl.message('cancel', name: 'cancel'); 53 | } 54 | 55 | //Locale代理类 56 | class GmLocalizationsDelegate extends LocalizationsDelegate { 57 | const GmLocalizationsDelegate(); 58 | 59 | //是否支持某个Local 60 | @override 61 | bool isSupported(Locale locale) => ['en', 'zh'].contains(locale.languageCode); 62 | 63 | // Flutter会调用此类加载相应的Locale资源类 64 | @override 65 | Future load(Locale locale) { 66 | //3 67 | return GmLocalizations.load(locale); 68 | } 69 | 70 | // 当Localizations Widget重新build时,是否调用load重新加载Locale资源. 71 | @override 72 | bool shouldReload(GmLocalizationsDelegate old) => false; 73 | } 74 | -------------------------------------------------------------------------------- /github_client_app/lib/l10n/messages_all.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart 2 | // This is a library that looks up messages for specific locales by 3 | // delegating to the appropriate library. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:intl/intl.dart'; 8 | import 'package:intl/message_lookup_by_library.dart'; 9 | // ignore: implementation_imports 10 | import 'package:intl/src/intl_helpers.dart'; 11 | 12 | import 'messages_messages.dart' as messages_messages; 13 | import 'messages_zh_CN.dart' as messages_zh_cn; 14 | 15 | typedef Future LibraryLoader(); 16 | Map _deferredLibraries = { 17 | // ignore: unnecessary_new 18 | 'messages': () => new Future.value(null), 19 | // ignore: unnecessary_new 20 | 'zh_CN': () => new Future.value(null), 21 | }; 22 | 23 | MessageLookupByLibrary _findExact(localeName) { 24 | switch (localeName) { 25 | case 'messages': 26 | return messages_messages.messages; 27 | case 'zh_CN': 28 | return messages_zh_cn.messages; 29 | default: 30 | return null; 31 | } 32 | } 33 | 34 | /// User programs should call this before using [localeName] for messages. 35 | Future initializeMessages(String localeName) async { 36 | var availableLocale = Intl.verifiedLocale( 37 | localeName, 38 | (locale) => _deferredLibraries[locale] != null, 39 | onFailure: (_) => null); 40 | if (availableLocale == null) { 41 | // ignore: unnecessary_new 42 | return new Future.value(false); 43 | } 44 | var lib = _deferredLibraries[availableLocale]; 45 | // ignore: unnecessary_new 46 | await (lib == null ? new Future.value(false) : lib()); 47 | // ignore: unnecessary_new 48 | initializeInternalMessageLookup(() => new CompositeMessageLookup()); 49 | messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor); 50 | // ignore: unnecessary_new 51 | return new Future.value(true); 52 | } 53 | 54 | bool _messagesExistFor(String locale) { 55 | try { 56 | return _findExact(locale) != null; 57 | } catch (e) { 58 | return false; 59 | } 60 | } 61 | 62 | MessageLookupByLibrary _findGeneratedMessagesFor(locale) { 63 | var actualLocale = Intl.verifiedLocale(locale, _messagesExistFor, 64 | onFailure: (_) => null); 65 | if (actualLocale == null) return null; 66 | return _findExact(actualLocale); 67 | } 68 | -------------------------------------------------------------------------------- /github_client_app/lib/l10n/messages_messages.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart 2 | // This is a library that provides messages for a messages locale. All the 3 | // messages from the main program should be duplicated here with the same 4 | // function name. 5 | 6 | // ignore_for_file: unnecessary_brace_in_string_interps 7 | 8 | import 'package:intl/intl.dart'; 9 | import 'package:intl/message_lookup_by_library.dart'; 10 | 11 | // ignore: unnecessary_new 12 | final messages = new MessageLookup(); 13 | 14 | // ignore: unused_element 15 | final _keepAnalysisHappy = Intl.defaultLocale; 16 | 17 | // ignore: non_constant_identifier_names 18 | typedef MessageIfAbsent(String message_str, List args); 19 | 20 | class MessageLookup extends MessageLookupByLibrary { 21 | get localeName => 'messages'; 22 | 23 | final messages = _notInlinedMessages(_notInlinedMessages); 24 | static _notInlinedMessages(_) => { 25 | "auto" : MessageLookupByLibrary.simpleMessage("Auto"), 26 | "cancel" : MessageLookupByLibrary.simpleMessage("cancel"), 27 | "home" : MessageLookupByLibrary.simpleMessage("Github"), 28 | "language" : MessageLookupByLibrary.simpleMessage("Language"), 29 | "login" : MessageLookupByLibrary.simpleMessage("Login"), 30 | "logout" : MessageLookupByLibrary.simpleMessage("logout"), 31 | "logoutTip" : MessageLookupByLibrary.simpleMessage("Are you sure you want to quit your current account?"), 32 | "noDescription" : MessageLookupByLibrary.simpleMessage("No description yet !"), 33 | "password" : MessageLookupByLibrary.simpleMessage("Password"), 34 | "passwordRequired" : MessageLookupByLibrary.simpleMessage("Password required!"), 35 | "setting" : MessageLookupByLibrary.simpleMessage("Setting"), 36 | "theme" : MessageLookupByLibrary.simpleMessage("Theme"), 37 | "title" : MessageLookupByLibrary.simpleMessage("Flutter APP"), 38 | "userName" : MessageLookupByLibrary.simpleMessage("User Name"), 39 | "userNameOrPasswordWrong" : MessageLookupByLibrary.simpleMessage("User name or password is not correct!"), 40 | "userNameRequired" : MessageLookupByLibrary.simpleMessage("User name required!"), 41 | "yes" : MessageLookupByLibrary.simpleMessage("yes") 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /github_client_app/lib/l10n/messages_zh_CN.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart 2 | // This is a library that provides messages for a zh_CN locale. All the 3 | // messages from the main program should be duplicated here with the same 4 | // function name. 5 | 6 | // ignore_for_file: unnecessary_brace_in_string_interps 7 | 8 | import 'package:intl/intl.dart'; 9 | import 'package:intl/message_lookup_by_library.dart'; 10 | 11 | // ignore: unnecessary_new 12 | final messages = new MessageLookup(); 13 | 14 | // ignore: unused_element 15 | final _keepAnalysisHappy = Intl.defaultLocale; 16 | 17 | // ignore: non_constant_identifier_names 18 | typedef MessageIfAbsent(String message_str, List args); 19 | 20 | class MessageLookup extends MessageLookupByLibrary { 21 | get localeName => 'zh_CN'; 22 | 23 | final messages = _notInlinedMessages(_notInlinedMessages); 24 | static _notInlinedMessages(_) => { 25 | "auto" : MessageLookupByLibrary.simpleMessage("跟随系统"), 26 | "cancel" : MessageLookupByLibrary.simpleMessage("取消"), 27 | "home" : MessageLookupByLibrary.simpleMessage("Github客户端"), 28 | "language" : MessageLookupByLibrary.simpleMessage("语言"), 29 | "login" : MessageLookupByLibrary.simpleMessage("登录"), 30 | "logout" : MessageLookupByLibrary.simpleMessage("注销"), 31 | "logoutTip" : MessageLookupByLibrary.simpleMessage("确定要退出当前账号吗?"), 32 | "noDescription" : MessageLookupByLibrary.simpleMessage("暂无描述!"), 33 | "password" : MessageLookupByLibrary.simpleMessage("密码"), 34 | "passwordRequired" : MessageLookupByLibrary.simpleMessage("密码不能为空"), 35 | "setting" : MessageLookupByLibrary.simpleMessage("设置"), 36 | "theme" : MessageLookupByLibrary.simpleMessage("换肤"), 37 | "title" : MessageLookupByLibrary.simpleMessage("Github客户端"), 38 | "userName" : MessageLookupByLibrary.simpleMessage("用户名"), 39 | "userNameOrPasswordWrong" : MessageLookupByLibrary.simpleMessage("用户名或密码不正确"), 40 | "userNameRequired" : MessageLookupByLibrary.simpleMessage("用户名不能为空"), 41 | "yes" : MessageLookupByLibrary.simpleMessage("确定") 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /github_client_app/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | import 'package:flutter_localizations/flutter_localizations.dart'; 4 | import 'index.dart'; 5 | 6 | void main() => Global.init().then((e) => runApp(MyApp())); 7 | 8 | class MyApp extends StatelessWidget { 9 | // This widget is the root of your application. 10 | @override 11 | Widget build(BuildContext context) { 12 | return MultiProvider( 13 | providers: [ 14 | ChangeNotifierProvider.value(value: ThemeModel()), 15 | ChangeNotifierProvider.value(value: UserModel()), 16 | ChangeNotifierProvider.value(value: LocaleModel()), 17 | ], 18 | child: Consumer2( 19 | builder: (BuildContext context, themeModel, localeModel, Widget child) { 20 | return MaterialApp( 21 | theme: ThemeData( 22 | primarySwatch: themeModel.theme, 23 | ), 24 | onGenerateTitle: (context){ 25 | return GmLocalizations.of(context).title; 26 | }, 27 | home: HomeRoute(), 28 | locale: localeModel.getLocale(), 29 | //我们只支持美国英语和中文简体 30 | supportedLocales: [ 31 | const Locale('en', 'US'), // 美国英语 32 | const Locale('zh', 'CN'), // 中文简体 33 | //其它Locales 34 | ], 35 | localizationsDelegates: [ 36 | // 本地化的代理类 37 | GlobalMaterialLocalizations.delegate, 38 | GlobalWidgetsLocalizations.delegate, 39 | GmLocalizationsDelegate() 40 | ], 41 | localeResolutionCallback: 42 | (Locale _locale, Iterable supportedLocales) { 43 | if (localeModel.getLocale() != null) { 44 | //如果已经选定语言,则不跟随系统 45 | return localeModel.getLocale(); 46 | } else { 47 | //跟随系统 48 | Locale locale; 49 | if (supportedLocales.contains(_locale)) { 50 | locale= _locale; 51 | } else { 52 | //如果系统语言不是中文简体或美国英语,则默认使用美国英语 53 | locale= Locale('en', 'US'); 54 | } 55 | return locale; 56 | } 57 | }, 58 | // 注册路由表 59 | routes: { 60 | "login": (context) => LoginRoute(), 61 | "themes": (context) => ThemeChangeRoute(), 62 | "language": (context) => LanguageRoute(), 63 | }, 64 | ); 65 | }, 66 | ), 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /github_client_app/lib/models/cacheConfig.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'cacheConfig.g.dart'; 4 | 5 | @JsonSerializable() 6 | class CacheConfig { 7 | CacheConfig(); 8 | 9 | bool enable; 10 | num maxAge; 11 | num maxCount; 12 | 13 | factory CacheConfig.fromJson(Map json) => _$CacheConfigFromJson(json); 14 | Map toJson() => _$CacheConfigToJson(this); 15 | } 16 | -------------------------------------------------------------------------------- /github_client_app/lib/models/cacheConfig.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'cacheConfig.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | CacheConfig _$CacheConfigFromJson(Map json) { 10 | return CacheConfig() 11 | ..enable = json['enable'] as bool 12 | ..maxAge = json['maxAge'] as num 13 | ..maxCount = json['maxCount'] as num; 14 | } 15 | 16 | Map _$CacheConfigToJson(CacheConfig instance) => 17 | { 18 | 'enable': instance.enable, 19 | 'maxAge': instance.maxAge, 20 | 'maxCount': instance.maxCount 21 | }; 22 | -------------------------------------------------------------------------------- /github_client_app/lib/models/index.dart: -------------------------------------------------------------------------------- 1 | export 'repo.dart' ; 2 | export 'cacheConfig.dart' ; 3 | export 'user.dart' ; 4 | export 'profile.dart' ; 5 | -------------------------------------------------------------------------------- /github_client_app/lib/models/profile.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | import "user.dart"; 3 | import "cacheConfig.dart"; 4 | part 'profile.g.dart'; 5 | 6 | @JsonSerializable() 7 | class Profile { 8 | Profile(); 9 | 10 | User user; 11 | String token; 12 | num theme; 13 | CacheConfig cache; 14 | String lastLogin; 15 | String locale; 16 | 17 | factory Profile.fromJson(Map json) => _$ProfileFromJson(json); 18 | Map toJson() => _$ProfileToJson(this); 19 | } 20 | -------------------------------------------------------------------------------- /github_client_app/lib/models/profile.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'profile.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | Profile _$ProfileFromJson(Map json) { 10 | return Profile() 11 | ..user = json['user'] == null 12 | ? null 13 | : User.fromJson(json['user'] as Map) 14 | ..token = json['token'] as String 15 | ..theme = json['theme'] as num 16 | ..cache = json['cache'] == null 17 | ? null 18 | : CacheConfig.fromJson(json['cache'] as Map) 19 | ..lastLogin = json['lastLogin'] as String 20 | ..locale = json['locale'] as String; 21 | } 22 | 23 | Map _$ProfileToJson(Profile instance) => { 24 | 'user': instance.user, 25 | 'token': instance.token, 26 | 'theme': instance.theme, 27 | 'cache': instance.cache, 28 | 'lastLogin': instance.lastLogin, 29 | 'locale': instance.locale 30 | }; 31 | -------------------------------------------------------------------------------- /github_client_app/lib/models/repo.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | import "user.dart"; 3 | part 'repo.g.dart'; 4 | 5 | @JsonSerializable() 6 | class Repo { 7 | Repo(); 8 | 9 | num id; 10 | String name; 11 | String full_name; 12 | User owner; 13 | Repo parent; 14 | bool private; 15 | String html_url; 16 | String description; 17 | bool fork; 18 | String homepage; 19 | String language; 20 | num forks_count; 21 | num stargazers_count; 22 | num watchers_count; 23 | num size; 24 | String default_branch; 25 | num open_issues_count; 26 | List topics; 27 | bool has_issues; 28 | bool has_projects; 29 | bool has_wiki; 30 | bool has_pages; 31 | bool has_downloads; 32 | String pushed_at; 33 | String created_at; 34 | String updated_at; 35 | Map permissions; 36 | num subscribers_count; 37 | Map license; 38 | 39 | factory Repo.fromJson(Map json) => _$RepoFromJson(json); 40 | Map toJson() => _$RepoToJson(this); 41 | } 42 | -------------------------------------------------------------------------------- /github_client_app/lib/models/repo.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'repo.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | Repo _$RepoFromJson(Map json) { 10 | return Repo() 11 | ..id = json['id'] as num 12 | ..name = json['name'] as String 13 | ..full_name = json['full_name'] as String 14 | ..owner = json['owner'] == null 15 | ? null 16 | : User.fromJson(json['owner'] as Map) 17 | ..parent = json['parent'] == null 18 | ? null 19 | : Repo.fromJson(json['parent'] as Map) 20 | ..private = json['private'] as bool 21 | ..html_url = json['html_url'] as String 22 | ..description = json['description'] as String 23 | ..fork = json['fork'] as bool 24 | ..homepage = json['homepage'] as String 25 | ..language = json['language'] as String 26 | ..forks_count = json['forks_count'] as num 27 | ..stargazers_count = json['stargazers_count'] as num 28 | ..watchers_count = json['watchers_count'] as num 29 | ..size = json['size'] as num 30 | ..default_branch = json['default_branch'] as String 31 | ..open_issues_count = json['open_issues_count'] as num 32 | ..topics = json['topics'] as List 33 | ..has_issues = json['has_issues'] as bool 34 | ..has_projects = json['has_projects'] as bool 35 | ..has_wiki = json['has_wiki'] as bool 36 | ..has_pages = json['has_pages'] as bool 37 | ..has_downloads = json['has_downloads'] as bool 38 | ..pushed_at = json['pushed_at'] as String 39 | ..created_at = json['created_at'] as String 40 | ..updated_at = json['updated_at'] as String 41 | ..permissions = json['permissions'] as Map 42 | ..subscribers_count = json['subscribers_count'] as num 43 | ..license = json['license'] as Map; 44 | } 45 | 46 | Map _$RepoToJson(Repo instance) => { 47 | 'id': instance.id, 48 | 'name': instance.name, 49 | 'full_name': instance.full_name, 50 | 'owner': instance.owner, 51 | 'parent': instance.parent, 52 | 'private': instance.private, 53 | 'html_url': instance.html_url, 54 | 'description': instance.description, 55 | 'fork': instance.fork, 56 | 'homepage': instance.homepage, 57 | 'language': instance.language, 58 | 'forks_count': instance.forks_count, 59 | 'stargazers_count': instance.stargazers_count, 60 | 'watchers_count': instance.watchers_count, 61 | 'size': instance.size, 62 | 'default_branch': instance.default_branch, 63 | 'open_issues_count': instance.open_issues_count, 64 | 'topics': instance.topics, 65 | 'has_issues': instance.has_issues, 66 | 'has_projects': instance.has_projects, 67 | 'has_wiki': instance.has_wiki, 68 | 'has_pages': instance.has_pages, 69 | 'has_downloads': instance.has_downloads, 70 | 'pushed_at': instance.pushed_at, 71 | 'created_at': instance.created_at, 72 | 'updated_at': instance.updated_at, 73 | 'permissions': instance.permissions, 74 | 'subscribers_count': instance.subscribers_count, 75 | 'license': instance.license 76 | }; 77 | -------------------------------------------------------------------------------- /github_client_app/lib/models/user.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'user.g.dart'; 4 | 5 | @JsonSerializable() 6 | class User { 7 | User(); 8 | 9 | String login; 10 | num id; 11 | String avatar_url; 12 | String url; 13 | String type; 14 | bool site_admin; 15 | String name; 16 | String company; 17 | String blog; 18 | String location; 19 | String email; 20 | bool hireable; 21 | String bio; 22 | num public_repos; 23 | num public_gists; 24 | num followers; 25 | num following; 26 | String created_at; 27 | String updated_at; 28 | num total_private_repos; 29 | num owned_private_repos; 30 | 31 | factory User.fromJson(Map json) => _$UserFromJson(json); 32 | Map toJson() => _$UserToJson(this); 33 | } 34 | -------------------------------------------------------------------------------- /github_client_app/lib/models/user.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'user.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | User _$UserFromJson(Map json) { 10 | return User() 11 | ..login = json['login'] as String 12 | ..id = json['id'] as num 13 | ..avatar_url = json['avatar_url'] as String 14 | ..url = json['url'] as String 15 | ..type = json['type'] as String 16 | ..site_admin = json['site_admin'] as bool 17 | ..name = json['name'] as String 18 | ..company = json['company'] as String 19 | ..blog = json['blog'] as String 20 | ..location = json['location'] as String 21 | ..email = json['email'] as String 22 | ..hireable = json['hireable'] as bool 23 | ..bio = json['bio'] as String 24 | ..public_repos = json['public_repos'] as num 25 | ..public_gists = json['public_gists'] as num 26 | ..followers = json['followers'] as num 27 | ..following = json['following'] as num 28 | ..created_at = json['created_at'] as String 29 | ..updated_at = json['updated_at'] as String 30 | ..total_private_repos = json['total_private_repos'] as num 31 | ..owned_private_repos = json['owned_private_repos'] as num; 32 | } 33 | 34 | Map _$UserToJson(User instance) => { 35 | 'login': instance.login, 36 | 'id': instance.id, 37 | 'avatar_url': instance.avatar_url, 38 | 'url': instance.url, 39 | 'type': instance.type, 40 | 'site_admin': instance.site_admin, 41 | 'name': instance.name, 42 | 'company': instance.company, 43 | 'blog': instance.blog, 44 | 'location': instance.location, 45 | 'email': instance.email, 46 | 'hireable': instance.hireable, 47 | 'bio': instance.bio, 48 | 'public_repos': instance.public_repos, 49 | 'public_gists': instance.public_gists, 50 | 'followers': instance.followers, 51 | 'following': instance.following, 52 | 'created_at': instance.created_at, 53 | 'updated_at': instance.updated_at, 54 | 'total_private_repos': instance.total_private_repos, 55 | 'owned_private_repos': instance.owned_private_repos 56 | }; 57 | -------------------------------------------------------------------------------- /github_client_app/lib/routes/index.dart: -------------------------------------------------------------------------------- 1 | export 'home_page.dart'; 2 | export 'login.dart'; 3 | export 'theme_change.dart'; 4 | export 'language.dart'; -------------------------------------------------------------------------------- /github_client_app/lib/routes/language.dart: -------------------------------------------------------------------------------- 1 | import '../index.dart'; 2 | 3 | class LanguageRoute extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | var color = Theme.of(context).primaryColor; 7 | var localeModel = Provider.of(context); 8 | var gm = GmLocalizations.of(context); 9 | Widget _buildLanguageItem(String lan, value) { 10 | return ListTile( 11 | title: Text( 12 | lan, 13 | // 对APP当前语言进行高亮显示 14 | style: TextStyle(color: localeModel.locale == value ? color : null), 15 | ), 16 | trailing: 17 | localeModel.locale == value ? Icon(Icons.done, color: color) : null, 18 | onTap: () { 19 | // 此行代码会通知MaterialApp重新build 20 | localeModel.locale = value; 21 | }, 22 | ); 23 | } 24 | 25 | return Scaffold( 26 | appBar: AppBar( 27 | title: Text(gm.language), 28 | ), 29 | body: ListView( 30 | children: [ 31 | _buildLanguageItem("中文简体", "zh_CN"), 32 | _buildLanguageItem("English", "en_US"), 33 | _buildLanguageItem(gm.auto, null), 34 | ], 35 | ), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /github_client_app/lib/routes/theme_change.dart: -------------------------------------------------------------------------------- 1 | import '../index.dart'; 2 | 3 | class ThemeChangeRoute extends StatelessWidget{ 4 | @override 5 | Widget build(BuildContext context) { 6 | return Scaffold( 7 | appBar: AppBar( 8 | title: Text(GmLocalizations.of(context).theme), 9 | ), 10 | body: ListView( //显示主题色块 11 | children: Global.themes.map((e) { 12 | return GestureDetector( 13 | child: Padding( 14 | padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 16), 15 | child: Container( 16 | color: e, 17 | height: 40, 18 | ), 19 | ), 20 | onTap: () { 21 | //主题更新后,MaterialApp会重新build 22 | Provider.of(context).theme = e; 23 | }, 24 | ); 25 | }).toList(), 26 | ), 27 | ); 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /github_client_app/lib/states/index.dart: -------------------------------------------------------------------------------- 1 | export 'profile_change_notifier.dart'; 2 | -------------------------------------------------------------------------------- /github_client_app/lib/states/profile_change_notifier.dart: -------------------------------------------------------------------------------- 1 | import '../index.dart'; 2 | 3 | class ProfileChangeNotifier extends ChangeNotifier { 4 | Profile get _profile => Global.profile; 5 | 6 | @override 7 | void notifyListeners() { 8 | Global.saveProfile(); //保存Profile变更 9 | super.notifyListeners(); //通知依赖的Widget更新 10 | } 11 | } 12 | 13 | class UserModel extends ProfileChangeNotifier { 14 | User get user => _profile.user; 15 | 16 | // APP是否登录(如果有用户信息,则证明登录过) 17 | bool get isLogin => user != null; 18 | 19 | //用户信息发生变化,更新用户信息并通知依赖它的子孙Widgets更新 20 | set user(User user) { 21 | if (user?.login != _profile.user?.login) { 22 | _profile.lastLogin = _profile.user?.login; 23 | _profile.user = user; 24 | notifyListeners(); 25 | } 26 | } 27 | } 28 | 29 | class ThemeModel extends ProfileChangeNotifier { 30 | // 获取当前主题,如果为设置主题,则默认使用蓝色主题 31 | ColorSwatch get theme => Global.themes 32 | .firstWhere((e) => e.value == _profile.theme, orElse: () => Colors.blue); 33 | 34 | // 主题改变后,通知其依赖项,新主题会立即生效 35 | set theme(ColorSwatch color) { 36 | if (color != theme) { 37 | _profile.theme = color[500].value; 38 | notifyListeners(); 39 | } 40 | } 41 | } 42 | 43 | class LocaleModel extends ProfileChangeNotifier { 44 | // 获取当前用户的APP语言配置Locale类,如果为null,则语言跟随系统语言 45 | Locale getLocale() { 46 | if (_profile.locale == null) return null; 47 | var t = _profile.locale.split("_"); 48 | return Locale(t[0], t[1]); 49 | } 50 | 51 | // 获取当前Locale的字符串表示 52 | String get locale => _profile.locale; 53 | 54 | // 用户改变APP语言后,通知依赖项更新,新语言会立即生效 55 | set locale(String locale) { 56 | if (locale != _profile.locale) { 57 | _profile.locale = locale; 58 | notifyListeners(); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /github_client_app/lib/widgets/index.dart: -------------------------------------------------------------------------------- 1 | export 'repo_item.dart'; -------------------------------------------------------------------------------- /github_client_app/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: github_client_app 2 | description: A Github client APP built with Flutter 3 | version: 1.0.0+1 4 | 5 | environment: 6 | sdk: ">=2.1.0 <3.0.0" 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | flutter_localizations: 12 | sdk: flutter 13 | intl: ^0.16.0 14 | dio: ^3.0.3 15 | provider: ^3.0.0+1 16 | shared_preferences: ^0.5.1+1 17 | cached_network_image: ^2.0.0 18 | fluttertoast: ^3.0.3 19 | flukit: ^1.0.2 20 | 21 | # The following adds the Cupertino Icons font to your application. 22 | # Use with the CupertinoIcons class for iOS style icons. 23 | cupertino_icons: ^0.1.2 24 | 25 | dev_dependencies: 26 | flutter_test: 27 | sdk: flutter 28 | json_model: ^0.0.2 29 | intl_translation: ^0.17.4 30 | build_runner: ^1.0.0 31 | json_serializable: ^2.0.0 32 | 33 | 34 | 35 | # For information on the generic Dart part of this file, see the 36 | # following page: https://dart.dev/tools/pub/pubspec 37 | 38 | # The following section is specific to Flutter. 39 | flutter: 40 | 41 | # The following line ensures that the Material Icons font is 42 | # included with your application, so that you can use the icons in 43 | # the material Icons class. 44 | uses-material-design: true 45 | 46 | # To add assets to your application, add an assets section, like this: 47 | assets: 48 | - imgs/avatar-default.png 49 | - imgs/logo_light.png 50 | - imgs/logo_dark.png 51 | 52 | # An image asset can refer to one or more resolution-specific "variants", see 53 | # https://flutter.dev/assets-and-images/#resolution-aware. 54 | 55 | # For details regarding adding assets from package dependencies, see 56 | # https://flutter.dev/assets-and-images/#from-packages 57 | 58 | # To add custom fonts to your application, add a fonts section here, 59 | # in this "flutter" section. Each entry in this list should have a 60 | # "family" key with the font family name, and a "fonts" key with a 61 | # list giving the asset and other descriptors for the font. For 62 | # example: 63 | 64 | fonts: 65 | - family: myIcon 66 | fonts: 67 | - asset: fonts/iconfont.ttf 68 | # fonts: 69 | # - family: Schyler 70 | # fonts: 71 | # - asset: fonts/Schyler-Regular.ttf 72 | # - asset: fonts/Schyler-Italic.ttf 73 | # style: italic 74 | # - family: Trajan Pro 75 | # fonts: 76 | # - asset: fonts/TrajanPro.ttf 77 | # - asset: fonts/TrajanPro_Bold.ttf 78 | # weight: 700 79 | # 80 | # For details regarding fonts from package dependencies, 81 | # see https://flutter.dev/custom-fonts/#from-packages 82 | -------------------------------------------------------------------------------- /github_client_app/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:github_client_app/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/home.png -------------------------------------------------------------------------------- /img_des.txt: -------------------------------------------------------------------------------- 1 | 3-1 StatelessWidget 2 | 3-2 StatefulWidget生命周期图 -------------------------------------------------------------------------------- /imgs/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/imgs/avatar.png -------------------------------------------------------------------------------- /intl.sh: -------------------------------------------------------------------------------- 1 | flutter pub pub run intl_translation:extract_to_arb --output-dir=l10n-arb lib/l10n/localization_intl.dart 2 | flutter pub pub run intl_translation:generate_from_arb --output-dir=lib/l10n --no-use-deferred-loading lib/l10n/localization_intl.dart l10n-arb/intl_*.arb 3 | -------------------------------------------------------------------------------- /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/Flutter.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # NOTE: This podspec is NOT to be published. It is only used as a local source! 3 | # 4 | 5 | Pod::Spec.new do |s| 6 | s.name = 'Flutter' 7 | s.version = '1.0.0' 8 | s.summary = 'High-performance, high-fidelity mobile apps.' 9 | s.description = <<-DESC 10 | Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS. 11 | DESC 12 | s.homepage = 'https://flutter.io' 13 | s.license = { :type => 'MIT' } 14 | s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } 15 | s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } 16 | s.ios.deployment_target = '8.0' 17 | s.vendored_frameworks = 'Flutter.framework' 18 | end 19 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/flutter_export_environment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This is a generated file; do not edit or check into version control. 3 | export "FLUTTER_ROOT=/Users/duwen/Documents/flutter" 4 | export "FLUTTER_APPLICATION_PATH=/Users/duwen/Documents/code/flutter_in_action" 5 | export "FLUTTER_TARGET=/Users/duwen/Documents/code/flutter_in_action/lib/main.dart" 6 | export "FLUTTER_BUILD_DIR=build" 7 | export "SYMROOT=${SOURCE_ROOT}/../build/ios" 8 | export "OTHER_LDFLAGS=$(inherited) -framework Flutter" 9 | export "FLUTTER_FRAMEWORK_DIR=/Users/duwen/Documents/flutter/bin/cache/artifacts/engine/ios" 10 | export "FLUTTER_BUILD_NAME=1.0.0" 11 | export "FLUTTER_BUILD_NUMBER=1" 12 | export "TRACK_WIDGET_CREATION=true" 13 | -------------------------------------------------------------------------------- /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 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 37 | # referring to absolute paths on developers' machines. 38 | system('rm -rf .symlinks') 39 | system('mkdir -p .symlinks/plugins') 40 | 41 | # Flutter Pods 42 | generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') 43 | if generated_xcode_build_settings.empty? 44 | puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first." 45 | end 46 | generated_xcode_build_settings.map { |p| 47 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR' 48 | symlink = File.join('.symlinks', 'flutter') 49 | File.symlink(File.dirname(p[:path]), symlink) 50 | pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) 51 | end 52 | } 53 | 54 | # Plugin Pods 55 | plugin_pods = parse_KV_file('../.flutter-plugins') 56 | plugin_pods.map { |p| 57 | symlink = File.join('.symlinks', 'plugins', p[:name]) 58 | File.symlink(p[:path], symlink) 59 | pod p[:name], :path => File.join(symlink, 'ios') 60 | } 61 | end 62 | 63 | # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. 64 | install! 'cocoapods', :disable_input_output_paths => true 65 | 66 | post_install do |installer| 67 | installer.pods_project.targets.each do |target| 68 | target.build_configurations.each do |config| 69 | config.build_settings['ENABLE_BITCODE'] = 'NO' 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/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/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/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/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/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/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/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/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/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/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/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/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/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/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/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/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/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/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/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/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/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/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/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/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/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/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/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/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/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/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wendux/flutter_in_action_source_code/e5e54b3082bc344fb9c236a9e24796ac568ac859/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 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | flutter_in_action 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /l10n-arb/intl_messages.arb: -------------------------------------------------------------------------------- 1 | { 2 | "@@last_modified": "2018-12-10T18:00:52.404540", 3 | "title": "Flutter APP", 4 | "@title": { 5 | "description": "Title for the Demo application", 6 | "type": "text", 7 | "placeholders": {} 8 | }, 9 | "remainingEmailsMessage": "{howMany,plural, =0{There are no emails left}=1{There is {howMany} email left}other{There are {howMany} emails left}}", 10 | "@remainingEmailsMessage": { 11 | "description": "How many emails remain after archiving.", 12 | "type": "text", 13 | "placeholders": { 14 | "howMany": { 15 | "example": 42 16 | } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /l10n-arb/intl_zh_CN.arb: -------------------------------------------------------------------------------- 1 | { 2 | "@@last_modified": "2018-12-10T15:46:20.897228", 3 | "@@locale":"zh_CN", 4 | "title": "Flutter应用", 5 | "@title": { 6 | "description": "Title for the Demo application", 7 | "type": "text", 8 | "placeholders": {} 9 | }, 10 | "remainingEmailsMessage": "{howMany,plural, =0{没有未读邮件}=1{有{howMany}封未读邮件}other{有{howMany}封未读邮件}}", 11 | "@remainingEmailsMessage": { 12 | "description": "How many emails remain after archiving.", 13 | "type": "text", 14 | "placeholders": { 15 | "howMany": { 16 | "example": 42 17 | } 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /lib/common.dart: -------------------------------------------------------------------------------- 1 | import 'package:camera/camera.dart'; 2 | 3 | List cameras; -------------------------------------------------------------------------------- /lib/event_bus.dart: -------------------------------------------------------------------------------- 1 | //订阅者回调签名 2 | typedef void EventCallback(arg); 3 | 4 | class EventBus { 5 | //私有构造函数 6 | EventBus._internal(); 7 | 8 | //保存单例 9 | static EventBus _singleton = new EventBus._internal(); 10 | 11 | //工厂构造函数 12 | factory EventBus()=> _singleton; 13 | 14 | //保存事件订阅者队列,key:事件名(id),value: 对应事件的订阅者队列 15 | var _emap = new Map(); 16 | 17 | //添加订阅者 18 | void on(eventName, EventCallback f) { 19 | if (eventName == null || f == null) return; 20 | _emap[eventName] ??= List(); 21 | _emap[eventName].add(f); 22 | } 23 | 24 | void ons(List list, EventCallback f) { 25 | list.forEach((e)=>on(e,f)); 26 | } 27 | 28 | //移除订阅者 29 | void off(eventName, [EventCallback f]) { 30 | var list = _emap[eventName]; 31 | if (eventName == null || list == null) return; 32 | if (f == null) { 33 | _emap[eventName] = null; 34 | } else { 35 | list.remove(f); 36 | } 37 | } 38 | 39 | //触发事件,事件触发后该事件所有订阅者会被调用 40 | void emit(eventName, arg) { 41 | var list = _emap[eventName]; 42 | if (list == null) return; 43 | int len = list.length - 1; 44 | //反向遍历,防止在订阅者在回调中移除自身带来的下标错位 45 | for (var i = len; i > -1; --i) { 46 | list[i](arg); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/http.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | var dio=Dio(); 3 | -------------------------------------------------------------------------------- /lib/i10n/_localization.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | //Locale资源类 5 | class DemoLocalizations { 6 | DemoLocalizations(this.isZh); 7 | 8 | bool isZh = false; 9 | 10 | static DemoLocalizations of(BuildContext context) { 11 | return Localizations.of(context, DemoLocalizations); 12 | } 13 | 14 | String get title { 15 | return isZh ? "Flutter应用" : "Flutter APP"; 16 | } 17 | } 18 | 19 | //Locale代理类 20 | class DemoLocalizationsDelegate extends LocalizationsDelegate { 21 | const DemoLocalizationsDelegate(); 22 | 23 | //是否支持某个Local 24 | @override 25 | bool isSupported(Locale locale) => ['en', 'zh'].contains(locale.languageCode); 26 | 27 | // Flutter会调用此类加载相应的Locale资源类 28 | @override 29 | Future load(Locale locale) { 30 | print("xxxx$locale"); 31 | return SynchronousFuture( DemoLocalizations(locale.languageCode == "zh")); 32 | } 33 | 34 | // 当Localizations Widget重新build时,是否调用load重新加载Locale资源. 35 | @override 36 | bool shouldReload(DemoLocalizationsDelegate old) => false; 37 | } 38 | -------------------------------------------------------------------------------- /lib/i10n/localization_intl.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:intl/intl.dart'; 3 | import 'messages_all.dart'; //1 4 | 5 | class DemoLocalizations { 6 | static Future load(Locale locale) { 7 | final String name = 8 | locale.countryCode.isEmpty ? locale.languageCode : locale.toString(); 9 | final String localeName = Intl.canonicalizedLocale(name); 10 | //2 11 | return initializeMessages(localeName).then((b) { 12 | Intl.defaultLocale = localeName; 13 | return new DemoLocalizations(); 14 | }); 15 | } 16 | 17 | static DemoLocalizations of(BuildContext context) { 18 | return Localizations.of(context, DemoLocalizations); 19 | } 20 | 21 | String get title { 22 | return Intl.message( 23 | 'Flutter APP', 24 | name: 'title', 25 | desc: 'Title for the Demo application', 26 | ); 27 | } 28 | 29 | remainingEmailsMessage(int howMany) => Intl.plural(howMany, 30 | zero: 'There are no emails left', 31 | one: 'There is $howMany email left', 32 | other: 'There are $howMany emails left', 33 | name: "remainingEmailsMessage", 34 | args: [howMany], 35 | desc: "How many emails remain after archiving.", 36 | examples: const {'howMany': 42}); 37 | } 38 | 39 | //Locale代理类 40 | class DemoLocalizationsDelegate 41 | extends LocalizationsDelegate { 42 | const DemoLocalizationsDelegate(); 43 | 44 | //是否支持某个Local 45 | @override 46 | bool isSupported(Locale locale) => ['en', 'zh'].contains(locale.languageCode); 47 | 48 | // Flutter会调用此类加载相应的Locale资源类 49 | @override 50 | Future load(Locale locale) { 51 | //3 52 | return DemoLocalizations.load(locale); 53 | } 54 | 55 | // 当Localizations Widget重新build时,是否调用load重新加载Locale资源. 56 | @override 57 | bool shouldReload(DemoLocalizationsDelegate old) => false; 58 | } 59 | -------------------------------------------------------------------------------- /lib/i10n/messages_all.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart 2 | // This is a library that looks up messages for specific locales by 3 | // delegating to the appropriate library. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:intl/intl.dart'; 8 | import 'package:intl/message_lookup_by_library.dart'; 9 | // ignore: implementation_imports 10 | import 'package:intl/src/intl_helpers.dart'; 11 | 12 | import 'messages_messages.dart' as messages_messages; 13 | import 'messages_zh_CN.dart' as messages_zh_CN; 14 | 15 | typedef Future LibraryLoader(); 16 | Map _deferredLibraries = { 17 | // ignore: unnecessary_new 18 | 'messages': () => new Future.value(null), 19 | // ignore: unnecessary_new 20 | 'zh_CN': () => new Future.value(null), 21 | }; 22 | 23 | MessageLookupByLibrary _findExact(localeName) { 24 | switch (localeName) { 25 | case 'messages': 26 | return messages_messages.messages; 27 | case 'zh_CN': 28 | return messages_zh_CN.messages; 29 | default: 30 | return null; 31 | } 32 | } 33 | 34 | /// User programs should call this before using [localeName] for messages. 35 | Future initializeMessages(String localeName) async { 36 | var availableLocale = Intl.verifiedLocale( 37 | localeName, 38 | (locale) => _deferredLibraries[locale] != null, 39 | onFailure: (_) => null); 40 | if (availableLocale == null) { 41 | // ignore: unnecessary_new 42 | return new Future.value(false); 43 | } 44 | var lib = _deferredLibraries[availableLocale]; 45 | // ignore: unnecessary_new 46 | await (lib == null ? new Future.value(false) : lib()); 47 | // ignore: unnecessary_new 48 | initializeInternalMessageLookup(() => new CompositeMessageLookup()); 49 | messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor); 50 | // ignore: unnecessary_new 51 | return new Future.value(true); 52 | } 53 | 54 | bool _messagesExistFor(String locale) { 55 | try { 56 | return _findExact(locale) != null; 57 | } catch (e) { 58 | return false; 59 | } 60 | } 61 | 62 | MessageLookupByLibrary _findGeneratedMessagesFor(locale) { 63 | var actualLocale = Intl.verifiedLocale(locale, _messagesExistFor, 64 | onFailure: (_) => null); 65 | if (actualLocale == null) return null; 66 | return _findExact(actualLocale); 67 | } 68 | -------------------------------------------------------------------------------- /lib/i10n/messages_messages.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart 2 | // This is a library that provides messages for a messages locale. All the 3 | // messages from the main program should be duplicated here with the same 4 | // function name. 5 | 6 | import 'package:intl/intl.dart'; 7 | import 'package:intl/message_lookup_by_library.dart'; 8 | 9 | // ignore: unnecessary_new 10 | final messages = new MessageLookup(); 11 | 12 | // ignore: unused_element 13 | final _keepAnalysisHappy = Intl.defaultLocale; 14 | 15 | // ignore: non_constant_identifier_names 16 | typedef MessageIfAbsent(String message_str, List args); 17 | 18 | class MessageLookup extends MessageLookupByLibrary { 19 | get localeName => 'messages'; 20 | 21 | static m0(howMany) => "${Intl.plural(howMany, zero: 'There are no emails left', one: 'There is ${howMany} email left', other: 'There are ${howMany} emails left')}"; 22 | 23 | final messages = _notInlinedMessages(_notInlinedMessages); 24 | static _notInlinedMessages(_) => { 25 | "remainingEmailsMessage" : m0, 26 | "title" : MessageLookupByLibrary.simpleMessage("Flutter APP") 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /lib/i10n/messages_zh_CN.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart 2 | // This is a library that provides messages for a zh_CN locale. All the 3 | // messages from the main program should be duplicated here with the same 4 | // function name. 5 | 6 | import 'package:intl/intl.dart'; 7 | import 'package:intl/message_lookup_by_library.dart'; 8 | 9 | // ignore: unnecessary_new 10 | final messages = new MessageLookup(); 11 | 12 | // ignore: unused_element 13 | final _keepAnalysisHappy = Intl.defaultLocale; 14 | 15 | // ignore: non_constant_identifier_names 16 | typedef MessageIfAbsent(String message_str, List args); 17 | 18 | class MessageLookup extends MessageLookupByLibrary { 19 | get localeName => 'zh_CN'; 20 | 21 | static m0(howMany) => "${Intl.plural(howMany, zero: '没有未读邮件', one: '有${howMany}封未读邮件', other: '有${howMany}封未读邮件')}"; 22 | 23 | final messages = _notInlinedMessages(_notInlinedMessages); 24 | static _notInlinedMessages(_) => { 25 | "remainingEmailsMessage" : m0, 26 | "title" : MessageLookupByLibrary.simpleMessage("Flutter应用") 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /lib/provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | class ChangeNotifierProvider extends StatefulWidget { 4 | ChangeNotifierProvider({ 5 | Key key, 6 | this.data, 7 | this.child, 8 | }); 9 | 10 | final Widget child; 11 | final T data; 12 | 13 | //定义一个便捷方法,方便子树中的widget获取共享数据 14 | static T of(BuildContext context, {bool listen = true}) { 15 | final provider = listen 16 | ? context.dependOnInheritedWidgetOfExactType>() 17 | : context.getElementForInheritedWidgetOfExactType>()?.widget 18 | as InheritedProvider; 19 | return provider.data; 20 | } 21 | 22 | @override 23 | _ChangeNotifierProviderState createState() => _ChangeNotifierProviderState(); 24 | } 25 | 26 | class _ChangeNotifierProviderState extends State> { 27 | void update() { 28 | //如果数据发生变化(model类调用了notifyListeners),重新构建InheritedProvider 29 | setState(() => {}); 30 | } 31 | 32 | @override 33 | void didUpdateWidget(ChangeNotifierProvider oldWidget) { 34 | //当Provider更新时,如果新旧数据不"==",则解绑旧数据监听,同时添加新数据监听 35 | if (widget.data != oldWidget.data) { 36 | oldWidget.data.removeListener(update); 37 | widget.data.addListener(update); 38 | } 39 | super.didUpdateWidget(oldWidget); 40 | } 41 | 42 | @override 43 | void initState() { 44 | // 给model添加监听器 45 | widget.data.addListener(update); 46 | super.initState(); 47 | } 48 | 49 | @override 50 | void dispose() { 51 | // 移除model的监听器 52 | widget.data.removeListener(update); 53 | super.dispose(); 54 | } 55 | 56 | @override 57 | Widget build(BuildContext context) { 58 | return InheritedProvider( 59 | data: widget.data, 60 | child: widget.child, 61 | ); 62 | } 63 | } 64 | 65 | // 一个通用的InheritedWidget,保存任需要跨组件共享的状态 66 | class InheritedProvider extends InheritedWidget { 67 | InheritedProvider({@required this.data, Widget child}) : super(child: child); 68 | 69 | final T data; 70 | 71 | @override 72 | bool updateShouldNotify(InheritedProvider old) { 73 | //在此简单返回true,则每次更新都会调用依赖其的子孙节点的`didChangeDependencies`。 74 | return true; 75 | } 76 | } 77 | 78 | // 这是一个便捷类,会获得当前context和指定数据类型的Provider 79 | class Consumer extends StatelessWidget { 80 | Consumer({ 81 | Key key, 82 | @required this.builder, 83 | this.child, 84 | }) : assert(builder != null), 85 | super(key: key); 86 | 87 | final Widget child; 88 | 89 | final Widget Function(BuildContext context, T value) builder; 90 | 91 | @override 92 | Widget build(BuildContext context) { 93 | return builder( 94 | context, 95 | ChangeNotifierProvider.of(context), 96 | ); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /lib/routes/align.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AlignRoute extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return Column( 7 | children: [ 8 | Container( 9 | height: 120.0, 10 | width: 120.0, 11 | color: Colors.blue[50], 12 | child: Align( 13 | alignment: Alignment.topRight, 14 | child: FlutterLogo( 15 | size: 60, 16 | ), 17 | ), 18 | ), 19 | Container( 20 | // height: 120.0, 21 | // width: 120.0, 22 | color: Colors.blue[50], 23 | child: Align( 24 | widthFactor: 2, 25 | heightFactor: 2, 26 | alignment: Alignment(2, 0.0), 27 | child: FlutterLogo( 28 | size: 60, 29 | ), 30 | ), 31 | ), 32 | Container( 33 | height: 120.0, 34 | width: 120.0, 35 | color: Colors.blue[50], 36 | child: Align( 37 | alignment: FractionalOffset(0.2, 0.6), 38 | child: FlutterLogo( 39 | size: 60, 40 | ), 41 | ), 42 | ), 43 | DecoratedBox( 44 | decoration: BoxDecoration(color: Colors.red), 45 | child: Center( 46 | child: Text("xxx"), 47 | ), 48 | ), 49 | DecoratedBox( 50 | decoration: BoxDecoration(color: Colors.red), 51 | child: Center( 52 | widthFactor: 1, 53 | heightFactor: 1, 54 | child: Text("xxx"), 55 | ), 56 | ) 57 | ] 58 | .map((e) => Padding(padding: EdgeInsets.only(top: 16), child: e)) 59 | .toList(), 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/routes/animated_switcher_counter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AnimatedSwitcherCounterRoute extends StatefulWidget { 4 | const AnimatedSwitcherCounterRoute({Key key}) : super(key: key); 5 | 6 | @override 7 | _AnimatedSwitcherCounterRouteState createState() => 8 | _AnimatedSwitcherCounterRouteState(); 9 | } 10 | 11 | class _AnimatedSwitcherCounterRouteState 12 | extends State { 13 | int _count = 0; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Center( 18 | child: Column( 19 | mainAxisAlignment: MainAxisAlignment.center, 20 | children: [ 21 | AnimatedSwitcher( 22 | duration: const Duration(milliseconds: 400), 23 | transitionBuilder: (Widget child, Animation animation) { 24 | return ScaleTransition(child: child, scale: animation); 25 | }, 26 | child: Text( 27 | '$_count', 28 | //显示指定key,不同的key会被认为是不同的Text,这样才能执行动画 29 | key: ValueKey(_count), 30 | style: Theme 31 | .of(context) 32 | .textTheme 33 | .headline4, 34 | ), 35 | ), 36 | RaisedButton( 37 | child: const Text( 38 | '+1', 39 | ), 40 | onPressed: () { 41 | setState(() { 42 | _count += 1; 43 | }); 44 | }, 45 | ), 46 | ], 47 | ), 48 | ); 49 | } 50 | } 51 | 52 | 53 | -------------------------------------------------------------------------------- /lib/routes/button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ButtonRoute extends StatefulWidget { 4 | @override 5 | _ButtonRouteState createState() => _ButtonRouteState(); 6 | } 7 | 8 | class _ButtonRouteState extends State { 9 | @override 10 | Widget build(BuildContext context) { 11 | return Column( 12 | children: [ 13 | RaisedButton( 14 | child: Text("normal"), 15 | onPressed: () => {}, 16 | ), 17 | OutlineButton( 18 | child: Text("normal"), 19 | onPressed: () => {}, 20 | ), 21 | IconButton( 22 | icon: Icon(Icons.thumb_up), 23 | onPressed: () => {}, 24 | ), 25 | FlatButton( 26 | color: Colors.blue, 27 | highlightColor: Colors.blue[700], 28 | colorBrightness: Brightness.dark, 29 | splashColor: Colors.grey, 30 | child: Text("Submit"), 31 | shape: 32 | RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)), 33 | onPressed: () => {}, 34 | ), 35 | RaisedButton.icon( 36 | icon: Icon(Icons.send), 37 | label: Text("发送"), 38 | onPressed: _onPressed, 39 | ), 40 | OutlineButton.icon( 41 | icon: Icon(Icons.add), 42 | label: Text("添加"), 43 | onPressed: _onPressed, 44 | ), 45 | FlatButton.icon( 46 | icon: Icon(Icons.info), 47 | label: Text("详情"), 48 | onPressed: _onPressed, 49 | ), 50 | ], 51 | ); 52 | } 53 | 54 | void _onPressed() { 55 | print("button pressed"); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/routes/clip.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ClipRoute extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | // 头像 7 | Widget avatar = Image.asset("imgs/avatar.png", width: 60.0); 8 | return Center( 9 | child: Column( 10 | children: [ 11 | avatar, //不剪裁 12 | ClipOval(child: avatar), //剪裁为圆形 13 | ClipRRect( 14 | //剪裁为圆角矩形 15 | borderRadius: BorderRadius.circular(5.0), 16 | child: avatar, 17 | ), 18 | Row( 19 | mainAxisAlignment: MainAxisAlignment.center, 20 | children: [ 21 | Align( 22 | alignment: Alignment.topLeft, 23 | widthFactor: .5, //宽度设为原来宽度一半,另一半会溢出 24 | child: avatar, 25 | ), 26 | Text( 27 | "你好世界", 28 | style: TextStyle(color: Colors.green), 29 | ) 30 | ], 31 | ), 32 | Row( 33 | mainAxisAlignment: MainAxisAlignment.center, 34 | children: [ 35 | ClipRect( 36 | //将溢出部分剪裁 37 | child: Align( 38 | alignment: Alignment.topLeft, 39 | widthFactor: .5, //宽度设为原来宽度一半 40 | child: avatar, 41 | ), 42 | ), 43 | Text("你好世界", style: TextStyle(color: Colors.green)) 44 | ], 45 | ), 46 | DecoratedBox( 47 | decoration: BoxDecoration(color: Colors.red), 48 | child: ClipRect( 49 | clipper: MyClipper(), //使用自定义的clipper 50 | child: avatar, 51 | ), 52 | ) 53 | ] 54 | .map((e) => Padding(padding: EdgeInsets.only(top: 16), child: e)) 55 | .toList(), 56 | ), 57 | ); 58 | } 59 | } 60 | 61 | class MyClipper extends CustomClipper { 62 | @override 63 | Rect getClip(Size size) => Rect.fromLTWH(10.0, 15.0, 40.0, 30.0); 64 | 65 | @override 66 | bool shouldReclip(CustomClipper oldClipper) => false; 67 | } 68 | -------------------------------------------------------------------------------- /lib/routes/color.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ColorRoute extends StatefulWidget { 4 | @override 5 | _ColorRouteState createState() => _ColorRouteState(); 6 | } 7 | 8 | class _ColorRouteState extends State { 9 | static Color _white = Color(0xFFFFFFFF); 10 | static MaterialColor white = MaterialColor( 11 | 0xFFFFFFFF, 12 | { 13 | 50: _white, 14 | 100: _white, 15 | 200: _white, 16 | 300: _white, 17 | 350: _white, 18 | 400: _white, 19 | 500: _white, 20 | 600: _white, 21 | 700: _white, 22 | 800: _white, 23 | 850: _white, 24 | 900: _white, 25 | }, 26 | ); 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return Theme( 31 | data: ThemeData( 32 | primarySwatch: Colors.blue, //使用白色主题 33 | ), 34 | child: Scaffold( 35 | appBar: AppBar( 36 | title: Text("颜色"), 37 | ), 38 | body: Column( 39 | children: [ 40 | Padding( 41 | padding: const EdgeInsets.symmetric(vertical: 10), 42 | child: NavBar(color: Colors.blue, title: "标题"), 43 | ), 44 | NavBar(color: Colors.white, title: "标题"), 45 | ] 46 | ), 47 | ), 48 | ); 49 | } 50 | } 51 | 52 | class NavBar extends StatelessWidget { 53 | final String title; 54 | final Color color; 55 | 56 | NavBar({ 57 | Key key, 58 | this.color, 59 | this.title, 60 | }); 61 | 62 | @override 63 | Widget build(BuildContext context) { 64 | return Container( 65 | constraints: BoxConstraints( 66 | minHeight: 52, 67 | minWidth: double.infinity, 68 | ), 69 | decoration: BoxDecoration( 70 | color: color, 71 | boxShadow: [ 72 | //阴影 73 | BoxShadow( 74 | color: Colors.black26, 75 | offset: Offset(0, 3), 76 | blurRadius: 3, 77 | ), 78 | ], 79 | ), 80 | child: Text( 81 | title, 82 | style: TextStyle( 83 | fontWeight: FontWeight.bold, 84 | color: color.computeLuminance() < 0.5 ? Colors.white : Colors.black, 85 | ), 86 | ), 87 | alignment: Alignment.center, 88 | ); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/routes/context.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ContextRoute extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return Scaffold( 7 | appBar: AppBar( 8 | title: Text("Context测试"), 9 | ), 10 | body: Container( 11 | child: Builder(builder: (context) { 12 | // 在Widget树中向上查找最近的父级`Scaffold` widget 13 | Scaffold scaffold = context.ancestorWidgetOfExactType(Scaffold); 14 | // 直接返回 AppBar的title, 此处实际上是Text("Context测试") 15 | return (scaffold.appBar as AppBar).title; 16 | }), 17 | ), 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/routes/custom_paint.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'dart:math'; 3 | 4 | class CustomPaintRoute extends StatelessWidget { 5 | @override 6 | Widget build(BuildContext context) { 7 | return Center( 8 | child: CustomPaint( 9 | size: Size(300, 300), //指定画布大小 10 | painter: MyPainter(), 11 | ), 12 | ); 13 | } 14 | } 15 | 16 | class MyPainter extends CustomPainter { 17 | @override 18 | void paint(Canvas canvas, Size size) { 19 | double eWidth = size.width / 15; 20 | double eHeight = size.height / 15; 21 | //棋盘背景 22 | var paint = Paint() 23 | ..isAntiAlias = true 24 | ..style = PaintingStyle.fill //填充 25 | ..color = Color(0x77cdb175); 26 | canvas.drawRect(Offset.zero & size, paint); 27 | 28 | //画棋盘网格 29 | paint 30 | ..style = PaintingStyle.stroke //线 31 | ..color = Colors.black87 32 | ..strokeWidth = 1.0; 33 | 34 | for (int i = 0; i <= 15; ++i) { 35 | double dy = eHeight * i; 36 | canvas.drawLine(Offset(0, dy), Offset(size.width, dy), paint); 37 | } 38 | 39 | for (int i = 0; i <= 15; ++i) { 40 | double dx = eWidth * i; 41 | canvas.drawLine(Offset(dx, 0), Offset(dx, size.height), paint); 42 | } 43 | 44 | //画一个黑子 45 | paint 46 | ..style = PaintingStyle.fill 47 | ..color = Colors.black; 48 | canvas.drawCircle( 49 | Offset(size.width / 2 - eWidth / 2, size.height / 2 - eHeight / 2), 50 | min(eWidth / 2, eHeight / 2) - 2, 51 | paint, 52 | ); 53 | //画一个白子 54 | paint.color = Colors.white; 55 | canvas.drawCircle( 56 | Offset(size.width / 2 + eWidth / 2, size.height / 2 - eHeight / 2), 57 | min(eWidth / 2, eHeight / 2) - 2, 58 | paint, 59 | ); 60 | } 61 | 62 | // 在实际场景中正确使用此方法可以避免重绘开销,我们简单的返回true 63 | @override 64 | bool shouldRepaint(CustomPainter oldDelegate) => true; 65 | } 66 | -------------------------------------------------------------------------------- /lib/routes/custom_ui_framework.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | 4 | 5 | 6 | 7 | class CustomHome extends Widget { 8 | @override 9 | Element createElement() { 10 | return HomeView(this); 11 | } 12 | } 13 | 14 | class HomeView extends ComponentElement{ 15 | HomeView(Widget widget) : super(widget); 16 | String text = "123456789"; 17 | 18 | @override 19 | Widget build() { 20 | Color primary=Theme.of(this).primaryColor; 21 | return GestureDetector( 22 | child: Center( 23 | child: FlatButton( 24 | child: Text(text, style: TextStyle(color: primary),), 25 | onPressed: () { 26 | var t = text.split("")..shuffle(); 27 | text = t.join(); 28 | markNeedsBuild(); 29 | }, 30 | ), 31 | ), 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/routes/decoratedbox.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class DecoratedBoxRoute extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return Column( 7 | children: [ 8 | DecoratedBox( 9 | decoration: BoxDecoration( 10 | gradient: 11 | LinearGradient(colors: [Colors.red, Colors.orange[700]]), 12 | //背景渐变 13 | borderRadius: BorderRadius.circular(3.0), 14 | //3像素圆角 15 | boxShadow: [ 16 | //阴影 17 | BoxShadow( 18 | color: Colors.black54, 19 | offset: Offset(2.0, 2.0), 20 | blurRadius: 4.0) 21 | ]), 22 | child: Padding( 23 | padding: EdgeInsets.symmetric( 24 | horizontal: 80.0, 25 | vertical: 18.0, 26 | ), 27 | child: Text( 28 | "Login", 29 | style: TextStyle(color: Colors.white), 30 | ), 31 | ), 32 | ), 33 | SizedBox( 34 | width: 100, 35 | height: 100, 36 | child: DecoratedBox( 37 | decoration: BoxDecoration( 38 | borderRadius: BorderRadius.circular(80), 39 | image: DecorationImage( 40 | image: AssetImage("imgs/avatar.png"), 41 | //alignment: Alignment.topLeft 42 | ), 43 | ), 44 | ), 45 | ) 46 | ], 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/routes/future_and_stream_builder.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class FutureAndStreamBuilderRoute extends StatefulWidget { 5 | @override 6 | _FutureAndStreamBuilderRouteState createState() => 7 | _FutureAndStreamBuilderRouteState(); 8 | } 9 | 10 | class _FutureAndStreamBuilderRouteState 11 | extends State { 12 | var t = Future.delayed(Duration(seconds: 3), () => "我是从互联网上获取的数据"); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | 17 | 18 | // return Center( 19 | // child: FutureBuilder( 20 | // future: t, 21 | // builder: (BuildContext context, AsyncSnapshot snapshot) { 22 | // print(snapshot.connectionState); 23 | // if (snapshot.connectionState == ConnectionState.done) { 24 | // if (snapshot.hasError) { 25 | // return Text("Error: ${snapshot.error}"); 26 | // } else { 27 | // return Text("Contents: ${snapshot.data}"); 28 | // } 29 | // } else { 30 | // // 31 | // return CircularProgressIndicator(); 32 | // } 33 | // }, 34 | // ), 35 | // ); 36 | return Center( 37 | child: StreamBuilder( 38 | stream: counter(), // 39 | //initialData: ,// a Stream or null 40 | builder: (BuildContext context, AsyncSnapshot snapshot) { 41 | if (snapshot.hasError) return Text('Error: ${snapshot.error}'); 42 | switch (snapshot.connectionState) { 43 | case ConnectionState.none: 44 | return Text('没有Stream'); 45 | case ConnectionState.waiting: 46 | return Text('等待数据...'); 47 | case ConnectionState.active: 48 | return Text('active: ${snapshot.data}'); 49 | case ConnectionState.done: 50 | return Text('Stream已关闭'); 51 | } 52 | return null; // unreachable 53 | }, 54 | ), 55 | ); 56 | } 57 | 58 | Future mockNetworkData() async { 59 | return Future.delayed(Duration(seconds: 3), () => "我是从互联网上获取的数据"); 60 | } 61 | 62 | Stream counter() { 63 | return Stream.periodic(Duration(seconds: 1), (i) { 64 | return i; 65 | }); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/routes/gradient_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../widgets/index.dart'; 3 | 4 | class GradientButtonRoute extends StatefulWidget { 5 | @override 6 | _GradientButtonRouteState createState() => _GradientButtonRouteState(); 7 | } 8 | 9 | class _GradientButtonRouteState extends State { 10 | @override 11 | Widget build(BuildContext context) { 12 | return Container( 13 | child: Column( 14 | children: [ 15 | GradientButton( 16 | colors: [Colors.orange, Colors.red], 17 | height: 50.0, 18 | child: Text("Submit"), 19 | onPressed: onTap, 20 | ), 21 | GradientButton( 22 | height: 50.0, 23 | colors: [Colors.lightGreen, Colors.green[700]], 24 | child: Text("Submit"), 25 | onPressed: onTap, 26 | ), 27 | GradientButton( 28 | height: 50.0, 29 | colors: [Colors.lightBlue[300], Colors.blueAccent], 30 | child: Text("Submit"), 31 | onPressed: onTap, 32 | ), 33 | ], 34 | ), 35 | ); 36 | } 37 | onTap() { 38 | print("button click"); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/routes/grow_transition.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class GrowTransition extends StatelessWidget { 4 | GrowTransition({this.child, this.animation}); 5 | 6 | final Widget child; 7 | final Animation animation; 8 | 9 | Widget build(BuildContext context) { 10 | return Center( 11 | child: AnimatedBuilder( 12 | animation: animation, 13 | builder: (BuildContext context, Widget child) { 14 | return Container( 15 | height: animation.value, 16 | width: animation.value, 17 | child: child 18 | ); 19 | }, 20 | child: child 21 | ), 22 | ); 23 | } 24 | } 25 | 26 | class GrowTransitionRoute extends StatefulWidget { 27 | @override 28 | _GrowTransitionRouteState createState() => _GrowTransitionRouteState(); 29 | } 30 | 31 | //需要继承TickerProvider,如果有多个AnimationController,则应该使用TickerProviderStateMixin。 32 | class _GrowTransitionRouteState extends State with SingleTickerProviderStateMixin{ 33 | 34 | Animation animation; 35 | AnimationController controller; 36 | 37 | initState() { 38 | super.initState(); 39 | controller = AnimationController( 40 | duration: const Duration(seconds: 2), vsync: this); 41 | animation = Tween(begin: 0.0, end: 300.0).animate(controller); 42 | controller.forward(); 43 | } 44 | 45 | @override 46 | Widget build(BuildContext context) { 47 | return GrowTransition( 48 | child: Image.asset("imgs/avatar.png"), 49 | animation: animation, 50 | ); 51 | } 52 | 53 | dispose() { 54 | //路由销毁时需要释放动画资源 55 | controller.dispose(); 56 | super.dispose(); 57 | } 58 | } -------------------------------------------------------------------------------- /lib/routes/hero_animation.dart: -------------------------------------------------------------------------------- 1 | // 路由A 2 | import 'package:flutter/material.dart'; 3 | 4 | class HeroAnimationRoute extends StatelessWidget { 5 | @override 6 | Widget build(BuildContext context) { 7 | return Container( 8 | alignment: Alignment.topCenter, 9 | child: Column( 10 | children: [ 11 | InkWell( 12 | child: Hero( 13 | tag: "avatar", //唯一标记,前后两个路由页Hero的tag必须相同 14 | child: ClipOval( 15 | child: Image.asset("imgs/avatar.png", 16 | width: 50.0, 17 | ), 18 | ), 19 | ), 20 | onTap: () { 21 | //打开B路由 22 | Navigator.push(context, PageRouteBuilder( 23 | pageBuilder: (BuildContext context, Animation animation, 24 | Animation secondaryAnimation) { 25 | return new FadeTransition( 26 | opacity: animation, 27 | child: Scaffold( 28 | appBar: AppBar( 29 | title: Text("原图"), 30 | ), 31 | body: HeroAnimationRouteB(), 32 | ), 33 | ); 34 | }) 35 | ); 36 | }, 37 | ), 38 | Padding( 39 | padding: const EdgeInsets.only(top:8.0), 40 | child: Text("点击头像"), 41 | ) 42 | ], 43 | ), 44 | ); 45 | } 46 | } 47 | 48 | class HeroAnimationRouteB extends StatelessWidget { 49 | @override 50 | Widget build(BuildContext context) { 51 | return Center( 52 | child: Hero( 53 | tag: "avatar", //唯一标记,前后两个路由页Hero的tag必须相同 54 | child: Image.asset("imgs/avatar.png"), 55 | ), 56 | ); 57 | } 58 | } -------------------------------------------------------------------------------- /lib/routes/image_icon.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ImageAndIconRoute extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | var img=AssetImage("imgs/avatar.png"); 7 | return SingleChildScrollView( 8 | child: Column( 9 | children: [ 10 | Image( 11 | image: img, 12 | height: 50.0, 13 | width: 100.0, 14 | fit: BoxFit.fill, 15 | ), 16 | Image( 17 | image: img, 18 | height: 50, 19 | width: 50.0, 20 | fit: BoxFit.contain, 21 | ), 22 | Image( 23 | image: img, 24 | width: 100.0, 25 | height: 50.0, 26 | fit: BoxFit.cover, 27 | ), 28 | Image( 29 | image: img, 30 | width: 100.0, 31 | height: 50.0, 32 | fit: BoxFit.fitWidth, 33 | ), 34 | Image( 35 | image: img, 36 | width: 100.0, 37 | height: 50.0, 38 | fit: BoxFit.fitHeight, 39 | ), 40 | Image( 41 | image: img, 42 | width: 100.0, 43 | height: 50.0, 44 | fit: BoxFit.scaleDown, 45 | ), 46 | Image( 47 | image: img, 48 | height: 50.0, 49 | width: 100.0, 50 | fit: BoxFit.none, 51 | ), 52 | Image( 53 | image: img, 54 | width: 100.0, 55 | color: Colors.blue, 56 | colorBlendMode: BlendMode.difference, 57 | fit: BoxFit.fill, 58 | ), 59 | Image( 60 | image: img, 61 | width: 100.0, 62 | height: 200.0, 63 | repeat: ImageRepeat.repeatY , 64 | ) 65 | ].map((e){ 66 | return Row( 67 | children: [ 68 | Padding( 69 | padding: EdgeInsets.all(16.0), 70 | child: SizedBox( 71 | width: 100, 72 | child: e, 73 | ), 74 | ), 75 | Text(e.fit.toString()) 76 | ], 77 | ); 78 | }).toList() 79 | ), 80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/routes/image_internal.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ImageInternalTestRoute extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return Column( 7 | children: [ 8 | MyImage( 9 | imageProvider: NetworkImage( 10 | "https://avatars2.githubusercontent.com/u/20411648?s=460&v=4", 11 | ), 12 | ), 13 | ], 14 | ); 15 | } 16 | } 17 | 18 | class MyImage extends StatefulWidget { 19 | const MyImage({ 20 | Key key, 21 | @required this.imageProvider, 22 | }) : assert(imageProvider != null), 23 | super(key: key); 24 | 25 | final ImageProvider imageProvider; 26 | 27 | @override 28 | _MyImageState createState() => _MyImageState(); 29 | } 30 | 31 | class _MyImageState extends State { 32 | ImageStream _imageStream; 33 | ImageInfo _imageInfo; 34 | 35 | @override 36 | void didChangeDependencies() { 37 | super.didChangeDependencies(); 38 | // We call _getImage here because createLocalImageConfiguration() needs to 39 | // be called again if the dependencies changed, in case the changes relate 40 | // to the DefaultAssetBundle, MediaQuery, etc, which that method uses. 41 | _getImage(); 42 | } 43 | 44 | @override 45 | void didUpdateWidget(MyImage oldWidget) { 46 | super.didUpdateWidget(oldWidget); 47 | if (widget.imageProvider != oldWidget.imageProvider) _getImage(); 48 | } 49 | 50 | void _getImage() { 51 | final ImageStream oldImageStream = _imageStream; 52 | _imageStream = 53 | widget.imageProvider.resolve(createLocalImageConfiguration(context)); 54 | if (_imageStream.key != oldImageStream?.key) { 55 | // If the keys are the same, then we got the same image back, and so we don't 56 | // need to update the listeners. If the key changed, though, we must make sure 57 | // to switch our listeners to the new image stream. 58 | final ImageStreamListener listener = ImageStreamListener(_updateImage); 59 | oldImageStream?.removeListener(listener); 60 | _imageStream.addListener(listener); 61 | } 62 | } 63 | 64 | void _updateImage(ImageInfo imageInfo, bool synchronousCall) { 65 | setState(() { 66 | // Trigger a build whenever the image changes. 67 | _imageInfo = imageInfo; 68 | }); 69 | } 70 | 71 | @override 72 | void dispose() { 73 | _imageStream.removeListener(ImageStreamListener(_updateImage)); 74 | super.dispose(); 75 | } 76 | 77 | @override 78 | Widget build(BuildContext context) { 79 | return RawImage( 80 | image: _imageInfo?.image, // this is a dart:ui Image object 81 | scale: _imageInfo?.scale ?? 1.0, 82 | ); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/routes/index.dart: -------------------------------------------------------------------------------- 1 | export 'gradient_button.dart'; 2 | export 'custom_paint.dart'; 3 | export 'turn_box.dart'; 4 | export 'gradient_circular_progress.dart'; 5 | export 'pointer.dart'; 6 | export 'custom_ui_framework.dart'; 7 | export 'test.dart'; 8 | export 'scaffold.dart'; 9 | export 'text.dart'; 10 | export 'button.dart'; 11 | export 'image_icon.dart'; 12 | export 'switch_checkbox.dart'; 13 | export 'progress.dart'; 14 | export 'row_column.dart'; 15 | export 'decoratedbox.dart'; 16 | export 'align.dart'; 17 | export 'clip.dart'; 18 | export 'table.dart'; 19 | export 'color.dart'; 20 | export 'theme.dart'; 21 | export 'image_internal.dart'; 22 | export 'scale_animation.dart'; 23 | export 'scale_animation_animatedwidget.dart'; 24 | export 'scale_animation_animatedbuilder.dart'; 25 | export 'grow_transition.dart'; 26 | export 'hero_animation.dart'; 27 | export 'stagger_animation.dart'; 28 | export 'animation_switcher.dart'; 29 | export 'animated_switcher_counter.dart'; 30 | export 'provider.dart'; 31 | export 'inheritedwidget.dart'; 32 | export 'notification.dart'; 33 | export 'padding.dart'; 34 | export 'size_constraints.dart'; 35 | export 'router.dart'; 36 | export 'future_and_stream_builder.dart'; 37 | export 'camera.dart'; 38 | export 'platform_view.dart'; 39 | export 'dialog.dart'; 40 | export 'animated_widgets.dart'; 41 | export 'context.dart'; 42 | export 'state.dart'; 43 | export 'textfield.dart'; -------------------------------------------------------------------------------- /lib/routes/inheritedwidget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class InheritedWidgetTestRoute extends StatefulWidget { 4 | @override 5 | _InheritedWidgetTestRouteState createState() => 6 | new _InheritedWidgetTestRouteState(); 7 | } 8 | 9 | class _InheritedWidgetTestRouteState extends State { 10 | int count = 0; 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Center( 15 | child: ShareDataWidget( 16 | //使用ShareDataWidget 17 | data: count, 18 | child: Column( 19 | mainAxisAlignment: MainAxisAlignment.center, 20 | children: [ 21 | Padding( 22 | padding: const EdgeInsets.only(bottom: 20.0), 23 | child: _TestWidget(), //子widget中依赖ShareDataWidget 24 | ), 25 | RaisedButton( 26 | child: Text("Increment"), 27 | //每点击一次,将count自增,然后重新build,ShareDataWidget的data将被更新 28 | onPressed: () => setState(() => ++count), 29 | ) 30 | ], 31 | ), 32 | ), 33 | ); 34 | } 35 | } 36 | 37 | 38 | class ShareDataWidget extends InheritedWidget { 39 | ShareDataWidget({@required this.data, Widget child}) : super(child: child); 40 | 41 | final int data; //需要在子树中共享的数据,保存点击次数 42 | 43 | //定义一个便捷方法,方便子树中的widget获取共享数据 44 | static ShareDataWidget of(BuildContext context) { 45 | Element a; 46 | //return context.dependOnInheritedWidgetOfExactType(); 47 | return context.getElementForInheritedWidgetOfExactType().widget; 48 | } 49 | 50 | //该回调决定当data发生变化时,是否通知子树中依赖data的Widget 51 | @override 52 | bool updateShouldNotify(ShareDataWidget old) { 53 | return old.data != data; 54 | } 55 | } 56 | 57 | class _TestWidget extends StatefulWidget { 58 | @override 59 | __TestWidgetState createState() => new __TestWidgetState(); 60 | } 61 | 62 | class __TestWidgetState extends State<_TestWidget> { 63 | @override 64 | Widget build(BuildContext context) { 65 | //使用InheritedWidget中的共享数据 66 | return Text(ShareDataWidget.of(context).data.toString()); 67 | } 68 | 69 | @override 70 | void didChangeDependencies() { 71 | super.didChangeDependencies(); 72 | //父或祖先widget中的InheritedWidget改变(updateShouldNotify返回true)时会被调用。 73 | //如果build中没有依赖InheritedWidget,则此回调不会被调用。 74 | print("Dependencies change"); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/routes/notification.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class NotificationRoute extends StatefulWidget { 4 | @override 5 | NotificationRouteState createState() { 6 | return new NotificationRouteState(); 7 | } 8 | } 9 | 10 | class NotificationRouteState extends State { 11 | String _msg=""; 12 | @override 13 | Widget build(BuildContext context) { 14 | //监听通知 15 | return NotificationListener( 16 | onNotification: (notification){ 17 | print(notification.msg); 18 | return false; 19 | }, 20 | child: NotificationListener( 21 | onNotification: (notification) { 22 | setState(() { 23 | _msg+=notification.msg+" "; 24 | }); 25 | return false; 26 | }, 27 | child: Center( 28 | child: Column( 29 | mainAxisSize: MainAxisSize.min, 30 | children: [ 31 | // RaisedButton( 32 | // onPressed: () => MyNotification("Hi").dispatch(context), 33 | // child: Text("Send Notification"), 34 | // ), 35 | Builder( 36 | builder: (context) { 37 | return RaisedButton( 38 | //按钮点击时分发通知 39 | onPressed: () => MyNotification("Hi").dispatch(context), 40 | child: Text("Send Notification"), 41 | ); 42 | }, 43 | ), 44 | Text(_msg) 45 | ], 46 | ), 47 | ), 48 | ), 49 | ); 50 | } 51 | } 52 | 53 | class MyNotification extends Notification { 54 | MyNotification(this.msg); 55 | final String msg; 56 | } -------------------------------------------------------------------------------- /lib/routes/padding.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class PaddingTestRoute extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return Padding( 7 | //上下左右各添加16像素补白 8 | padding: EdgeInsets.all(16.0), 9 | child: Column( 10 | //显式指定对齐方式为左对齐,排除对齐干扰 11 | crossAxisAlignment: CrossAxisAlignment.start, 12 | children: [ 13 | Padding( 14 | //左边添加8像素补白 15 | padding: const EdgeInsets.only(left: 8.0), 16 | child: Text("Hello world"), 17 | ), 18 | Padding( 19 | //上下各添加8像素补白 20 | padding: const EdgeInsets.symmetric(vertical: 8.0), 21 | child: Text("I am Jack"), 22 | ), 23 | Padding( 24 | // 分别指定四个方向的补白 25 | padding: const EdgeInsets.fromLTRB(20.0,.0,20.0,20.0), 26 | child: Text("Your friend"), 27 | ) 28 | ], 29 | ), 30 | ); 31 | } 32 | } -------------------------------------------------------------------------------- /lib/routes/platform_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:webview_flutter/webview_flutter.dart'; 3 | class PlatformViewRoute extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return WebView( 7 | initialUrl: "https://flutterchina.club", 8 | javascriptMode: JavascriptMode.unrestricted, 9 | ); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/routes/pointer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class PointerRoute extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return Stack( 7 | children: [ 8 | Listener( 9 | child: ConstrainedBox( 10 | constraints: BoxConstraints.tight(Size(300.0, 200.0)), 11 | child: DecoratedBox( 12 | decoration: BoxDecoration(color: Colors.blue)), 13 | ), 14 | onPointerDown: (event) => print("down0"), 15 | ), 16 | Listener( 17 | child: ConstrainedBox( 18 | constraints: BoxConstraints.tight(Size(200.0, 100.0)), 19 | child: Center(child: Text("左上角200*100范围内非文本区域点击")), 20 | ), 21 | onPointerDown: (event) => print("down1"), 22 | //behavior: HitTestBehavior.translucent, //放开此行注释后可以"点透" 23 | //behavior: HitTestBehavior.opaque, // 放开此行后只会打印"down1" 24 | ) 25 | ], 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/routes/progress.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ProgressRoute extends StatefulWidget { 4 | @override 5 | _ProgressRouteState createState() => _ProgressRouteState(); 6 | } 7 | 8 | class _ProgressRouteState extends State 9 | with SingleTickerProviderStateMixin { 10 | AnimationController _animationController; 11 | 12 | @override 13 | void initState() { 14 | _animationController = 15 | new AnimationController(vsync: this, duration: Duration(seconds: 3)); 16 | _animationController.forward(); 17 | _animationController.addListener(() => setState(() => {})); 18 | 19 | super.initState(); 20 | } 21 | 22 | @override 23 | void dispose() { 24 | _animationController.dispose(); 25 | super.dispose(); 26 | } 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return SingleChildScrollView( 31 | child: Column( 32 | children: [ 33 | LinearProgressIndicator( 34 | backgroundColor: Colors.grey[200], 35 | valueColor: AlwaysStoppedAnimation(Colors.blue), 36 | ), 37 | LinearProgressIndicator( 38 | backgroundColor: Colors.grey[200], 39 | valueColor: AlwaysStoppedAnimation(Colors.blue), 40 | value: .5, 41 | ), 42 | SizedBox( 43 | height: 3, 44 | child: LinearProgressIndicator( 45 | backgroundColor: Colors.grey[200], 46 | valueColor: AlwaysStoppedAnimation(Colors.blue), 47 | value: .5, 48 | ), 49 | ), 50 | SizedBox( 51 | height: 100, 52 | width: 130, 53 | child: CircularProgressIndicator( 54 | backgroundColor: Colors.grey[200], 55 | valueColor: AlwaysStoppedAnimation(Colors.blue), 56 | value: .7, 57 | ), 58 | ), 59 | CircularProgressIndicator( 60 | backgroundColor: Colors.grey[200], 61 | valueColor: AlwaysStoppedAnimation(Colors.blue), 62 | value: .5, 63 | ), 64 | LinearProgressIndicator( 65 | backgroundColor: Colors.grey[200], 66 | valueColor: ColorTween(begin: Colors.grey, end: Colors.blue) 67 | .animate(_animationController), 68 | value: _animationController.value, 69 | ), 70 | CircularProgressIndicator( 71 | backgroundColor: Colors.grey[200], 72 | valueColor: ColorTween(begin: Colors.grey, end: Colors.blue) 73 | .animate(_animationController), 74 | value: _animationController.value, 75 | ), 76 | CircularProgressIndicator( 77 | backgroundColor: Colors.grey[200], 78 | valueColor: ColorTween(begin: Colors.grey, end: Colors.blue) 79 | .animate(_animationController), 80 | ) 81 | ].map((e) { 82 | return Padding( 83 | padding: EdgeInsets.all(16), 84 | child: e, 85 | ); 86 | }).toList(), 87 | ), 88 | ); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/routes/provider.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | import 'package:flutter/material.dart'; 3 | import '../provider.dart'; 4 | 5 | 6 | 7 | class ProviderRoute extends StatefulWidget { 8 | @override 9 | _ProviderRouteState createState() => _ProviderRouteState(); 10 | } 11 | 12 | class _ProviderRouteState extends State { 13 | @override 14 | void didUpdateWidget(ProviderRoute oldWidget) { 15 | // TODO: implement didUpdateWidget 16 | super.didUpdateWidget(oldWidget); 17 | print("xxx"); 18 | } 19 | @override 20 | Widget build(BuildContext context) { 21 | return Center( 22 | child: ChangeNotifierProvider( 23 | data: CartModel(), 24 | child: Builder(builder: (context) { 25 | return Column( 26 | children: [ 27 | Consumer( 28 | builder: (BuildContext context, cart) => 29 | Text("总价: ${cart.totalPrice}"), 30 | ), 31 | Builder(builder: (context) { 32 | print("RaisedButton build"); 33 | return RaisedButton( 34 | child: Text("添加商品"), 35 | onPressed: () { 36 | ChangeNotifierProvider.of(context, listen: false) 37 | .add(Item(20.0, 1)); 38 | }, 39 | ); 40 | }) 41 | ], 42 | ); 43 | }), 44 | ), 45 | ); 46 | } 47 | } 48 | 49 | class CartModel extends ChangeNotifier { 50 | // 用于保存购物车中商品列表 51 | final List _items = []; 52 | 53 | // 禁止改变购物车里的商品信息 54 | UnmodifiableListView get items => UnmodifiableListView(_items); 55 | 56 | // 购物车中商品的总价 57 | double get totalPrice => 58 | _items.fold(0, (value, item) => value + item.count * item.price); 59 | 60 | // 将 [item] 添加到购物车。这是唯一一种能从外部改变购物车的方法。 61 | void add(Item item) { 62 | _items.add(item); 63 | // 通知监听器(订阅者),重新构建InheritedProvider, 更新状态。 64 | notifyListeners(); 65 | } 66 | } 67 | 68 | class Item { 69 | Item(this.price, this.count); 70 | 71 | double price; //商品单价 72 | int count; // 商品份数 73 | } 74 | -------------------------------------------------------------------------------- /lib/routes/router.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../widgets/index.dart'; 3 | 4 | class RouterTestRoute extends StatelessWidget { 5 | @override 6 | Widget build(BuildContext context) { 7 | return Center( 8 | child: RaisedButton( 9 | child: Text("打开提示页"), 10 | onPressed: () async { 11 | var result = await Navigator.push( 12 | context, 13 | MaterialPageRoute( 14 | builder: (context) { 15 | return TipRoute( 16 | text: "我是提示xxxx", 17 | ); 18 | }, 19 | ), 20 | ); 21 | print("路由返回值: $result"); 22 | }, 23 | 24 | ), 25 | ); 26 | } 27 | } 28 | 29 | class TipRoute extends StatelessWidget { 30 | TipRoute({ 31 | Key key, 32 | @required this.text, // 接收一个text参数 33 | }) : super(key: key); 34 | final String text; 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | return Scaffold( 39 | appBar: AppBar( 40 | title: Text("提示"), 41 | ), 42 | body: Padding( 43 | padding: EdgeInsets.all(18), 44 | child: Center( 45 | child: Column( 46 | children: [ 47 | Text(text), 48 | RaisedButton( 49 | onPressed: () => Navigator.pop(context, "我是返回值"), 50 | child: Text("返回"), 51 | ) 52 | ], 53 | ), 54 | ), 55 | ), 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/routes/row_column.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CenterColumnRoute extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return ConstrainedBox( 7 | constraints: BoxConstraints(minWidth: double.infinity), 8 | child: Column( 9 | crossAxisAlignment: CrossAxisAlignment.center, 10 | children: [ 11 | Text("hi"), 12 | Text("world"), 13 | ], 14 | ), 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/routes/scale_animation.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ScaleAnimationRoute extends StatefulWidget { 4 | @override 5 | _ScaleAnimationRouteState createState() => new _ScaleAnimationRouteState(); 6 | } 7 | 8 | //需要继承TickerProvider,如果有多个AnimationController,则应该使用TickerProviderStateMixin。 9 | class _ScaleAnimationRouteState extends State with SingleTickerProviderStateMixin{ 10 | 11 | Animation animation; 12 | AnimationController controller; 13 | 14 | initState() { 15 | super.initState(); 16 | controller = new AnimationController( 17 | duration: const Duration(seconds: 2), vsync: this); 18 | 19 | //匀速 20 | //图片宽高从0变到300 21 | animation = new Tween(begin: 0.0, end: 300.0).animate(controller) 22 | ..addListener(() { 23 | setState(()=>{}); 24 | }); 25 | 26 | // //使用弹性曲线 27 | // animation=CurvedAnimation(parent: controller, curve: Curves.bounceIn); 28 | // //图片宽高从0变到300 29 | // animation = new Tween(begin: 0.0, end: 300.0).animate(animation) 30 | // ..addListener(() { 31 | // setState(() { 32 | // }); 33 | // }); 34 | 35 | //启动动画(正向执行) 36 | controller.forward(); 37 | } 38 | 39 | @override 40 | Widget build(BuildContext context) { 41 | 42 | return new Center( 43 | child: Image.asset("imgs/avatar.png", 44 | width: animation.value, 45 | height: animation.value 46 | ), 47 | ); 48 | } 49 | 50 | dispose() { 51 | //路由销毁时需要释放动画资源 52 | controller.dispose(); 53 | super.dispose(); 54 | } 55 | } -------------------------------------------------------------------------------- /lib/routes/scale_animation_animatedbuilder.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | 4 | class ScaleAnimationRoute2 extends StatefulWidget { 5 | @override 6 | _ScaleAnimationRouteState createState() => new _ScaleAnimationRouteState(); 7 | } 8 | 9 | class _ScaleAnimationRouteState extends State 10 | with SingleTickerProviderStateMixin { 11 | 12 | Animation animation; 13 | AnimationController controller; 14 | 15 | initState() { 16 | super.initState(); 17 | controller = new AnimationController( 18 | duration: const Duration(seconds: 2), vsync: this); 19 | //图片宽高从0变到300 20 | animation = new Tween(begin: 0.0, end: 300.0).animate(controller); 21 | //启动动画 22 | controller.forward(); 23 | } 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return AnimatedBuilder( 28 | animation: animation, 29 | child: Image.asset("imgs/avatar.png"), 30 | builder: (BuildContext ctx, Widget child) { 31 | return new Center( 32 | child: Container( 33 | height: animation.value, 34 | width: animation.value, 35 | child: child, 36 | ), 37 | ); 38 | }, 39 | ); 40 | } 41 | 42 | dispose() { 43 | //路由销毁时需要释放动画资源 44 | controller.dispose(); 45 | super.dispose(); 46 | } 47 | } -------------------------------------------------------------------------------- /lib/routes/scale_animation_animatedwidget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AnimatedImage extends AnimatedWidget { 4 | AnimatedImage({Key key, Animation animation}) 5 | : super(key: key, listenable: animation); 6 | 7 | Widget build(BuildContext context) { 8 | final Animation animation = listenable; 9 | return new Center( 10 | child: Image.asset("imgs/avatar.png", 11 | width: animation.value, 12 | height: animation.value 13 | ), 14 | ); 15 | } 16 | } 17 | 18 | 19 | class ScaleAnimationRoute1 extends StatefulWidget { 20 | @override 21 | _ScaleAnimationRouteState createState() => new _ScaleAnimationRouteState(); 22 | } 23 | 24 | class _ScaleAnimationRouteState extends State 25 | with SingleTickerProviderStateMixin { 26 | 27 | Animation animation; 28 | AnimationController controller; 29 | 30 | initState() { 31 | super.initState(); 32 | controller = new AnimationController( 33 | duration: const Duration(seconds: 2), vsync: this); 34 | //图片宽高从0变到300 35 | animation = new Tween(begin: 0.0, end: 300.0).animate(controller); 36 | //启动动画 37 | controller.forward(); 38 | } 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | return AnimatedImage(animation: animation,); 43 | } 44 | 45 | dispose() { 46 | //路由销毁时需要释放动画资源 47 | controller.dispose(); 48 | super.dispose(); 49 | } 50 | } -------------------------------------------------------------------------------- /lib/routes/size_constraints.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SizeConstraintsRoute extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | Widget redBox = DecoratedBox( 7 | decoration: BoxDecoration(color: Colors.red), 8 | ); 9 | return Scaffold( 10 | appBar: AppBar( 11 | title: Text("尺寸限制类容器"), 12 | actions: [ 13 | UnconstrainedBox( 14 | child: SizedBox( 15 | width: 20, 16 | height: 20, 17 | child: CircularProgressIndicator( 18 | strokeWidth: 3, 19 | value: .9, 20 | valueColor: AlwaysStoppedAnimation(Colors.white70), 21 | ), 22 | ), 23 | ) 24 | ], 25 | ), 26 | body: SingleChildScrollView( 27 | child: Column( 28 | children: [ 29 | ConstrainedBox( 30 | constraints: BoxConstraints( 31 | minWidth: double.infinity, //宽度尽可能大 32 | minHeight: 50.0 //最小高度为50像素 33 | ), 34 | child: Container(height: 5.0, child: redBox), 35 | ), 36 | SizedBox(width: 80.0, height: 80.0, child: redBox), 37 | ConstrainedBox( 38 | constraints: BoxConstraints(minWidth: 60.0, minHeight: 60.0), 39 | //父 40 | child: ConstrainedBox( 41 | constraints: BoxConstraints(minWidth: 90.0, minHeight: 20.0), 42 | //子 43 | child: redBox, 44 | )), 45 | ConstrainedBox( 46 | constraints: BoxConstraints(minWidth: 90.0, minHeight: 20.0), 47 | child: ConstrainedBox( 48 | constraints: BoxConstraints(minWidth: 60.0, minHeight: 60.0), 49 | child: redBox, 50 | )), 51 | AspectRatio( 52 | aspectRatio: 3, //宽是高的三倍 53 | child: redBox, 54 | ) 55 | ] 56 | .map((e) => Padding( 57 | padding: EdgeInsets.only(top: 30), 58 | child: e, 59 | )) 60 | .toList(), 61 | ), 62 | ), 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/routes/stagger_animation.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class StaggerRoute extends StatefulWidget { 4 | @override 5 | _StaggerRouteState createState() => _StaggerRouteState(); 6 | } 7 | 8 | class _StaggerRouteState extends State with TickerProviderStateMixin { 9 | AnimationController _controller; 10 | 11 | @override 12 | void initState() { 13 | super.initState(); 14 | 15 | _controller = AnimationController( 16 | duration: const Duration(milliseconds: 2000), 17 | vsync: this 18 | ); 19 | } 20 | 21 | 22 | Future _playAnimation() async { 23 | try { 24 | //先正向执行动画 25 | await _controller.forward().orCancel; 26 | //再反向执行动画 27 | await _controller.reverse().orCancel; 28 | } on TickerCanceled { 29 | // the animation got canceled, probably because we were disposed 30 | } 31 | } 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return GestureDetector( 36 | behavior: HitTestBehavior.opaque, 37 | onTap: () { 38 | _playAnimation(); 39 | }, 40 | child: Center( 41 | child: Container( 42 | width: 300.0, 43 | height: 300.0, 44 | decoration: BoxDecoration( 45 | color: Colors.black.withOpacity(0.1), 46 | border: Border.all( 47 | color: Colors.black.withOpacity(0.5), 48 | ), 49 | ), 50 | //调用我们定义的交错动画Widget 51 | child: StaggerAnimation( 52 | controller: _controller 53 | ), 54 | ), 55 | ), 56 | ); 57 | } 58 | } 59 | 60 | class StaggerAnimation extends StatelessWidget { 61 | StaggerAnimation({ Key key, this.controller }): super(key: key){ 62 | //高度动画 63 | height = Tween( 64 | begin:.0 , 65 | end: 300.0, 66 | ).animate( 67 | CurvedAnimation( 68 | parent: controller, 69 | curve: Interval( 70 | 0.0, 0.6, //间隔,前60%的动画时间 71 | curve: Curves.ease, 72 | ), 73 | ), 74 | ); 75 | 76 | color = ColorTween( 77 | begin:Colors.green , 78 | end:Colors.red, 79 | ).animate( 80 | CurvedAnimation( 81 | parent: controller, 82 | curve: Interval( 83 | 0.0, 0.6,//间隔,前60%的动画时间 84 | curve: Curves.ease, 85 | ), 86 | ), 87 | ); 88 | 89 | padding = Tween( 90 | begin:EdgeInsets.only(left: .0), 91 | end:EdgeInsets.only(left: 100.0), 92 | ).animate( 93 | CurvedAnimation( 94 | parent: controller, 95 | curve: Interval( 96 | 0.6, 1.0, //间隔,后40%的动画时间 97 | curve: Curves.ease, 98 | ), 99 | ), 100 | ); 101 | } 102 | 103 | 104 | final Animation controller; 105 | Animation height; 106 | Animation padding; 107 | Animation color; 108 | 109 | Widget _buildAnimation(BuildContext context, Widget child) { 110 | return Container( 111 | alignment: Alignment.bottomCenter, 112 | padding:padding.value , 113 | child: Container( 114 | color: color.value, 115 | width: 50.0, 116 | height: height.value, 117 | ), 118 | ); 119 | } 120 | 121 | @override 122 | Widget build(BuildContext context) { 123 | return AnimatedBuilder( 124 | builder: _buildAnimation, 125 | animation: controller, 126 | ); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /lib/routes/state.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class RetrieveStateRoute extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return Scaffold( 7 | appBar: AppBar( 8 | title: Text("子树中获取State对象"), 9 | ), 10 | body: Center( 11 | child: Builder(builder: (context) { 12 | return RaisedButton( 13 | onPressed: () { 14 | //ScaffoldState _state = context.findAncestorStateOfType(); 15 | ScaffoldState _state = Scaffold.of(context); 16 | _state.showSnackBar( 17 | SnackBar( 18 | content: Text("我是SnackBar"), 19 | ), 20 | ); 21 | }, 22 | child: Text("显示SnackBar"), 23 | ); 24 | }), 25 | ), 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/routes/switch_checkbox.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SwitchAndCheckBoxRoute extends StatefulWidget { 4 | @override 5 | _SwitchAndCheckBoxRouteState createState() => new _SwitchAndCheckBoxRouteState(); 6 | } 7 | 8 | class _SwitchAndCheckBoxRouteState extends State { 9 | bool _switchSelected=true; //维护单选开关状态 10 | bool _checkboxSelected=true;//维护复选框状态 11 | @override 12 | Widget build(BuildContext context) { 13 | return Column( 14 | children: [ 15 | Row( 16 | children: [ 17 | Switch( 18 | value: _switchSelected,//当前状态 19 | onChanged:(value){ 20 | //重新构建页面 21 | setState(() { 22 | _switchSelected=value; 23 | }); 24 | }, 25 | ), 26 | Text("关"), 27 | Switch( 28 | value: !_switchSelected,//当前状态 29 | onChanged:(value){ 30 | 31 | }, 32 | ), 33 | Text("开"), 34 | ], 35 | ), 36 | Row( 37 | children: [ 38 | Checkbox( 39 | value: _checkboxSelected, 40 | activeColor: Colors.red, //选中时的颜色 41 | onChanged:(value){ 42 | setState(() { 43 | _checkboxSelected=value; 44 | }); 45 | } , 46 | ), 47 | Text("未选中"), 48 | Checkbox( 49 | value: !_checkboxSelected, 50 | activeColor: Colors.red, //选中时的颜色 51 | onChanged:(value){ 52 | 53 | } , 54 | ), 55 | Text("选中"), 56 | ], 57 | ) 58 | ], 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/routes/table.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class TableRoute extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return Column( 7 | children: [ 8 | Table( 9 | border: TableBorder.all(color: Colors.grey[400]), 10 | defaultVerticalAlignment: TableCellVerticalAlignment.middle, 11 | children: [ 12 | TableRow( 13 | decoration: BoxDecoration(color: Colors.grey[200]), 14 | children: _addPadding( 15 | [ 16 | Text("姓名"), 17 | Text("性别"), 18 | Text("备注"), 19 | ], 20 | ), 21 | ), 22 | TableRow( 23 | children: _addPadding([ 24 | Text("张三"), 25 | Text("男"), 26 | Text("我和李四不是兄弟" * 2), 27 | ]), 28 | ), 29 | TableRow( 30 | children: _addPadding([ 31 | Text("李四"), 32 | Text("男"), 33 | Text("不予置评"), 34 | ]), 35 | ), 36 | ], 37 | ), 38 | DataTable( 39 | sortColumnIndex: 0, 40 | sortAscending: true, 41 | rows: [ 42 | DataRow( 43 | cells: [ 44 | DataCell(Text("1")), 45 | DataCell(Text("28.5")), 46 | ], 47 | ), 48 | DataRow( 49 | cells: [ 50 | DataCell(Text("2")), 51 | DataCell(Text("27.5")), 52 | ], 53 | ), 54 | ], 55 | columns: [ 56 | DataColumn( 57 | label: Text("Id"), 58 | numeric: true, 59 | onSort: (a, b) => print("$a $b")), 60 | DataColumn(label: Text("价格¥"), numeric: true) 61 | ], 62 | ), 63 | ], 64 | ); 65 | } 66 | 67 | _addPadding(List w) { 68 | return w 69 | .map((e) => Padding( 70 | padding: EdgeInsets.all(6), 71 | child: e, 72 | )) 73 | .toList(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/routes/test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/rendering.dart'; 3 | 4 | class TestRoute extends StatefulWidget { 5 | @override 6 | _TestRouteState createState() => _TestRouteState(); 7 | } 8 | 9 | class _TestRouteState extends State { 10 | BuildContext ctx; 11 | static GlobalKey _globalKey = new GlobalKey(); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Column(children: [ 16 | ListTile(title:Text("商品列表")), 17 | Expanded( 18 | child: ListView.builder(itemBuilder: (BuildContext context, int index) { 19 | return ListTile(title: Text("$index")); 20 | }), 21 | ), 22 | ]); 23 | } 24 | 25 | @override 26 | void didUpdateWidget(TestRoute oldWidget) { 27 | super.didUpdateWidget(oldWidget); 28 | print("didUpdateWidget"); 29 | } 30 | } 31 | 32 | class Test extends StatefulWidget { 33 | Test({Key key}) : super(key: key); 34 | 35 | @override 36 | _TestState createState() => _TestState(); 37 | } 38 | 39 | class _TestState extends State { 40 | List ctx = []; 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | return ListView.builder(itemBuilder: (BuildContext context, int index) { 45 | return ListTile(title: Text("$index")); 46 | }); 47 | } 48 | 49 | @override 50 | void didUpdateWidget(Test oldWidget) { 51 | super.didUpdateWidget(oldWidget); 52 | print("didUpdateWidget test"); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/routes/text.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/gestures.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class TextRoute extends StatefulWidget { 5 | @override 6 | _TextRouteState createState() => _TextRouteState(); 7 | } 8 | 9 | class _TextRouteState extends State { 10 | GestureRecognizer _tapRecognizer; 11 | 12 | @override 13 | void initState() { 14 | _tapRecognizer = TapGestureRecognizer(); 15 | super.initState(); 16 | } 17 | 18 | @override 19 | void dispose() { 20 | _tapRecognizer.dispose(); 21 | super.dispose(); 22 | } 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return SingleChildScrollView( 27 | child: Column( 28 | children: [ 29 | Text( 30 | "Hello world", 31 | textAlign: TextAlign.center, 32 | ), 33 | Text( 34 | "Hello world! I'm Jack. " * 4, 35 | maxLines: 1, 36 | overflow: TextOverflow.ellipsis, 37 | ), 38 | Text( 39 | "Hello world", 40 | textScaleFactor: 1.5, 41 | ), 42 | Text( 43 | "Hello world " * 6, //字符串重复六次 44 | textAlign: TextAlign.center, 45 | ), 46 | Text( 47 | "Hello world", 48 | style: TextStyle( 49 | color: Colors.blue, 50 | fontSize: 18.0, 51 | height: 1.2, 52 | fontFamily: "Courier", 53 | background: new Paint()..color = Colors.yellow, 54 | decoration: TextDecoration.underline, 55 | decorationStyle: TextDecorationStyle.dashed), 56 | ), 57 | Text.rich( 58 | TextSpan(children: [ 59 | TextSpan(text: "Home: "), 60 | TextSpan( 61 | text: "https://flutterchina.club", 62 | style: TextStyle(color: Colors.blue), 63 | recognizer: _tapRecognizer), 64 | ]), 65 | ), 66 | DefaultTextStyle( 67 | //1.设置文本默认样式 68 | style: TextStyle( 69 | color: Colors.red, 70 | fontSize: 20.0, 71 | ), 72 | textAlign: TextAlign.start, 73 | child: Column( 74 | crossAxisAlignment: CrossAxisAlignment.start, 75 | children: [ 76 | Text("hello world"), 77 | Text("I am Jack"), 78 | Text( 79 | "I am Jack", 80 | style: TextStyle( 81 | inherit: false, //2.不继承默认样式 82 | color: Colors.grey), 83 | ), 84 | ], 85 | ), 86 | ), 87 | ], 88 | ), 89 | ); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /lib/routes/textfield.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class FocusTestRoute extends StatefulWidget { 4 | @override 5 | _FocusTestRouteState createState() => new _FocusTestRouteState(); 6 | } 7 | 8 | class _FocusTestRouteState extends State { 9 | FocusNode focusNode1 = new FocusNode(); 10 | FocusNode focusNode2 = new FocusNode(); 11 | FocusScopeNode focusScopeNode; 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Padding( 16 | padding: EdgeInsets.all(16.0), 17 | child: Column( 18 | children: [ 19 | TextField( 20 | autofocus: true, 21 | focusNode: focusNode1, //关联focusNode1 22 | decoration: InputDecoration(labelText: "input1"), 23 | ), 24 | TextField( 25 | focusNode: focusNode2, //关联focusNode2 26 | decoration: InputDecoration(labelText: "input2"), 27 | ), 28 | Builder( 29 | builder: (ctx) { 30 | return Column( 31 | children: [ 32 | RaisedButton( 33 | child: Text("移动焦点"), 34 | onPressed: () { 35 | //将焦点从第一个TextField移到第二个TextField 36 | // 这是一种写法 FocusScope.of(context).requestFocus(focusNode2); 37 | // 这是第二种写法 38 | if (null == focusScopeNode) { 39 | focusScopeNode = FocusScope.of(context); 40 | } 41 | focusScopeNode.requestFocus(focusNode2); 42 | }, 43 | ), 44 | RaisedButton( 45 | child: Text("隐藏键盘"), 46 | onPressed: () { 47 | // 当所有编辑框都失去焦点时键盘就会收起 48 | focusNode1.unfocus(); 49 | focusNode2.unfocus(); 50 | }, 51 | ), 52 | ], 53 | ); 54 | }, 55 | ), 56 | Text("自定义下划线颜色"), 57 | TextField( 58 | decoration: InputDecoration( 59 | labelText: "请输入用户名", 60 | prefixIcon: Icon(Icons.person), 61 | enabledBorder: UnderlineInputBorder( 62 | borderSide: BorderSide(color: Colors.grey), 63 | ), 64 | focusedBorder: UnderlineInputBorder( 65 | borderSide: BorderSide(color: Colors.blue), 66 | ), 67 | ), 68 | ), 69 | ], 70 | ), 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/routes/theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ThemeTestRoute extends StatefulWidget { 4 | @override 5 | _ThemeTestRouteState createState() => new _ThemeTestRouteState(); 6 | } 7 | 8 | class _ThemeTestRouteState extends State { 9 | Color _themeColor = Colors.teal; //当前路由主题色 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | ThemeData themeData = Theme.of(context); 14 | return Theme( 15 | data: ThemeData( 16 | primarySwatch: _themeColor, //用于导航栏、FloatingActionButton的背景色等 17 | iconTheme: IconThemeData(color: _themeColor) //用于Icon颜色 18 | ), 19 | child: Scaffold( 20 | appBar: AppBar(title: Text("主题测试")), 21 | body: Column( 22 | mainAxisAlignment: MainAxisAlignment.center, 23 | children: [ 24 | //第一行Icon使用主题中的iconTheme 25 | Row( 26 | mainAxisAlignment: MainAxisAlignment.center, 27 | children: [ 28 | Icon(Icons.favorite), 29 | Icon(Icons.airport_shuttle), 30 | Text(" 颜色跟随主题") 31 | ] 32 | ), 33 | //为第二行Icon自定义颜色(固定为黑色) 34 | Theme( 35 | data: themeData.copyWith( 36 | iconTheme: themeData.iconTheme.copyWith( 37 | color: Colors.black 38 | ), 39 | ), 40 | child: Row( 41 | mainAxisAlignment: MainAxisAlignment.center, 42 | children: [ 43 | Icon(Icons.favorite), 44 | Icon(Icons.airport_shuttle), 45 | Text(" 颜色固定黑色") 46 | ] 47 | ), 48 | ), 49 | ], 50 | ), 51 | floatingActionButton: FloatingActionButton( 52 | onPressed: () => //切换主题 53 | setState(() => 54 | _themeColor = 55 | _themeColor == Colors.teal ? Colors.blue : Colors.teal 56 | ), 57 | child: Icon(Icons.palette) 58 | ), 59 | ), 60 | ); 61 | } 62 | } -------------------------------------------------------------------------------- /lib/routes/turn_box.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../widgets/index.dart'; 3 | 4 | class TurnBoxRoute extends StatefulWidget { 5 | @override 6 | _TurnBoxRouteState createState() => new _TurnBoxRouteState(); 7 | } 8 | 9 | class _TurnBoxRouteState extends State { 10 | double _turns = .0; 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | 15 | return Center( 16 | child: Column( 17 | children: [ 18 | TurnBox( 19 | turns: _turns, 20 | speed: 500, 21 | child: Icon(Icons.refresh, size: 50,), 22 | ), 23 | TurnBox( 24 | turns: _turns, 25 | speed: 1000, 26 | child: Icon(Icons.refresh, size: 150.0,), 27 | ), 28 | RaisedButton( 29 | child: Text("顺时针旋转1/5圈"), 30 | onPressed: () { 31 | setState(() { 32 | _turns += .2; 33 | }); 34 | }, 35 | ), 36 | RaisedButton( 37 | child: Text("逆时针旋转1/5圈"), 38 | onPressed: () { 39 | setState(() { 40 | _turns -= .2; 41 | }); 42 | }, 43 | ) 44 | ], 45 | ), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/widgets/gradient_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class GradientButton extends StatelessWidget { 4 | GradientButton({ 5 | this.colors, 6 | this.width, 7 | this.height, 8 | this.onPressed, 9 | this.borderRadius, 10 | @required this.child, 11 | }); 12 | 13 | // 渐变色数组 14 | final List colors; 15 | 16 | // 按钮宽高 17 | final double width; 18 | final double height; 19 | 20 | final Widget child; 21 | final BorderRadius borderRadius; 22 | 23 | //点击回调 24 | final GestureTapCallback onPressed; 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | ThemeData theme = Theme.of(context); 29 | 30 | //确保colors数组不空 31 | List _colors = colors ?? 32 | [theme.primaryColor, theme.primaryColorDark ?? theme.primaryColor]; 33 | 34 | return DecoratedBox( 35 | decoration: BoxDecoration( 36 | gradient: LinearGradient(colors: _colors), 37 | borderRadius: borderRadius, 38 | //border: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)), 39 | ), 40 | child: Material( 41 | type: MaterialType.transparency, 42 | child: InkWell( 43 | splashColor: _colors.last, 44 | highlightColor: Colors.transparent, 45 | borderRadius: borderRadius, 46 | onTap: onPressed, 47 | child: ConstrainedBox( 48 | constraints: BoxConstraints.tightFor(height: height, width: width), 49 | child: Center( 50 | child: Padding( 51 | padding: const EdgeInsets.all(8.0), 52 | child: DefaultTextStyle( 53 | style: TextStyle(fontWeight: FontWeight.bold), 54 | child: child, 55 | ), 56 | ), 57 | ), 58 | ), 59 | ), 60 | ), 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/widgets/index.dart: -------------------------------------------------------------------------------- 1 | export 'page_scaffold.dart'; 2 | export 'gradient_button.dart'; 3 | export 'gradient_circular_progress_indicator.dart'; 4 | export 'turn_box.dart'; -------------------------------------------------------------------------------- /lib/widgets/page_scaffold.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class PageScaffold extends StatelessWidget { 4 | PageScaffold({this.title, this.body, this.padding}); 5 | 6 | final String title; 7 | final Widget body; 8 | final bool padding; 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Scaffold( 13 | appBar: AppBar( 14 | title: Text(title), 15 | ), 16 | body: padding 17 | ? Padding( 18 | padding: const EdgeInsets.all(16.0), 19 | child: body, 20 | ) 21 | : body, 22 | ); 23 | } 24 | } 25 | 26 | class PageInfo { 27 | PageInfo( 28 | this.title, 29 | this.builder, { 30 | this.withScaffold = true, 31 | this.padding = true, 32 | }); 33 | 34 | String title; 35 | WidgetBuilder builder; 36 | bool withScaffold; 37 | bool padding; 38 | } 39 | 40 | class ListPage extends StatelessWidget { 41 | ListPage(this.children); 42 | 43 | final List children; 44 | 45 | @override 46 | Widget build(BuildContext context) { 47 | return ListView(children: _generateItem(context)); 48 | } 49 | 50 | void _openPage(BuildContext context, PageInfo page) { 51 | Navigator.push(context, MaterialPageRoute(builder: (context) { 52 | if (!page.withScaffold) { 53 | return page.builder(context); 54 | } 55 | return PageScaffold(title: page.title, body: page.builder(context),); 56 | })); 57 | } 58 | 59 | List _generateItem(BuildContext context) { 60 | return children.map((page) { 61 | return ListTile( 62 | title: Text(page.title), 63 | trailing: Icon(Icons.keyboard_arrow_right), 64 | onTap: () => _openPage(context, page), 65 | ); 66 | }).toList(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/widgets/turn_box.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | /// Animates the rotation of a widget when [turns] is changed. 4 | 5 | class TurnBox extends StatefulWidget { 6 | const TurnBox({ 7 | Key key, 8 | this.turns = .0, 9 | this.speed = 200, 10 | this.child 11 | }) :super(key: key); 12 | 13 | /// Controls the rotation of the child. 14 | /// 15 | /// If the current value of the turns is v, the child will be 16 | /// rotated v * 2 * pi radians before being painted. 17 | final double turns; 18 | 19 | /// Animation duration in milliseconds 20 | final int speed; 21 | 22 | final Widget child; 23 | 24 | @override 25 | _TurnBoxState createState() => new _TurnBoxState(); 26 | } 27 | 28 | class _TurnBoxState extends State 29 | with SingleTickerProviderStateMixin { 30 | AnimationController _controller; 31 | 32 | @override 33 | void initState() { 34 | super.initState(); 35 | _controller = new AnimationController( 36 | vsync: this, 37 | lowerBound: -double.infinity, 38 | upperBound: double.infinity 39 | ); 40 | _controller.value = widget.turns; 41 | } 42 | 43 | @override 44 | void dispose() { 45 | _controller.dispose(); 46 | super.dispose(); 47 | } 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | return RotationTransition( 52 | turns: _controller, 53 | child: widget.child, 54 | ); 55 | } 56 | 57 | @override 58 | void didUpdateWidget(TurnBox oldWidget) { 59 | super.didUpdateWidget(oldWidget); 60 | if (oldWidget.turns != widget.turns) { 61 | _controller.animateTo( 62 | widget.turns, 63 | duration: Duration(milliseconds: widget.speed??200), 64 | curve: Curves.easeOut, 65 | ); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_in_action 2 | description: Flutter in action source code 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # Read more about versioning at semver.org. 10 | version: 1.0.0+1 11 | 12 | environment: 13 | sdk: ">=2.0.0-dev.68.0 <3.0.0" 14 | 15 | dependencies: 16 | flutter: 17 | sdk: flutter 18 | flutter_localizations: 19 | sdk: flutter 20 | 21 | # The following adds the Cupertino Icons font to your application. 22 | # Use with the CupertinoIcons class for iOS style icons. 23 | cupertino_icons: ^0.1.2 24 | intl: ^0.16.1 25 | dio: ^3.0.3 26 | dio_cookie_manager: ^1.0.0 27 | provider: ^3.0.0+1 28 | image_picker: ^0.6.0+20 29 | geolocator: ^5.1.1+1 30 | camera: ^0.5.2+2 31 | path_provider: ^1.2.0 32 | video_player: ^0.10.1+6 33 | webview_flutter: ^0.3.10+4 34 | 35 | dev_dependencies: 36 | flutter_test: 37 | sdk: flutter 38 | intl_translation: ^0.17.2 39 | 40 | 41 | # For information on the generic Dart part of this file, see the 42 | # following page: https://www.dartlang.org/tools/pub/pubspec 43 | 44 | # The following section is specific to Flutter. 45 | flutter: 46 | 47 | # The following line ensures that the Material Icons font is 48 | # included with your application, so that you can use the icons in 49 | # the material Icons class. 50 | uses-material-design: true 51 | 52 | # To add assets to your application, add an assets section, like this: 53 | assets: 54 | - imgs/avatar.png 55 | 56 | 57 | # An image asset can refer to one or more resolution-specific "variants", see 58 | # https://flutter.io/assets-and-images/#resolution-aware. 59 | 60 | # For details regarding adding assets from package dependencies, see 61 | # https://flutter.io/assets-and-images/#from-packages 62 | 63 | # To add custom fonts to your application, add a fonts section here, 64 | # in this "flutter" section. Each entry in this list should have a 65 | # "family" key with the font family name, and a "fonts" key with a 66 | # list giving the asset and other descriptors for the font. For 67 | # example: 68 | # fonts: 69 | # - family: Schyler 70 | # fonts: 71 | # - asset: fonts/Schyler-Regular.ttf 72 | # - asset: fonts/Schyler-Italic.ttf 73 | # style: italic 74 | # - family: Trajan Pro 75 | # fonts: 76 | # - asset: fonts/TrajanPro.ttf 77 | # - asset: fonts/TrajanPro_Bold.ttf 78 | # weight: 700 79 | # 80 | # For details regarding fonts from package dependencies, 81 | # see https://flutter.io/custom-fonts/#from-packages 82 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:flutter_in_action/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | --------------------------------------------------------------------------------