├── .DS_Store
├── README.md
├── flutter_wanandroid
├── .gitignore
├── .idea
│ ├── dictionaries
│ │ └── jakey.xml
│ ├── libraries
│ │ ├── Dart_Packages.xml
│ │ ├── Dart_SDK.xml
│ │ └── Flutter_Plugins.xml
│ ├── misc.xml
│ ├── modules.xml
│ ├── runConfigurations
│ │ └── main_dart.xml
│ ├── vcs.xml
│ └── workspace.xml
├── .metadata
├── README.md
├── android
│ ├── .gitignore
│ ├── app
│ │ ├── build.gradle
│ │ └── src
│ │ │ └── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── java
│ │ │ └── com
│ │ │ │ └── jakey
│ │ │ │ └── flutterwanandroid
│ │ │ │ └── MainActivity.java
│ │ │ └── res
│ │ │ ├── drawable
│ │ │ └── launch_background.xml
│ │ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ └── ic_launcher.png
│ │ │ └── values
│ │ │ └── styles.xml
│ ├── build.gradle
│ ├── gradle.properties
│ ├── gradle
│ │ └── wrapper
│ │ │ ├── gradle-wrapper.jar
│ │ │ └── gradle-wrapper.properties
│ ├── gradlew
│ ├── gradlew.bat
│ └── settings.gradle
├── assets
│ ├── drawer_header_bg.jpg
│ └── logo.png
├── flutter_wanandroid.iml
├── flutter_wanandroid_android.iml
├── ios
│ ├── .gitignore
│ ├── Flutter
│ │ ├── AppFrameworkInfo.plist
│ │ ├── Debug.xcconfig
│ │ └── Release.xcconfig
│ ├── Podfile
│ ├── Podfile.lock
│ ├── Runner.xcodeproj
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace
│ │ │ └── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── Runner.xcscheme
│ ├── Runner.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── Runner
│ │ ├── AppDelegate.h
│ │ ├── AppDelegate.m
│ │ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── icon-1024.png
│ │ │ ├── icon-20-ipad.png
│ │ │ ├── icon-20@2x-ipad.png
│ │ │ ├── icon-20@2x.png
│ │ │ ├── icon-20@3x.png
│ │ │ ├── icon-29-ipad.png
│ │ │ ├── icon-29.png
│ │ │ ├── icon-29@2x-ipad.png
│ │ │ ├── icon-29@2x.png
│ │ │ ├── icon-29@3x.png
│ │ │ ├── icon-40.png
│ │ │ ├── icon-40@2x.png
│ │ │ ├── icon-40@3x.png
│ │ │ ├── icon-50.png
│ │ │ ├── icon-50@2x.png
│ │ │ ├── icon-57.png
│ │ │ ├── icon-57@2x.png
│ │ │ ├── icon-60@2x.png
│ │ │ ├── icon-60@3x.png
│ │ │ ├── icon-72.png
│ │ │ ├── icon-72@2x.png
│ │ │ ├── icon-76.png
│ │ │ ├── icon-76@2x.png
│ │ │ └── icon-83.5@2x.png
│ │ └── LaunchImage.imageset
│ │ │ ├── Contents.json
│ │ │ ├── LaunchImage.png
│ │ │ ├── LaunchImage@2x.png
│ │ │ ├── LaunchImage@3x.png
│ │ │ └── README.md
│ │ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ │ ├── Info.plist
│ │ └── main.m
├── lib
│ ├── adapter
│ │ ├── base_adapter.dart
│ │ └── feed_item_adapter.dart
│ ├── common
│ │ ├── application.dart
│ │ ├── config.dart
│ │ └── wanandroid_api.dart
│ ├── home
│ │ ├── drawer_page.dart
│ │ ├── index_page.dart
│ │ ├── main_screen.dart
│ │ ├── navi_page.dart
│ │ ├── project_page.dart
│ │ ├── tree
│ │ │ ├── tree_item_tabs_page.dart
│ │ │ └── tree_page.dart
│ │ └── web_view_page.dart
│ ├── main.dart
│ ├── model
│ │ └── data_model.dart
│ ├── notifications
│ │ └── notifications.dart
│ ├── startup
│ │ ├── login_page.dart
│ │ └── register_page.dart
│ ├── test_page.dart
│ ├── utils
│ │ ├── dialog_util.dart
│ │ ├── http_util.dart
│ │ ├── log.dart
│ │ ├── shares_util.dart
│ │ └── snackbar_util.dart
│ └── widgets
│ │ ├── pull_to_refresh
│ │ ├── pull_to_refresh.dart
│ │ └── src
│ │ │ ├── indicator
│ │ │ └── classic_indicator.dart
│ │ │ ├── internals
│ │ │ ├── default_constants.dart
│ │ │ ├── indicator_config.dart
│ │ │ ├── indicator_wrap.dart
│ │ │ └── refresh_physics.dart
│ │ │ └── smart_refresher.dart
│ │ └── xbanner.dart
├── pubspec.lock
├── pubspec.yaml
└── test
│ └── widget_test.dart
└── raw
├── .DS_Store
├── Screenshot_1.png
├── Screenshot_2.png
├── Screenshot_3.png
├── Screenshot_4.png
├── Screenshot_5.png
├── Screenshot_6.png
├── app-release.apk
└── screenrecord.gif
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/.DS_Store
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # flutter_wanandroid
2 | Flutter版 [玩Android \- wanandroid\.com \- 每日推荐优质文章](http://www.wanandroid.com/) 网站的移动端练手项目,总体架构完成,还有部分细节未完成。
3 |
4 | # Android apk下载测试
5 | [apk 下载](https://github.com/JakeyYe/flutter_wanandroid/blob/master/raw/app-release.apk),该apk为release版,iOS安装包没有,可以自己下载代码安装测试。
6 |
7 | # 效果演示(Android手机,iOS效果类似)
8 |
9 |
10 |
11 |
12 |
17 |
18 |
23 |
24 |
25 | # 不足之处
26 | 1. ~~`TabBar+TabBarView`布局(最后一张图片的布局)点击Tab快速跳转界面会出现bug,关于此问题官方还为解决[Use TabBarView with AutomaticKeepAliveClientMixin and with 4 or more pages will cause error · Issue \#16502 · flutter/flutter](https://github.com/flutter/flutter/issues/16502);~~(已解决该问题,可查看代码了解解决方案)
27 |
28 | 2. WebView控件支持不是太好,[Inline Android and iOS WebView · Issue \#730 · flutter/flutter](https://github.com/flutter/flutter/issues/730);
29 |
30 | 3. 项目不足之处是并未完全完成:)。
--------------------------------------------------------------------------------
/flutter_wanandroid/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .dart_tool/
3 |
4 | .packages
5 | .pub/
6 |
7 | build/
8 |
9 | .flutter-plugins
10 |
--------------------------------------------------------------------------------
/flutter_wanandroid/.idea/dictionaries/jakey.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/flutter_wanandroid/.idea/libraries/Dart_SDK.xml:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/flutter_wanandroid/.idea/libraries/Flutter_Plugins.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/flutter_wanandroid/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/flutter_wanandroid/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/flutter_wanandroid/.idea/runConfigurations/main_dart.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/flutter_wanandroid/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/flutter_wanandroid/.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: 44b7e7d3f42f050a79712daab253af06e9daf530
8 | channel: beta
9 |
--------------------------------------------------------------------------------
/flutter_wanandroid/README.md:
--------------------------------------------------------------------------------
1 | # flutter_wanandroid
2 |
3 | A new Flutter project for WanAndroid.
4 |
5 | ## Getting Started
6 |
7 | For help getting started with Flutter, view our online
8 | [documentation](https://flutter.io/).
9 |
--------------------------------------------------------------------------------
/flutter_wanandroid/android/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | *.class
3 | .gradle
4 | /local.properties
5 | /key.properties
6 | /.idea/workspace.xml
7 | /.idea/libraries
8 | .DS_Store
9 | /build
10 | /captures
11 | GeneratedPluginRegistrant.java
12 |
--------------------------------------------------------------------------------
/flutter_wanandroid/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 | apply plugin: 'com.android.application'
15 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
16 |
17 | //需要添加签名文件
18 | def keystorePropertiesFile = rootProject.file("key.properties")
19 | def keystoreProperties = new Properties()
20 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
21 |
22 | android {
23 | compileSdkVersion 27
24 |
25 | lintOptions {
26 | disable 'InvalidPackage'
27 | }
28 |
29 | defaultConfig {
30 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
31 | applicationId "com.jakey.flutterwanandroid"
32 | minSdkVersion 16
33 | targetSdkVersion 27
34 | versionCode 1
35 | versionName "1.0"
36 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
37 | }
38 |
39 | signingConfigs{
40 | release{
41 | keyAlias keystoreProperties['keyAlias']
42 | keyPassword keystoreProperties['keyPassword']
43 | storeFile file(keystoreProperties['storeFile'])
44 | storePassword keystoreProperties['storePassword']
45 | }
46 | }
47 |
48 | buildTypes {
49 | release {
50 | // TODO: Add your own signing config for the release build.
51 | // Signing with the debug keys for now, so `flutter run --release` works.
52 | signingConfig signingConfigs.release
53 | }
54 | }
55 | }
56 |
57 | flutter {
58 | source '../..'
59 | }
60 |
61 | dependencies {
62 | testImplementation 'junit:junit:4.12'
63 | androidTestImplementation 'com.android.support.test:runner:1.0.1'
64 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
65 | }
66 |
--------------------------------------------------------------------------------
/flutter_wanandroid/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
15 |
19 |
26 |
30 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/flutter_wanandroid/android/app/src/main/java/com/jakey/flutterwanandroid/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.jakey.flutterwanandroid;
2 |
3 | import android.os.Bundle;
4 |
5 | import io.flutter.app.FlutterActivity;
6 | import io.flutter.plugins.GeneratedPluginRegistrant;
7 |
8 | public class MainActivity extends FlutterActivity {
9 | @Override
10 | protected void onCreate(Bundle savedInstanceState) {
11 | super.onCreate(savedInstanceState);
12 | GeneratedPluginRegistrant.registerWith(this);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/flutter_wanandroid/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | -
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/flutter_wanandroid/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/flutter_wanandroid/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/flutter_wanandroid/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/flutter_wanandroid/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | jcenter()
5 | }
6 |
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:3.0.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 |
--------------------------------------------------------------------------------
/flutter_wanandroid/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 |
--------------------------------------------------------------------------------
/flutter_wanandroid/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/flutter_wanandroid/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.1-all.zip
7 |
--------------------------------------------------------------------------------
/flutter_wanandroid/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/flutter_wanandroid/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/flutter_wanandroid/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 |
--------------------------------------------------------------------------------
/flutter_wanandroid/assets/drawer_header_bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/assets/drawer_header_bg.jpg
--------------------------------------------------------------------------------
/flutter_wanandroid/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/assets/logo.png
--------------------------------------------------------------------------------
/flutter_wanandroid/flutter_wanandroid.iml:
--------------------------------------------------------------------------------
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 |
39 |
--------------------------------------------------------------------------------
/flutter_wanandroid/flutter_wanandroid_android.iml:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .vagrant/
3 | .sconsign.dblite
4 | .svn/
5 |
6 | .DS_Store
7 | *.swp
8 | profile
9 |
10 | DerivedData/
11 | build/
12 | GeneratedPluginRegistrant.h
13 | GeneratedPluginRegistrant.m
14 |
15 | .generated/
16 |
17 | *.pbxuser
18 | *.mode1v3
19 | *.mode2v3
20 | *.perspectivev3
21 |
22 | !default.pbxuser
23 | !default.mode1v3
24 | !default.mode2v3
25 | !default.perspectivev3
26 |
27 | xcuserdata
28 |
29 | *.moved-aside
30 |
31 | *.pyc
32 | *sync/
33 | Icon?
34 | .tags*
35 |
36 | /Flutter/app.flx
37 | /Flutter/app.zip
38 | /Flutter/flutter_assets/
39 | /Flutter/App.framework
40 | /Flutter/Flutter.framework
41 | /Flutter/Generated.xcconfig
42 | /ServiceDefinitions.json
43 |
44 | Pods/
45 |
--------------------------------------------------------------------------------
/flutter_wanandroid/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 | UIRequiredDeviceCapabilities
24 |
25 | arm64
26 |
27 | MinimumOSVersion
28 | 8.0
29 |
30 |
31 |
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/flutter_wanandroid/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 | def parse_KV_file(file, separator='=')
8 | file_abs_path = File.expand_path(file)
9 | if !File.exists? file_abs_path
10 | return [];
11 | end
12 | pods_ary = []
13 | skip_line_start_symbols = ["#", "/"]
14 | File.foreach(file_abs_path) { |line|
15 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
16 | plugin = line.split(pattern=separator)
17 | if plugin.length == 2
18 | podname = plugin[0].strip()
19 | path = plugin[1].strip()
20 | podpath = File.expand_path("#{path}", file_abs_path)
21 | pods_ary.push({:name => podname, :path => podpath});
22 | else
23 | puts "Invalid plugin specification: #{line}"
24 | end
25 | }
26 | return pods_ary
27 | end
28 |
29 | target 'Runner' do
30 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
31 | # referring to absolute paths on developers' machines.
32 | system('rm -rf Pods/.symlinks')
33 | system('mkdir -p Pods/.symlinks/plugins')
34 |
35 | # Flutter Pods
36 | generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig')
37 | if generated_xcode_build_settings.empty?
38 | puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first."
39 | end
40 | generated_xcode_build_settings.map { |p|
41 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR'
42 | symlink = File.join('Pods', '.symlinks', 'flutter')
43 | File.symlink(File.dirname(p[:path]), symlink)
44 | pod 'Flutter', :path => File.join(symlink, File.basename(p[:path]))
45 | end
46 | }
47 |
48 | # Plugin Pods
49 | plugin_pods = parse_KV_file('../.flutter-plugins')
50 | plugin_pods.map { |p|
51 | symlink = File.join('Pods', '.symlinks', 'plugins', p[:name])
52 | File.symlink(p[:path], symlink)
53 | pod p[:name], :path => File.join(symlink, 'ios')
54 | }
55 | end
56 |
57 | post_install do |installer|
58 | installer.pods_project.targets.each do |target|
59 | target.build_configurations.each do |config|
60 | config.build_settings['ENABLE_BITCODE'] = 'NO'
61 | end
62 | end
63 | end
64 |
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Flutter (1.0.0)
3 | - flutter_web_view (0.0.1):
4 | - Flutter
5 | - flutter_webview_plugin (0.0.1):
6 | - Flutter
7 | - path_provider (0.0.1):
8 | - Flutter
9 | - shared_preferences (0.0.1):
10 | - Flutter
11 |
12 | DEPENDENCIES:
13 | - Flutter (from `Pods/.symlinks/flutter/ios`)
14 | - flutter_web_view (from `Pods/.symlinks/plugins/flutter_web_view/ios`)
15 | - flutter_webview_plugin (from `Pods/.symlinks/plugins/flutter_webview_plugin/ios`)
16 | - path_provider (from `Pods/.symlinks/plugins/path_provider/ios`)
17 | - shared_preferences (from `Pods/.symlinks/plugins/shared_preferences/ios`)
18 |
19 | EXTERNAL SOURCES:
20 | Flutter:
21 | :path: Pods/.symlinks/flutter/ios
22 | flutter_web_view:
23 | :path: Pods/.symlinks/plugins/flutter_web_view/ios
24 | flutter_webview_plugin:
25 | :path: Pods/.symlinks/plugins/flutter_webview_plugin/ios
26 | path_provider:
27 | :path: Pods/.symlinks/plugins/path_provider/ios
28 | shared_preferences:
29 | :path: Pods/.symlinks/plugins/shared_preferences/ios
30 |
31 | SPEC CHECKSUMS:
32 | Flutter: 9d0fac939486c9aba2809b7982dfdbb47a7b0296
33 | flutter_web_view: 6172ec2a6b4c7a33d840300a74354b913416782e
34 | flutter_webview_plugin: 116575b48572029304775b768e9f15ebfc316274
35 | path_provider: 09407919825bfe3c2deae39453b7a5b44f467873
36 | shared_preferences: 5a1d487c427ee18fcd3ea1f2a131569481834b53
37 |
38 | PODFILE CHECKSUM: 13dcf421f4da2e937a57e8ba760ed880beae536f
39 |
40 | COCOAPODS: 1.4.0
41 |
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
33 |
34 |
40 |
41 |
42 |
43 |
44 |
45 |
56 |
58 |
64 |
65 |
66 |
67 |
68 |
69 |
75 |
77 |
83 |
84 |
85 |
86 |
88 |
89 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner/AppDelegate.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | @interface AppDelegate : FlutterAppDelegate
5 |
6 | @end
7 |
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner/AppDelegate.m:
--------------------------------------------------------------------------------
1 | #include "AppDelegate.h"
2 | #include "GeneratedPluginRegistrant.h"
3 |
4 | @implementation AppDelegate
5 |
6 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
7 | [GeneratedPluginRegistrant registerWithRegistry:self];
8 | // Override point for customization after application launch.
9 | return [super application:application didFinishLaunchingWithOptions:launchOptions];
10 | }
11 |
12 | @end
13 |
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images": [
3 | {
4 | "size": "20x20",
5 | "idiom": "iphone",
6 | "filename": "icon-20@2x.png",
7 | "scale": "2x"
8 | },
9 | {
10 | "size": "20x20",
11 | "idiom": "iphone",
12 | "filename": "icon-20@3x.png",
13 | "scale": "3x"
14 | },
15 | {
16 | "size": "29x29",
17 | "idiom": "iphone",
18 | "filename": "icon-29.png",
19 | "scale": "1x"
20 | },
21 | {
22 | "size": "29x29",
23 | "idiom": "iphone",
24 | "filename": "icon-29@2x.png",
25 | "scale": "2x"
26 | },
27 | {
28 | "size": "29x29",
29 | "idiom": "iphone",
30 | "filename": "icon-29@3x.png",
31 | "scale": "3x"
32 | },
33 | {
34 | "size": "40x40",
35 | "idiom": "iphone",
36 | "filename": "icon-40@2x.png",
37 | "scale": "2x"
38 | },
39 | {
40 | "size": "40x40",
41 | "idiom": "iphone",
42 | "filename": "icon-40@3x.png",
43 | "scale": "3x"
44 | },
45 | {
46 | "size": "57x57",
47 | "idiom": "iphone",
48 | "filename": "icon-57.png",
49 | "scale": "1x"
50 | },
51 | {
52 | "size": "57x57",
53 | "idiom": "iphone",
54 | "filename": "icon-57@2x.png",
55 | "scale": "2x"
56 | },
57 | {
58 | "size": "60x60",
59 | "idiom": "iphone",
60 | "filename": "icon-60@2x.png",
61 | "scale": "2x"
62 | },
63 | {
64 | "size": "60x60",
65 | "idiom": "iphone",
66 | "filename": "icon-60@3x.png",
67 | "scale": "3x"
68 | },
69 | {
70 | "size": "20x20",
71 | "idiom": "ipad",
72 | "filename": "icon-20-ipad.png",
73 | "scale": "1x"
74 | },
75 | {
76 | "size": "20x20",
77 | "idiom": "ipad",
78 | "filename": "icon-20@2x-ipad.png",
79 | "scale": "2x"
80 | },
81 | {
82 | "size": "29x29",
83 | "idiom": "ipad",
84 | "filename": "icon-29-ipad.png",
85 | "scale": "1x"
86 | },
87 | {
88 | "size": "29x29",
89 | "idiom": "ipad",
90 | "filename": "icon-29@2x-ipad.png",
91 | "scale": "2x"
92 | },
93 | {
94 | "size": "40x40",
95 | "idiom": "ipad",
96 | "filename": "icon-40.png",
97 | "scale": "1x"
98 | },
99 | {
100 | "size": "40x40",
101 | "idiom": "ipad",
102 | "filename": "icon-40@2x.png",
103 | "scale": "2x"
104 | },
105 | {
106 | "size": "50x50",
107 | "idiom": "ipad",
108 | "filename": "icon-50.png",
109 | "scale": "1x"
110 | },
111 | {
112 | "size": "50x50",
113 | "idiom": "ipad",
114 | "filename": "icon-50@2x.png",
115 | "scale": "2x"
116 | },
117 | {
118 | "size": "72x72",
119 | "idiom": "ipad",
120 | "filename": "icon-72.png",
121 | "scale": "1x"
122 | },
123 | {
124 | "size": "72x72",
125 | "idiom": "ipad",
126 | "filename": "icon-72@2x.png",
127 | "scale": "2x"
128 | },
129 | {
130 | "size": "76x76",
131 | "idiom": "ipad",
132 | "filename": "icon-76.png",
133 | "scale": "1x"
134 | },
135 | {
136 | "size": "76x76",
137 | "idiom": "ipad",
138 | "filename": "icon-76@2x.png",
139 | "scale": "2x"
140 | },
141 | {
142 | "size": "83.5x83.5",
143 | "idiom": "ipad",
144 | "filename": "icon-83.5@2x.png",
145 | "scale": "2x"
146 | },
147 | {
148 | "size": "1024x1024",
149 | "idiom": "ios-marketing",
150 | "filename": "icon-1024.png",
151 | "scale": "1x"
152 | }
153 | ],
154 | "info": {
155 | "version": 1,
156 | "author": "icon.wuruihong.com"
157 | }
158 | }
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024.png
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20-ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20-ipad.png
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x-ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x-ipad.png
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29-ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29-ipad.png
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29.png
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x-ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x-ipad.png
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40.png
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-50.png
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-50@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-50@2x.png
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-57.png
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-57@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-57@2x.png
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-72.png
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-72@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-72@2x.png
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76.png
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png
--------------------------------------------------------------------------------
/flutter_wanandroid/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 |
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/flutter_wanandroid/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/flutter_wanandroid/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.
--------------------------------------------------------------------------------
/flutter_wanandroid/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 |
--------------------------------------------------------------------------------
/flutter_wanandroid/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 |
--------------------------------------------------------------------------------
/flutter_wanandroid/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_wanandroid
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UIRequiredDeviceCapabilities
30 |
31 | arm64
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 | UISupportedInterfaceOrientations~ipad
40 |
41 | UIInterfaceOrientationPortrait
42 | UIInterfaceOrientationPortraitUpsideDown
43 | UIInterfaceOrientationLandscapeLeft
44 | UIInterfaceOrientationLandscapeRight
45 |
46 | UIViewControllerBasedStatusBarAppearance
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/flutter_wanandroid/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 |
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/adapter/base_adapter.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | abstract class BaseAdapter{
4 | Widget getItemView(BuildContext context,item);
5 | }
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/adapter/feed_item_adapter.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import '../adapter/base_adapter.dart';
3 | import '../home/web_view_page.dart';
4 |
5 | class FeedItemAdapter extends BaseAdapter{
6 |
7 | @override
8 | Widget getItemView(BuildContext context, item) {
9 |
10 | Color primaryColor = Theme.of(context).primaryColor;
11 |
12 | List spanList = [];
13 |
14 | ///Article Item 中的'项目'标志
15 | if (item['tags'].length > 0) {
16 | String name = item['tags'][0]['name'];
17 | spanList.add(
18 | new TextSpan(text: name, style: new TextStyle(color: primaryColor)));
19 | }
20 |
21 | spanList.addAll([
22 | new TextSpan(text: ' 作者:${item['author']} '),
23 | new TextSpan(
24 | text: '分类:${item['superChapterName']}/${item['chapterName']} ',
25 | style: new TextStyle(color: primaryColor)),
26 | new TextSpan(
27 | text: '时间:${item['niceDate']}',
28 | style: new TextStyle(color: Colors.grey)),
29 | ]);
30 |
31 | Widget itemAuthors = new Text.rich(new TextSpan(children: spanList));
32 |
33 | ///TODO 这里请求时若添加cookie,返回值这个'collect'应该就不一样
34 | Widget itemCollect = new IconButton(
35 | icon: item['collect']
36 | ? const Icon(Icons.favorite)
37 | : const Icon(Icons.favorite_border),
38 | onPressed: () {
39 | print('这里这里 ${item['title']}');
40 | });
41 |
42 | Widget itemTitle =
43 | new Text(item['title'], style: new TextStyle(fontSize: 18.0));
44 |
45 | return new Material(
46 | child: new Column(
47 | children: [
48 | new InkWell(
49 | onTap: () {
50 | Navigator
51 | .of(context)
52 | .push(new MaterialPageRoute(builder: (context) {
53 | return new WebViewPage(
54 | key: new Key(item['title']),
55 | title: item['title'],
56 | url: item['link']);
57 | }));
58 | },
59 | child: new Padding(
60 | padding: const EdgeInsets.only(top: 4.0, bottom: 4.0),
61 | child: new Row(
62 | crossAxisAlignment: CrossAxisAlignment.center,
63 | children: [
64 | new Expanded(
65 | child: new Column(
66 | mainAxisAlignment: MainAxisAlignment.start,
67 | children: [
68 | new Row(
69 | mainAxisAlignment: MainAxisAlignment.center,
70 | crossAxisAlignment: CrossAxisAlignment.center,
71 | children: [
72 | new SizedBox(
73 | width: 8.0,
74 | ),
75 | new Expanded(child: itemTitle),
76 | ],
77 | ),
78 | new Row(
79 | mainAxisAlignment: MainAxisAlignment.start,
80 | crossAxisAlignment: CrossAxisAlignment.center,
81 | children: [
82 | new SizedBox(
83 | width: 8.0,
84 | ),
85 | new Expanded(child: itemAuthors),
86 | ],
87 | ),
88 | ],
89 | ),
90 | ),
91 | new SizedBox(
92 | width: 4.0,
93 | ),
94 | itemCollect,
95 | new SizedBox(
96 | width: 8.0,
97 | ),
98 | ],
99 | ),
100 | )),
101 | new Divider(
102 | height: 1.0,
103 | ),
104 | ],
105 | ),
106 | );
107 | }
108 |
109 |
110 | }
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/common/application.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | ///保存常量
4 | const int timeOut = 5000;
5 |
6 | class Application {
7 | static Widget progressWidget = new Center(
8 | child: new CircularProgressIndicator(),
9 | );
10 |
11 | static Widget loadMoreWidget = new Row(
12 | mainAxisAlignment: MainAxisAlignment.center,
13 | crossAxisAlignment: CrossAxisAlignment.center,
14 | children: [
15 | new CircularProgressIndicator(value: 8.0,),
16 | new Text('加载中...',style: new TextStyle(fontSize: 16.0),)
17 | ],
18 | );
19 |
20 | static Widget noMoreWidget = new Center(
21 | child: new Text('没有更多了:(',style: new TextStyle(fontSize: 16.0),),
22 | );
23 |
24 | static Widget getLoadMoreFailedWidget({VoidCallback onPressed}) {
25 | return new Row(
26 | mainAxisAlignment: MainAxisAlignment.center,
27 | crossAxisAlignment: CrossAxisAlignment.center,
28 | children: [
29 | new Icon(Icons.sms_failed),
30 | new FlatButton(onPressed: onPressed, child: new Text('加载失败,点击重试',style: new TextStyle(fontSize: 16.0),))
31 | ],
32 | );
33 | }
34 |
35 | static Widget getReloadWidget({VoidCallback onPressed}) {
36 | return new Center(
37 | child: new FlatButton(
38 | onPressed: onPressed,
39 | child: new Text(
40 | '请求数据出错,请点击重试.',
41 | style: new TextStyle(
42 | fontSize: 16.0,
43 | fontStyle: FontStyle.italic,
44 | color: Colors.blue),
45 | )),
46 | );
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/common/config.dart:
--------------------------------------------------------------------------------
1 |
2 | ///将需要引入的基本配置全部声明在该文件中,然后直接引入该文件
3 | ///使用export导出一个更大的库
4 |
5 | export 'package:flutter/material.dart';
6 | export 'dart:convert';
7 | export 'dart:async';
8 | export 'package:meta/meta.dart';
9 |
10 | export '../utils/snackbar_util.dart';
11 | export '../utils/dialog_util.dart';
12 | export '../utils/log.dart';
13 | export '../utils/http_util.dart';
14 | export '../utils/shares_util.dart';
15 | export '../common/wanandroid_api.dart';
16 | export '../common/application.dart';
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/common/wanandroid_api.dart:
--------------------------------------------------------------------------------
1 |
2 |
3 | ///WanAndroid API常量
4 |
5 | ///post 登录
6 | const login_api='http://www.wanandroid.com/user/login';
7 |
8 | ///post 注册
9 | const register_api='http://www.wanandroid.com/user/register';
10 |
11 | ///get 首页 banner api
12 | const banner_api='http://www.wanandroid.com/banner/json';
13 |
14 | ///get 首页文章列表(网站中首页的最新博文),param 是页码,页码从0开始,页码拼接
15 | const home_articles_api='http://www.wanandroid.com/article/list/{param}/json';
16 |
17 | ///get 搜索热词(大家都在搜)
18 | const search_hotkey_api='http://www.wanandroid.com/hotkey/json';
19 |
20 | ///post 根据关键字搜索,搜索关键字以空格隔开,多个关键字,将空格换成加号
21 | const search_api='http://www.wanandroid.com/article/query/{param}/json';
22 |
23 | ///get 体系数据
24 | const tree_api='http://www.wanandroid.com/tree/json';
25 |
26 | ///get 体系下的文章API
27 | const tree_article_api='http://www.wanandroid.com/article/list/{param}/json?cid={param}';
28 |
29 | const navi_api='http://www.wanandroid.com/navi/json';
30 |
31 |
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/home/drawer_page.dart:
--------------------------------------------------------------------------------
1 | import '../common/config.dart';
2 | import '../startup/login_page.dart';
3 | import '../notifications/notifications.dart';
4 |
5 | ///DrawerHeader
6 | class DrawerHeaderPage extends StatefulWidget {
7 | const DrawerHeaderPage({Key key}) : super(key: key);
8 |
9 | @override
10 | _DrawerHeaderState createState() => new _DrawerHeaderState();
11 | }
12 |
13 | class _DrawerHeaderState extends State {
14 | ///是否登录
15 | bool isLogin = false;
16 | String username;
17 |
18 | @override
19 | void initState() {
20 | super.initState();
21 |
22 | ///每次打开Drawer都会调用这个方法
23 | print('drawer Page initState');
24 |
25 | ///去获取cookie值
26 | getCookie().then((cookie) {
27 | if (cookie != null && cookie.isNotEmpty) {
28 |
29 | print('cookie $cookie');
30 | ///这里解析Cookie
31 | ///loginUserName=JakeyYe; Expires=Wed, 13-Jun-2018 09:13:39 GMT; Path=/
32 | ///loginUserPassword=ye829225; Expires=Wed, 13-Jun-2018 09:13:39 GMT; Path=/
33 | if (cookie.contains('loginUserName=')) {
34 | List list = cookie.split(';');
35 | for (String s in list) {
36 | if (s.contains('loginUserName=')) {
37 | String loginUserName = s.substring('loginUserName'.length + 1);
38 | setState(() {
39 | isLogin = true;
40 | username = loginUserName;
41 | });
42 | break;
43 | }
44 | }
45 | }
46 | }
47 | });
48 | }
49 |
50 | Widget getDrawerHeaderWidgets() {
51 | //可以使用通知来刷新这个
52 |
53 | Widget widget;
54 |
55 | ///登录状态
56 | if (isLogin) {
57 | widget = new Stack(
58 | children: [
59 | new Container(
60 | child: new Row(
61 | mainAxisAlignment: MainAxisAlignment.center,
62 | crossAxisAlignment: CrossAxisAlignment.center,
63 | children: [
64 |
65 | ///圆形Image
66 | new SizedBox(
67 |
68 | height: 64.0,
69 | width: 64.0,
70 | child: new DecoratedBox(
71 | decoration: new BoxDecoration(
72 | border: new Border.all(
73 | width: 1.0, color: const Color(0xFFFFFFFF)),
74 | shape: BoxShape.circle,
75 | // borderRadius: new BorderRadius.circular(6.0),
76 | image: new DecorationImage(
77 | image: new AssetImage(
78 | 'assets/logo.png',
79 | ),
80 | fit: BoxFit.contain,
81 | alignment: Alignment.center,
82 | ),
83 | ),
84 | ),
85 | ),
86 |
87 | ///圆形图片+用户名(登录状态)
88 | const SizedBox(
89 | width: 12.0,
90 | ),
91 |
92 | ///用户名
93 | Text('JakeyYe')
94 | ],
95 | ),
96 | ),
97 |
98 | ///右下角加一个点击弹出选择框,一项操作'退出登录'
99 | new PositionedDirectional(
100 | bottom: 12.0,
101 | end: 12.0,
102 | child: new GestureDetector(
103 | child: new Icon(Icons.exit_to_app),
104 | onTap: () {
105 | ///弹出弹窗提示用户是否确认退出登录
106 | showAlertDialog(context, '退出登录?').then((value) {
107 | if (value == true) {
108 | ///退出登录操作
109 | setState(() {
110 | isLogin = false;
111 | });
112 | }
113 | });
114 | },
115 | ),
116 | ),
117 | ],
118 | );
119 | } else {
120 | ///非登录状态
121 | widget = new Container(
122 | child: new Row(
123 | mainAxisAlignment: MainAxisAlignment.center,
124 | crossAxisAlignment: CrossAxisAlignment.center,
125 | children: [
126 | ///未登录状态的圆形Image,前景加一层灰色
127 | new SizedBox(
128 | ///使用SizedBox来控制大小
129 | height: 64.0,
130 | width: 64.0,
131 | child: new DecoratedBox(
132 | // child: new Material(color: Colors.grey[400]),
133 | decoration: new BoxDecoration(
134 | border: new Border.all(
135 | width: 1.0, color: const Color(0xFFFFFFFF)),
136 | shape: BoxShape.circle,
137 | // borderRadius: new BorderRadius.circular(6.0),
138 | image: new DecorationImage(
139 | image: new AssetImage(
140 | 'assets/logo.png',
141 | ),
142 | fit: BoxFit.contain,
143 | alignment: Alignment.center,
144 | ),
145 | ),
146 | ),
147 |
148 | ///这个装饰是作为背景的,当然也可以设置为前景
149 | ),
150 |
151 | ///圆形图片+用户名(登录状态)
152 | const SizedBox(
153 | width: 12.0,
154 | ),
155 | FlatButton(
156 | child: Text(
157 | '去登陆',
158 | style: new TextStyle(color: Colors.blue, fontSize: 16.0),
159 | ),
160 | onPressed: () {
161 | Navigator.push(context,
162 | new MaterialPageRoute(builder: (context) {
163 | return new LoginPage();
164 | }));
165 | },
166 | )
167 | ],
168 | ),
169 | );
170 | }
171 | return widget;
172 | }
173 |
174 | @override
175 | Widget build(BuildContext context) {
176 | print('drawer page build');
177 |
178 | ///DrawerHeader有两种状态,登录状态和未登录状态
179 |
180 | ///NotificationListener通知
181 | return new NotificationListener(
182 | onNotification: (notification){
183 | isLogin=true;
184 | },
185 | child: new DrawerHeader(
186 | //这个margin是
187 | margin: EdgeInsets.only(top: 0.0, bottom: 12.0),
188 |
189 | ///背景图片
190 | // decoration: new BoxDecoration(color: Colors.amberAccent),
191 | decoration: BoxDecoration(
192 | image: new DecorationImage(
193 | fit: BoxFit.fill,
194 | image: new AssetImage('assets/drawer_header_bg.jpg'))),
195 | duration: const Duration(milliseconds: 750),
196 | child: getDrawerHeaderWidgets(),
197 | ));
198 | }
199 | }
200 |
201 | ///Drawer,由两部分组成,顶部用户信息和下面的条目
202 | class DrawerPage extends StatefulWidget {
203 | final GlobalKey scaffoldKey;
204 |
205 | DrawerPage({@required this.scaffoldKey});
206 |
207 | @override
208 | _DrawerPageState createState() => new _DrawerPageState();
209 | }
210 |
211 | class _DrawerPageState extends State {
212 | static const List _drawerContents = const ['收藏', '设置', '关于'];
213 |
214 | @override
215 | Widget build(BuildContext context) {
216 | final List allDrawerItems = [
217 | new DrawerHeaderPage(),
218 |
219 | ///要使用这个MediaQuery.removePadding()才可以将Drawer的布局绘制到statusBar上
220 | new MediaQuery.removePadding(
221 | context: context,
222 | removeTop: true,
223 | child: new Expanded(
224 | child: new ListView(
225 | children: [_getDrawerItems()],
226 | )))
227 | ];
228 |
229 | ///这里直接使用Drawer+ListView
230 | return new Drawer(
231 | child: new Column(
232 | children: allDrawerItems,
233 | ));
234 | }
235 |
236 | void _showNotImplementedMessage(String item) {
237 | Navigator.pop(context); // Dismiss the drawer.
238 | widget.scaffoldKey.currentState.showSnackBar(new SnackBar(
239 | content: new Text('Drawer item: $item')));
240 | }
241 |
242 | Widget _buildListTile(String title, Widget icon, GestureTapCallback onTap) {
243 | return new ListTile(
244 | leading: new CircleAvatar(child: icon),
245 | title: new Text(title),
246 | onTap: onTap,
247 | );
248 | }
249 |
250 | _getDrawerItems() {
251 | return new Column(
252 | mainAxisSize: MainAxisSize.min,
253 | crossAxisAlignment: CrossAxisAlignment.stretch,
254 | children: [
255 | new Divider(
256 | height: 2.0,
257 | ),
258 | _buildListTile(_drawerContents[0], new Icon(Icons.favorite), () {
259 | _showNotImplementedMessage('收藏');
260 | }),
261 | new Divider(
262 | height: 2.0,
263 | ),
264 | _buildListTile(_drawerContents[1], new Icon(Icons.settings), () {
265 | _showNotImplementedMessage('设置');
266 | }),
267 | new Divider(
268 | height: 2.0,
269 | ),
270 | _buildListTile(_drawerContents[2], new Icon(Icons.description), () {
271 | _showNotImplementedMessage('关于');
272 | }),
273 | ],
274 | );
275 | }
276 | }
277 |
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/home/index_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:cached_network_image/cached_network_image.dart';
2 | import '../widgets/pull_to_refresh/pull_to_refresh.dart';
3 |
4 | import '../common/config.dart';
5 | import '../home/web_view_page.dart';
6 | import '../widgets/xbanner.dart';
7 | import '../adapter/feed_item_adapter.dart';
8 |
9 | class Banner extends StatefulWidget {
10 | Banner({@required Key key}) : super(key: key);
11 |
12 | @override
13 | _BannerState createState() => new _BannerState();
14 | }
15 |
16 | class _BannerState extends State {
17 | ///有没有点击重新加载的第三种情况???
18 | bool loading = true;
19 | bool isReload = false;
20 | List data;
21 |
22 | /// "desc":"",
23 | /// "id":4,
24 | /// "imagePath":"http://www.wanandroid.com/blogimgs/ab17e8f9-6b79-450b-8079-0f2287eb6f0f.png",
25 | /// "isVisible":1,
26 | /// "order":0,
27 | /// "title":"看看别人的面经,搞定面试~",
28 | /// "type":1,
29 | /// "url":"http://www.wanandroid.com/article/list/0?cid=73"
30 |
31 | @override
32 | void initState() {
33 | super.initState();
34 | print('Banner initState');
35 | _loadDataBanner();
36 | }
37 |
38 | _loadDataBanner() async {
39 | print('_loadData');
40 | await HttpUtil.get(banner_api).then((dataModel) {
41 | if (!mounted) return;
42 |
43 | ///这个errorCode只有两种情况
44 | if (dataModel.errorCode == 0) {
45 | data = dataModel.data;
46 | setState(() {
47 | loading = false;
48 | isReload = false;
49 | });
50 | } else {
51 | setState(() {
52 | loading = false;
53 | isReload = true;
54 | });
55 | }
56 | });
57 | }
58 |
59 | _buildBanner() {
60 | return new XBanner(
61 | data.map((obj) {
62 | ///TODO 这里有缺陷,还需要更改???
63 | return new DecoratedBox(
64 | decoration: new BoxDecoration(
65 | image: new DecorationImage(
66 | ///对齐方式,如果不指定,默认就是Center方式
67 | alignment: Alignment.topLeft,
68 |
69 | ///这个才是按原来的比例缩放展示
70 | fit: BoxFit.fill,
71 | image: new CachedNetworkImageProvider(obj['imagePath']),
72 | ),
73 | ),
74 | child: new Container(
75 | ///这里设置宽度无效
76 | margin: EdgeInsets.only(bottom: 16.0),
77 |
78 | child: new Align(
79 | child: new SizedBox(
80 | height: 36.0,
81 | width: double.infinity,
82 | child: new DecoratedBox(
83 | decoration: new BoxDecoration(color: Colors.black45),
84 | child: new Align(
85 | child: new Padding(
86 | padding: const EdgeInsets.only(left: 8.0),
87 | child: new Text(
88 | obj['title'],
89 | style: new TextStyle(color: Colors.white),
90 | ),
91 | ),
92 | alignment: Alignment.centerLeft,
93 | ),
94 | ),
95 | ),
96 | alignment: Alignment.bottomLeft,
97 | ),
98 | ));
99 | }).toList(),
100 | pageClick: (i) {
101 | print('click $i ${data[i]['title']} ${data[i]['url']}');
102 |
103 | ///TODO 这里跳转也会重建
104 | Navigator.of(context).push(new MaterialPageRoute(builder: (context) {
105 | return new WebViewPage(
106 | key: new Key('WebViewPage'),
107 | title: data[i]['title'],
108 | url: data[i]['url']);
109 | }));
110 | },
111 | );
112 | }
113 |
114 | _buildBannerBody() {
115 | Widget body;
116 | if (loading == true) {
117 | body = Application.progressWidget;
118 | } else {
119 | ///获取数据出错,需要重新加载
120 | if (isReload) {
121 | body = Application.getReloadWidget(onPressed: () {
122 | if (!mounted) return;
123 |
124 | setState(() {
125 | loading = true;
126 | isReload = false;
127 | });
128 | _loadDataBanner();
129 | });
130 | } else {
131 | body = _buildBanner();
132 | }
133 | }
134 | return body;
135 | }
136 |
137 | @override
138 | Widget build(BuildContext context) {
139 | print('Banner build');
140 |
141 | ///这个高度可以控制Banner的高度
142 | return new SizedBox(
143 | height: 200.0,
144 | child: _buildBannerBody(),
145 | );
146 | }
147 | }
148 |
149 | class ArticleList extends StatefulWidget {
150 | ArticleList({@required Key key}) : super(key: key);
151 |
152 | @override
153 | _ArticleListState createState() => new _ArticleListState();
154 | }
155 |
156 | class _ArticleListState extends State {
157 | bool loading = true;
158 | bool isReload = false;
159 | List data;
160 |
161 | ///是否还有更多
162 | bool hasMore = true;
163 |
164 | ///Banner
165 | bool loadingBanner = true;
166 | bool isReloadBanner = false;
167 | List bannerData;
168 |
169 | ///当前页数
170 | int curPage = 0;
171 | int curPageSize = 0;
172 |
173 | RefreshController _refreshController;
174 |
175 | FeedItemAdapter _feedItemAdapter;
176 |
177 | /// "apkLink":"",
178 | /// "author":"HIT-Alibaba",
179 | /// "chapterId":73,
180 | /// "chapterName":"面试相关",
181 | /// "collect":false,
182 | /// "courseId":13,
183 | /// "desc":"",
184 | /// "envelopePic":"",
185 | /// "fresh":true,
186 | /// "id":2945,
187 | /// "link":"https://hit-alibaba.github.io/interview/basic/network/HTTP.html",
188 | /// "niceDate":"1天前",
189 | /// "origin":"",
190 | /// "projectLink":"",
191 | /// "publishTime":1526988545000,
192 | /// "superChapterId":61,
193 | /// "superChapterName":"热门专题",
194 | /// "tags":[
195 | ///
196 | /// ],
197 | /// "title":"笔试面试知识整理",
198 | /// "type":0,
199 | /// "userId":-1,
200 | /// "visible":1,
201 | /// "zan":0
202 |
203 | _loadDataBanner() async {
204 | print('_loadDataBanner');
205 |
206 | await HttpUtil.get(banner_api).then((dataModel) {
207 | if (!mounted) return;
208 |
209 | ///这个errorCode只有两种情况
210 | if (dataModel.errorCode == 0) {
211 | bannerData = dataModel.data;
212 | setState(() {
213 | loadingBanner = false;
214 | isReloadBanner = false;
215 | });
216 | } else {
217 | setState(() {
218 | loadingBanner = false;
219 | isReloadBanner = true;
220 | });
221 | }
222 | });
223 | }
224 |
225 | _buildBanner() {
226 | print('_buildBanner');
227 |
228 | return new XBanner(
229 | bannerData.map((obj) {
230 | return new DecoratedBox(
231 | decoration: new BoxDecoration(
232 | image: new DecorationImage(
233 | ///对齐方式,如果不指定,默认就是Center方式
234 | alignment: Alignment.topLeft,
235 |
236 | ///这个才是按原来的比例缩放展示
237 | fit: BoxFit.fill,
238 | image: new CachedNetworkImageProvider(obj['imagePath']),
239 | ),
240 | ),
241 | child: new Container(
242 | margin: EdgeInsets.only(bottom: 16.0),
243 | child: new Align(
244 | child: new SizedBox(
245 | height: 36.0,
246 | width: double.infinity,
247 | child: new DecoratedBox(
248 | decoration: new BoxDecoration(color: Colors.black45),
249 | child: new Align(
250 | child: new Padding(
251 | padding: const EdgeInsets.only(left: 8.0),
252 | child: new Text(
253 | obj['title'],
254 | style: new TextStyle(color: Colors.white),
255 | ),
256 | ),
257 | alignment: Alignment.centerLeft,
258 | ),
259 | ),
260 | ),
261 | alignment: Alignment.bottomLeft,
262 | ),
263 | ));
264 | }).toList(),
265 | pageClick: (i) {
266 | print('click $i ${bannerData[i]['title']} ${bannerData[i]['url']}');
267 | Navigator.of(context).push(new MaterialPageRoute(builder: (context) {
268 | return new WebViewPage(
269 |
270 | ///TODO 这样是不是不会重新创建同一个页面了
271 | key: new Key(bannerData[i]['title']),
272 | title: bannerData[i]['title'],
273 | url: bannerData[i]['url']);
274 | }));
275 | },
276 | );
277 | }
278 |
279 | _buildBannerBody() {
280 | print('_buildBannerBody');
281 |
282 | Widget body;
283 | if (loadingBanner == true) {
284 | body = Application.progressWidget;
285 | } else {
286 | ///获取数据出错,需要重新加载
287 | if (isReloadBanner) {
288 | body = Application.getReloadWidget(onPressed: () {
289 | if (!mounted) return;
290 |
291 | setState(() {
292 | loadingBanner = true;
293 | isReloadBanner = false;
294 | });
295 | _loadDataBanner();
296 | });
297 | } else {
298 | body = _buildBanner();
299 | }
300 | }
301 | return body;
302 | }
303 |
304 | @override
305 | void initState() {
306 | super.initState();
307 | _refreshController = new RefreshController();
308 | _feedItemAdapter = new FeedItemAdapter();
309 |
310 | print('IndexPage initState');
311 |
312 | ///Banner data
313 | _loadDataBanner();
314 |
315 | ///ArticleList data
316 | _loadData();
317 | }
318 |
319 | _loadData() async {
320 | print('_loadData');
321 |
322 | await HttpUtil.get(home_articles_api, [curPage]).then((dataModel) {
323 | if (!mounted) return;
324 |
325 | ///这个errorCode只有两种情况
326 | if (dataModel.errorCode == 0) {
327 | curPage++;
328 |
329 | ///判断是否还有更多数据
330 | if (dataModel.data['curPage'] < dataModel.data['pageCount']) {
331 | hasMore = true;
332 | } else {
333 | hasMore = false;
334 | }
335 |
336 | if (data != null) {
337 | data.addAll(dataModel.data['datas']);
338 | curPageSize += dataModel.data['size'];
339 |
340 | _refreshController.sendBack(false, RefreshStatus.idle);
341 | } else {
342 | data = dataModel.data['datas'];
343 | curPageSize = dataModel.data['size'];
344 | }
345 |
346 | setState(() {
347 | loading = false;
348 | isReload = false;
349 | });
350 | } else {
351 | if (data != null) {
352 | _refreshController.sendBack(false, RefreshStatus.failed);
353 | } else {
354 | setState(() {
355 | loading = false;
356 | isReload = true;
357 | });
358 | }
359 | }
360 | });
361 | }
362 |
363 | _buildBody() {
364 | print('_buildBody');
365 |
366 | Widget body;
367 | if (loading == true) {
368 | body = Application.progressWidget;
369 | } else {
370 | ///获取数据出错,需要重新加载
371 | if (isReload) {
372 | body = Application.getReloadWidget(onPressed: () {
373 | if (!mounted) return;
374 |
375 | setState(() {
376 | loading = true;
377 | isReload = false;
378 | });
379 | _loadData();
380 | });
381 | } else {
382 | ///TODO 这里ListView会在一开始将所有Item都加载出来
383 | body = new SmartRefresher(
384 | enablePullUp: true,
385 | enablePullDown: false,
386 | controller: _refreshController,
387 | onRefresh: _onRefresh,
388 | footerBuilder: _footerCreate,
389 | child: new ListView.builder(
390 | physics: const NeverScrollableScrollPhysics(),
391 | shrinkWrap: true,
392 | itemCount: curPageSize + 1,
393 | itemBuilder: (context, index) {
394 | print('index $index');
395 |
396 | if (index == 0) {
397 | ///这里没直接使用new Banner()是有原因的,如果使用new Banner()就会在Banner
398 | ///消失再出现时返回创建Banner,Banner的流程都会走一遍
399 | // return new Banner();
400 | ///这个高度可以控制Banner的高度
401 | return new SizedBox(
402 | height: 200.0,
403 | child: _buildBannerBody(),
404 | );
405 | } else {
406 | return _feedItemAdapter.getItemView(
407 | context, data[index - 1]);
408 | }
409 | }));
410 | }
411 | }
412 | return body;
413 | }
414 |
415 | Widget _footerCreate(BuildContext context, int mode) {
416 | return new ClassicIndicator(
417 | mode: mode,
418 | refreshingText: 'loading...',
419 | idleIcon: const Icon(Icons.arrow_upward),
420 | idleText: '上拉加载更多...',
421 | );
422 | }
423 |
424 | ///无论顶部还是底部的指示器,当进入刷新状态,onRefresh都会被回调
425 | _onRefresh(bool up) {
426 | if (!up) {
427 | if (hasMore) {
428 | _loadData();
429 | }else{
430 | _refreshController.sendBack(false, RefreshStatus.noMore);
431 | }
432 | }
433 | }
434 |
435 | @override
436 | Widget build(BuildContext context) {
437 | print('Article build');
438 | return _buildBody();
439 | }
440 | }
441 |
442 | class IndexPage extends StatefulWidget {
443 | final GlobalKey scaffoldKey;
444 |
445 | IndexPage({@required Key key, @required this.scaffoldKey}) : super(key: key);
446 |
447 | @override
448 | _IndexPageState createState() => new _IndexPageState();
449 | }
450 |
451 | class _IndexPageState extends State {
452 | @override
453 | Widget build(BuildContext context) {
454 | return new Container(
455 | decoration: new BoxDecoration(
456 | color: Colors.grey[100],
457 | ),
458 | child: new ArticleList(
459 | key: new Key('IndexPage'),
460 | ),
461 | );
462 | }
463 | }
464 |
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/home/main_screen.dart:
--------------------------------------------------------------------------------
1 | import '../common/config.dart';
2 | import '../home/drawer_page.dart';
3 | import '../home/index_page.dart';
4 | import '../home/navi_page.dart';
5 | import '../home/project_page.dart';
6 | import '../home/tree/tree_page.dart';
7 |
8 | enum _NavigationItemName { indexs, tree, navi, project }
9 |
10 | class NavigationIconView {
11 | NavigationIconView({
12 | Widget icon,
13 | String title,
14 | Color color,
15 | _NavigationItemName name,
16 | }):
17 | _name = name,
18 | item = new BottomNavigationBarItem(
19 |
20 | ///BottomNavigationBarItem,底部导航栏的Item
21 | icon: icon,
22 | title: new Text(title),
23 | backgroundColor: color,
24 | );
25 |
26 | final BottomNavigationBarItem item;
27 | _NavigationItemName _name;
28 | Widget page;
29 |
30 | Widget build(BuildContext context, GlobalKey scaffoldKey) {
31 | if (page != null) {
32 | return page;
33 | }
34 | switch (_name) {
35 | case _NavigationItemName.indexs:
36 | page = new IndexPage(
37 | scaffoldKey: scaffoldKey,
38 | key: new Key(_name.toString()),
39 | );
40 | break;
41 | case _NavigationItemName.tree:
42 | page = new TreePage(
43 | scaffoldKey: scaffoldKey,
44 | key: new Key(_name.toString()),
45 | );
46 | break;
47 | case _NavigationItemName.navi:
48 | page = new NaviPage(
49 | scaffoldKey: scaffoldKey,
50 | key: new Key(_name.toString()),
51 | );
52 | break;
53 | case _NavigationItemName.project:
54 | page = new ProjectPage(
55 | key: new Key(_name.toString()),
56 | scaffoldKey: scaffoldKey);
57 | break;
58 | default:
59 | //error
60 | print('error $_name');
61 | }
62 | return page;
63 | }
64 | }
65 |
66 | ///主界面
67 | class MainScreen extends StatefulWidget {
68 | @override
69 | _MainScreenState createState() => new _MainScreenState();
70 | }
71 |
72 | ///TODO AutomaticKeepAliveClientMixin 对IndexPage跳转WebViewPage会调用IndexPage的build方法没效果
73 | class _MainScreenState extends State{
74 |
75 | int _currentIndex = 0;
76 |
77 | ///底部导航栏的类型
78 | BottomNavigationBarType _type = BottomNavigationBarType.fixed;
79 |
80 | ///导航元素
81 | List _navigationViews;
82 |
83 | ///对应导航元素的页面
84 | List pages = [];
85 |
86 | List _titleList = ['首页', '体系', '导航', '项目'];
87 |
88 | ///GlobalKey 全局Key,使用全局Key来唯一标志widget
89 | ///可以通过GlobalKey.currentState获取当前key对应的Widget的状态
90 | final GlobalKey _scaffoldKey = new GlobalKey();
91 |
92 |
93 | @override
94 | void initState() {
95 | super.initState();
96 | ///TODO Dart项目中的StackTrance和Flutter中的不一样,所以需要不同分析
97 | Log.d(StackTrace.current, 'Log initState');
98 | }
99 |
100 | Widget _buildNavigationBar(BuildContext context) {
101 | if (_navigationViews == null || _navigationViews.length == 0) {
102 | _navigationViews = [
103 | new NavigationIconView(
104 | icon: const Icon(Icons.home),
105 | title: _titleList[0],
106 | color: Colors.blue,
107 | name: _NavigationItemName.indexs
108 | ),
109 | new NavigationIconView(
110 | icon: const Icon(Icons.extension),
111 | title: _titleList[1],
112 | color: Colors.blue,
113 | name: _NavigationItemName.tree
114 | ),
115 | new NavigationIconView(
116 | icon: const Icon(Icons.navigation),
117 | title: _titleList[2],
118 | color: Colors.blue,
119 | name: _NavigationItemName.navi
120 | ),
121 | new NavigationIconView(
122 | icon: const Icon(Icons.collections_bookmark),
123 | title: _titleList[3],
124 | color: Colors.blue,
125 | name: _NavigationItemName.project
126 | )
127 | ];
128 | }
129 |
130 | return new BottomNavigationBar(
131 | items: _navigationViews
132 | .map((NavigationIconView navigationView) => navigationView.item)
133 | .toList(),
134 | currentIndex: _currentIndex,
135 | type: _type,
136 | onTap: (int index) {
137 | ///ValueChanged
138 | if (index != _currentIndex) {
139 | print('BottomNavigationBar onTap $index');
140 | setState(() {
141 | _currentIndex = index;
142 | });
143 | }
144 | },
145 | );
146 | }
147 |
148 | Widget _buildPage(BuildContext context,
149 | GlobalKey scaffoldKey) {
150 |
151 | print('_buildPage $_currentIndex');
152 |
153 | _navigationViews[_currentIndex].build(context, scaffoldKey);
154 |
155 | _navigationViews.forEach((NavigationIconView item) {
156 | if (!(pages.indexOf(item.page) >= 0) && item.page != null) {
157 | pages.add(item.page);
158 | }
159 | });
160 |
161 | ///IndexedStack可以直接控制那个Widget在最上面
162 | ///这里如果使用IndexedStack的话就不能使用懒加载PageView了
163 | // return new IndexedStack(
164 | // index: _currentIndex,
165 | // children:pages,
166 | // );
167 |
168 | ///相当于List重新排序
169 | pages
170 | ..removeWhere((Widget page) => page == _navigationViews[_currentIndex].page)
171 | ..add(_navigationViews[_currentIndex].page);
172 |
173 | return new Stack(
174 | children: pages,
175 | );
176 | }
177 |
178 | @override
179 | Widget build(BuildContext context) {
180 | print('build $_currentIndex');
181 |
182 | Widget botNavBar = _buildNavigationBar(context);
183 |
184 | String appBarTitle;
185 | if(_currentIndex==0){
186 | appBarTitle='WanAndroid';
187 | }else{
188 | appBarTitle=_titleList[_currentIndex];
189 | }
190 |
191 | return new Scaffold(
192 | key: _scaffoldKey,
193 |
194 | ///这里只切换了AppBar和BottomNavigationBar之间的布局
195 | appBar: new AppBar(
196 | title: new Text(appBarTitle),
197 | ),
198 |
199 | drawer: new DrawerPage(scaffoldKey: _scaffoldKey),
200 | ///BottomNavigationBar模式的,使用Stack作为页面的容器,为每个节目传入一个Key的话,这样
201 | ///随着底部导航栏切换页面的时候就不会每次都重建页面了
202 | body: _buildPage(context, _scaffoldKey),
203 |
204 | ///这里界面只是Stack中的一层布局,所以要求每次背景都不能是透明的,否则界面将穿透
205 |
206 | ///这里好像调用了几次
207 | bottomNavigationBar: botNavBar,
208 | );
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/home/navi_page.dart:
--------------------------------------------------------------------------------
1 | import '../common/config.dart';
2 | import '../home/web_view_page.dart';
3 | import 'package:rect_getter/rect_getter.dart';
4 | import 'package:after_layout/after_layout.dart';
5 |
6 | class _ChipsTile extends StatelessWidget {
7 | const _ChipsTile({
8 | Key key,
9 | this.label,
10 | this.children,
11 | }) : super(key: key);
12 |
13 | final String label;
14 | final List children;
15 |
16 | // Wraps a list of chips into a ListTile for display as a section in the demo.
17 | @override
18 | Widget build(BuildContext context) {
19 | return new ListTile(
20 | title: new Padding(
21 | padding: const EdgeInsets.only(top: 16.0, bottom: 4.0),
22 | child: new Text(label, textAlign: TextAlign.start),
23 | ),
24 | subtitle: new Wrap(
25 | children: children
26 | .map((Widget chip) => new Padding(
27 | padding: const EdgeInsets.all(4.0),
28 | child: chip,
29 | ))
30 | .toList(),
31 | ),
32 | );
33 | }
34 | }
35 |
36 | ///当前界面关于这个问题 https://github.com/flutter/flutter/issues/12319
37 |
38 | class NaviPage extends StatefulWidget {
39 | final GlobalKey scaffoldKey;
40 |
41 | NaviPage({@required Key key, @required this.scaffoldKey}) : super(key: key);
42 |
43 | @override
44 | _NaviPageState createState() => new _NaviPageState();
45 | }
46 |
47 | class _NaviPageState extends State with AfterLayoutMixin {
48 | bool loading = true;
49 | bool isReload = false;
50 | List data;
51 | int itemSize;
52 |
53 | ///左边列表数据
54 | List leftList = [];
55 |
56 | int selectedIndex = 0;
57 |
58 | ScrollController _scrollController;
59 |
60 | List rightListKey = [];
61 |
62 | List rightListItemTop = [];
63 |
64 | @override
65 | void initState() {
66 | super.initState();
67 | _scrollController = new ScrollController();
68 | _loadData();
69 | }
70 |
71 | _loadData() async {
72 | print('NaviPage _loadData');
73 |
74 | await HttpUtil
75 | .get(
76 | navi_api,
77 | )
78 | .then((dataModel) {
79 | if (!mounted) return;
80 |
81 | ///这个errorCode只有两种情况
82 | if (dataModel.errorCode == 0) {
83 | data = dataModel.data;
84 | itemSize = data.length;
85 | print(itemSize);
86 |
87 | for (int i = 0; i < itemSize; i++) {
88 | leftList.add(data[i]['name']);
89 | }
90 |
91 | setState(() {
92 | loading = false;
93 | isReload = false;
94 | });
95 | } else {
96 | setState(() {
97 | loading = false;
98 | isReload = true;
99 | });
100 | }
101 | });
102 | }
103 |
104 | _buildBody() {
105 | print('NaviPage _buildBody');
106 | Widget body;
107 | if (loading == true) {
108 | body = Application.progressWidget;
109 | } else {
110 | ///获取数据出错,需要重新加载
111 | if (isReload) {
112 | body = Application.getReloadWidget(onPressed: () {
113 | if (!mounted) return;
114 |
115 | setState(() {
116 | loading = true;
117 | isReload = false;
118 | });
119 | _loadData();
120 | });
121 | } else {
122 | body = new Row(
123 | children: [_buildLeftList(), _buildRightList()],
124 | );
125 | }
126 | }
127 | return body;
128 | }
129 |
130 | _buildLeftList() {
131 | print('NaviPage _buildLeftList');
132 |
133 | return new Expanded(
134 | child: new NotificationListener(
135 | onNotification: (onNotification) {
136 | ///在布局完成的第一次操作前完成Widget信息的获取
137 | if (rightListItemTop.isEmpty) {
138 | for (int i = 0; i < rightListKey.length; i++) {
139 | var rect = RectGetter.getRectFromKey(rightListKey[i]);
140 | rightListItemTop.add(rect.top);
141 | }
142 | }
143 | },
144 | child: new ListView.builder(
145 | itemCount: leftList.length,
146 | itemBuilder: (context, index) {
147 | return new Column(
148 | mainAxisAlignment: MainAxisAlignment.center,
149 | crossAxisAlignment: CrossAxisAlignment.center,
150 | children: [
151 | new SizedBox(
152 | height: 48.0,
153 | width: double.infinity,
154 | child: new InkWell(
155 | onTap: () {
156 | ///在布局完成的第一次操作前完成Widget信息的获取
157 | if (rightListItemTop.isEmpty) {
158 | for (int i = 0; i < rightListKey.length; i++) {
159 | var rect =
160 | RectGetter.getRectFromKey(rightListKey[i]);
161 | rightListItemTop.add(rect.top);
162 | }
163 | }
164 |
165 | if (selectedIndex != index) {
166 | selectedIndex = index;
167 | ///调用这里就可以设置右边滑动
168 | _scrollController.jumpTo(
169 | rightListItemTop[selectedIndex] -
170 | rightListItemTop[0]);
171 | ///调用这里设置左边Item切换
172 | setState(() {});
173 | }
174 | },
175 |
176 | ///设置Item选中的背景颜色
177 | child: new Container(
178 | color: selectedIndex == index
179 | ? Colors.grey[300]
180 | : Colors.transparent,
181 | child: new Center(
182 | child: new Text(
183 | leftList[index],
184 | style: new TextStyle(fontSize: 16.0),
185 | )),
186 | ),
187 | ),
188 | ),
189 | new Divider(
190 | height: 1.0,
191 | ),
192 | ]);
193 | })),
194 | );
195 | }
196 |
197 | _buildRightList() {
198 | print('_NaviPage _buildRightList');
199 |
200 | rightListKey.clear();
201 |
202 | List children = [];
203 |
204 | for (int i = 0; i < leftList.length; i++) {
205 | Map itemRightList = new Map();
206 |
207 | var articles = data[i]['articles'];
208 | for (int i = 0; i < articles.length; i++) {
209 | itemRightList.putIfAbsent(
210 | articles[i]['title'], () => articles[i]['link']);
211 | }
212 |
213 | final List actionChips = itemRightList.keys.map(
214 | (String name) {
215 | return new ActionChip(
216 | key: new ValueKey(name),
217 | label: new Text(name),
218 | onPressed: () {
219 | Navigator
220 | .of(context)
221 | .push(new MaterialPageRoute(builder: (context) {
222 | return new WebViewPage(
223 | key: new Key(name), title: name, url: itemRightList[name]);
224 | }));
225 | },
226 | );
227 | },
228 | ).toList();
229 |
230 | ///将这个globalKey收集起来,后面获取Widget的信息
231 | var globalKey = RectGetter.createGlobalKey();
232 |
233 | rightListKey.add(globalKey);
234 |
235 | children.add(new RectGetter(
236 | key: globalKey,
237 | child: new GestureDetector(
238 | onTap: () {
239 | ///在布局完成的第一次操作前完成Widget信息的获取
240 | if (rightListItemTop.isEmpty) {
241 | for (int i = 0; i < rightListKey.length; i++) {
242 | var rect = RectGetter.getRectFromKey(rightListKey[i]);
243 | rightListItemTop.add(rect.top);
244 | }
245 | }
246 | },
247 | child: new _ChipsTile(
248 | label: leftList[i],
249 | children: actionChips,
250 | ),
251 | )));
252 | }
253 |
254 | return new Expanded(
255 | flex: 2,
256 | child: new NotificationListener(
257 | onNotification: (notification) {
258 | ///在布局完成的第一次操作前完成Widget信息的获取
259 | if (rightListItemTop.isEmpty) {
260 | for (int i = 0; i < rightListKey.length; i++) {
261 | var rect = RectGetter.getRectFromKey(rightListKey[i]);
262 | rightListItemTop.add(rect.top);
263 | }
264 | }
265 | },
266 |
267 | ///直接使用new ListView,children就会在之前全部被创建
268 | child: new SingleChildScrollView(
269 | controller: _scrollController,
270 | child: new Column(
271 | children: children,
272 | ),
273 | )));
274 | }
275 |
276 | @override
277 | Widget build(BuildContext context) {
278 | print('NaviPage build');
279 | return new Container(
280 | decoration: new BoxDecoration(
281 | color: Colors.grey[100],
282 | ),
283 | child: new Material(
284 | child: _buildBody(),
285 | ),
286 | );
287 | }
288 |
289 | ///TODO 这个方法这里不太好使,因为第一次布局一定是进度条布局,真正的布局至少是在第二次
290 | @override
291 | void afterFirstLayout(BuildContext context) {
292 | // TODO: implement afterFirstLayout
293 | }
294 | }
295 |
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/home/project_page.dart:
--------------------------------------------------------------------------------
1 | import '../common/config.dart';
2 |
3 | ///ProjectPage可以直接使用tree_item_tab_page来代替
4 | class ProjectPage extends StatefulWidget {
5 | final GlobalKey scaffoldKey;
6 |
7 | ProjectPage({@required Key key, @required this.scaffoldKey})
8 | : super(key: key);
9 |
10 | @override
11 | _ProjectPageState createState() => new _ProjectPageState();
12 | }
13 |
14 | class _ProjectPageState extends State {
15 | @override
16 | Widget build(BuildContext context) {
17 | return new Container(
18 | decoration: new BoxDecoration(
19 | color: Colors.grey[100],
20 | ),
21 | child: new Center(
22 | child: new Column(
23 | crossAxisAlignment: CrossAxisAlignment.center,
24 | children: [
25 | new Text('项目页面',style: new TextStyle(fontSize: 18.0),),
26 | new Padding(
27 | padding: const EdgeInsets.all(8.0),
28 | child: new Text('与前面一个页面(结构TabBar+TabBarView)相似,故该页面不实现了',style: new TextStyle(fontSize: 16.0),),
29 | )
30 | ],
31 | )));
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/home/tree/tree_item_tabs_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_wanandroid/common/config.dart';
2 | import 'package:flutter_wanandroid/adapter/feed_item_adapter.dart';
3 |
4 | import 'package:flutter_wanandroid/widgets/pull_to_refresh/pull_to_refresh.dart';
5 |
6 | class SortItemPageBean {
7 | int cid;
8 |
9 | ///当前TabBar page
10 | Widget page;
11 |
12 | ///当前页面滑动的位置
13 | double offset;
14 |
15 | SortItemPageBean({@required this.cid});
16 | }
17 |
18 | ///TODO SortItemPage和IndexPage中的ListView相同,后期是不是可以提取出来
19 | ///SortItemPage代表 tree 的下的TabBarView Page
20 | class SortItemPage extends StatefulWidget {
21 | final int cid;
22 | ScrollController controller;
23 | double offset;
24 |
25 | bool loading = true;
26 | bool isReload = false;
27 | List data;
28 |
29 | bool hasMore = true;
30 |
31 | int itemSize;
32 |
33 | bool isLoadMoring = false;
34 | bool isLoadMoreFailed = false;
35 |
36 | ///当前页数,参数从0开始
37 | int curPage = 0;
38 |
39 | SortItemPage({@required this.cid, @required this.offset});
40 |
41 | @override
42 | _SortItemPageState createState() => new _SortItemPageState();
43 | }
44 |
45 | ///AutomaticKeepAliveClientMixin对保留Page状态有作用
46 | class _SortItemPageState extends State {
47 | // RefreshController _refreshController;
48 | FeedItemAdapter _feedItemAdapter;
49 |
50 | _loadData() async {
51 | print('SortItemPage _loadData');
52 |
53 | await HttpUtil.get(
54 | tree_article_api, [widget.curPage, widget.cid]).then((dataModel) {
55 | if (!mounted) return;
56 |
57 | ///这个errorCode只有两种情况
58 | if (dataModel.errorCode == 0) {
59 | widget.curPage++;
60 |
61 | ///判断是否还有更多数据
62 | if (dataModel.data['curPage'] < dataModel.data['pageCount']) {
63 | widget.hasMore = true;
64 | } else {
65 | widget.hasMore = false;
66 | }
67 |
68 | if (widget.data != null) {
69 | widget.data.addAll(dataModel.data['datas']);
70 | widget.itemSize = widget.data.length;
71 | } else {
72 | widget.data = dataModel.data['datas'];
73 | widget.itemSize = widget.data.length;
74 | }
75 |
76 | setState(() {
77 | widget.loading = false;
78 | widget.isReload = false;
79 | if (widget.isLoadMoring) {
80 | widget.isLoadMoring = false;
81 | if (widget.isLoadMoreFailed) {
82 | widget.isLoadMoreFailed = false;
83 | }
84 | }
85 | });
86 | } else {
87 | setState(() {
88 | widget.loading = false;
89 | widget.isReload = true;
90 |
91 | ///加载更多,加载失败
92 | if (widget.isLoadMoring) {
93 | widget.isLoadMoreFailed = true;
94 | }
95 | });
96 | }
97 | });
98 | }
99 |
100 | _buildTabPage() {
101 | Widget body;
102 | if (widget.loading == true) {
103 | body = Application.progressWidget;
104 | } else {
105 | ///获取数据出错,需要重新加载
106 | if (widget.isReload) {
107 | body = Application.getReloadWidget(onPressed: () {
108 | if (!mounted) return;
109 |
110 | setState(() {
111 | widget.loading = true;
112 | widget.isReload = false;
113 | });
114 | _loadData();
115 | });
116 | } else {
117 | ///TODO 这里上拉加载不使用SmartRefresher了,自己写个简单的上拉加载
118 | body = new ListView.builder(
119 | physics: const AlwaysScrollableScrollPhysics(),
120 | controller: widget.controller,
121 | shrinkWrap: true,
122 | itemCount: widget.itemSize + 1,
123 | itemBuilder: (context, index) {
124 | print('SortItemPage _buildTabPage index $index');
125 |
126 | ///已加载更多一次,而且加载失败了
127 | if (widget.isLoadMoring && widget.isLoadMoreFailed) {
128 | return Application.getLoadMoreFailedWidget(onPressed: () {
129 | widget.isLoadMoreFailed=false;
130 | _loadData();
131 | });
132 | }
133 |
134 | ///滑动到最后一个,显示加载更多的布局
135 | if (index == widget.itemSize) {
136 | if (widget.hasMore) {
137 | return Application.loadMoreWidget;
138 | } else {
139 | return Application.noMoreWidget;
140 | }
141 | }
142 | return _feedItemAdapter.getItemView(context, widget.data[index]);
143 | });
144 | }
145 | }
146 | return body;
147 | }
148 |
149 | @override
150 | void initState() {
151 | super.initState();
152 |
153 | print('_SortItemPageState initState');
154 |
155 | ///避免多次请求网络数据
156 | if (widget.loading) {
157 | _loadData();
158 | }
159 | _feedItemAdapter = new FeedItemAdapter();
160 | }
161 |
162 | @override
163 | Widget build(BuildContext context) {
164 | print('_SortItemPageState build ${widget.cid}');
165 |
166 | ///ScrollController是控制ListView滑动到那个位置的,设置
167 | widget.controller =
168 | new ScrollController(initialScrollOffset: widget.offset);
169 | widget.controller.addListener(() {
170 | ///当绑定了该ScrollController的ListView滑动时就会调用该方法
171 | widget.offset = widget.controller.offset;
172 | print('_SortItemPageState _buildTabPage ${widget.offset}');
173 |
174 | ///这里判断是否滑动到底部了,就可以进行加载更多的操作了
175 | if (widget.controller.position.pixels ==
176 | widget.controller.position.maxScrollExtent) {
177 | if (!widget.isLoadMoring) {
178 | widget.isLoadMoring = true;
179 | _loadData();
180 | }
181 | }
182 | });
183 |
184 | return _buildTabPage();
185 | }
186 | }
187 |
188 | class TreeItemTabsPage extends StatefulWidget {
189 | final Map tabsLabel;
190 | final String title;
191 |
192 | TreeItemTabsPage(
193 | {@required Key key, @required this.title, @required this.tabsLabel})
194 | : super(key: key);
195 |
196 | @override
197 | _TreeItemTabsPageState createState() => new _TreeItemTabsPageState();
198 | }
199 |
200 | class _TreeItemTabsPageState extends State
201 | with SingleTickerProviderStateMixin {
202 | TabController _tabController;
203 | List sortItemPageBean = [];
204 |
205 | @override
206 | void initState() {
207 | super.initState();
208 |
209 | print('_TreeItemTabsPageState initState');
210 | _tabController =
211 | new TabController(length: widget.tabsLabel.length, vsync: this);
212 |
213 | widget.tabsLabel.keys.map((name) {
214 | print('_TreeItemTabsPageState $name');
215 | sortItemPageBean.add(new SortItemPageBean(cid: widget.tabsLabel[name]));
216 | }).toList();
217 | }
218 |
219 | @override
220 | void dispose() {
221 | _tabController.dispose();
222 | super.dispose();
223 | }
224 |
225 | @override
226 | Widget build(BuildContext context) {
227 | return new Scaffold(
228 | appBar: new AppBar(
229 | title: new Text(widget.title),
230 | bottom: new TabBar(
231 | controller: _tabController,
232 | isScrollable: true,
233 | indicator: const UnderlineTabIndicator(),
234 | tabs: widget.tabsLabel.keys.map((label) {
235 | return new Tab(text: label);
236 | }).toList()),
237 | ),
238 | body: new TabBarView(
239 | // key: new Key('tree_item_tabs'),
240 | controller: _tabController,
241 | children: sortItemPageBean.map((bean) {
242 | if (bean.page == null) {
243 | bean.offset = 0.0;
244 | bean.page = new SortItemPage(
245 | cid: bean.cid,
246 | offset: bean.offset,
247 | );
248 | }
249 | return bean.page;
250 | }).toList(),
251 | ),
252 | );
253 | }
254 | }
255 |
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/home/tree/tree_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_wanandroid/common/config.dart';
2 | import 'package:flutter_wanandroid/home/tree/tree_item_tabs_page.dart';
3 |
4 | ///知识体系页面
5 | class TreePage extends StatefulWidget {
6 | final GlobalKey scaffoldKey;
7 |
8 | TreePage({@required Key key, @required this.scaffoldKey}) : super(key: key);
9 |
10 | @override
11 | _TreePageState createState() => new _TreePageState();
12 | }
13 |
14 | ///"children":[
15 | /// {
16 | /// "children":[
17 | /// ],
18 | /// "courseId":13,
19 | /// "id":60,--------------cid
20 | /// "name":"Android Studio相关",
21 | /// "order":1000,
22 | /// "parentChapterId":150,
23 | /// "visible":1
24 | /// },
25 | /// {
26 | /// "children":Array[0],
27 | /// "courseId":13,
28 | /// "id":169,
29 | /// "name":"gradle",
30 | /// "order":1001,
31 | /// "parentChapterId":150,
32 | /// "visible":1
33 | /// },
34 | /// Object{...}
35 | /// ],
36 | /// "courseId":13,
37 | /// "id":150,
38 | /// "name":"开发环境",
39 | /// "order":1,
40 | /// "parentChapterId":0,-------------id
41 | /// "visible":1
42 | class _TreePageState extends State {
43 | bool loading = true;
44 | bool isReload = false;
45 | List data;
46 |
47 | int itemSize;
48 |
49 | @override
50 | void initState() {
51 | super.initState();
52 | _loadData();
53 | }
54 |
55 | _loadData() async {
56 | print('TreePage _loadData');
57 |
58 | await HttpUtil
59 | .get(
60 | tree_api,
61 | )
62 | .then((dataModel) {
63 | if (!mounted) return;
64 |
65 | ///这个errorCode只有两种情况
66 | if (dataModel.errorCode == 0) {
67 | data = dataModel.data;
68 |
69 | itemSize = data.length;
70 | setState(() {
71 | loading = false;
72 | isReload = false;
73 | });
74 | } else {
75 | setState(() {
76 | loading = false;
77 | isReload = true;
78 | });
79 | }
80 | });
81 | }
82 |
83 | _buildBody() {
84 | Widget body;
85 | if (loading == true) {
86 | body = Application.progressWidget;
87 | } else {
88 | ///获取数据出错,需要重新加载
89 | if (isReload) {
90 | body = Application.getReloadWidget(onPressed: () {
91 | if (!mounted) return;
92 |
93 | setState(() {
94 | loading = true;
95 | isReload = false;
96 | });
97 | _loadData();
98 | });
99 | } else {
100 | body = new ListView.builder(
101 | physics: const ScrollPhysics(),
102 | shrinkWrap: true,
103 | itemCount: itemSize,
104 | itemBuilder: (context, index) {
105 | print('TreePage index $index');
106 | return _buildTreeListItem(data[index]);
107 | });
108 | }
109 | }
110 | return body;
111 | }
112 |
113 | _buildTreeListItem(item) {
114 | Color primaryColor = Theme.of(context).primaryColor;
115 |
116 | Map tabsLabel = new Map();
117 | StringBuffer stringBuffer = new StringBuffer();
118 |
119 | int length = item['children'].length;
120 | for (int i = 0; i < length; i++) {
121 | Map map = item['children'][i];
122 |
123 | tabsLabel.putIfAbsent(map['name'], () => map['id']);
124 |
125 | stringBuffer.write(map['name']);
126 | if (i < length - 1) {
127 | stringBuffer.write(' ');
128 | }
129 | }
130 |
131 | Widget childsText = new Text(stringBuffer.toString());
132 |
133 | return new Material(
134 | child: new InkWell(
135 | onTap: () {
136 | Navigator.of(context).push(new MaterialPageRoute(builder: (context) {
137 | return new TreeItemTabsPage(
138 | key: new Key(item['name']),
139 | title: item['name'],
140 | tabsLabel: tabsLabel
141 | );
142 | }));
143 | },
144 | child: new Card(
145 | elevation: 4.0,
146 | child: new Padding(
147 | padding: const EdgeInsets.all(4.0),
148 | child: new Row(
149 | children: [
150 | new Expanded(
151 | child: new Column(
152 | children: [
153 | new Text(item['name'],
154 | style: new TextStyle(
155 | color: primaryColor, fontSize: 18.0)),
156 | childsText,
157 | ],
158 | ),
159 | ),
160 | const Icon(Icons.chevron_right),
161 | ],
162 | ),
163 | )),
164 | ),
165 | );
166 | }
167 |
168 | @override
169 | Widget build(BuildContext context) {
170 | return new Container(
171 | decoration: new BoxDecoration(
172 | color: Colors.grey[100],
173 | ),
174 | child: _buildBody(),
175 | );
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/home/web_view_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
2 |
3 | import '../common/config.dart';
4 | import 'package:url_launcher/url_launcher.dart';
5 |
6 | ///WebViewPage 具体内容页面 TODO WebView这个页面效果不好
7 | class WebViewPage extends StatefulWidget {
8 | final String title;
9 | final String url;
10 |
11 | WebViewPage({@required Key key, @required this.title, @required this.url})
12 | : super(key: key);
13 |
14 | @override
15 | _DetailPageState createState() => new _DetailPageState();
16 | }
17 |
18 | class _DetailPageState extends State {
19 |
20 | bool isFavorite=false;
21 | @override
22 | initState() {
23 | super.initState();
24 | }
25 |
26 | @override
27 | Widget build(BuildContext context) {
28 |
29 | ///flutter_webview_plugin, The webview is not integrated in the widget tree,
30 | /// it is a native view on top of the flutter view. you won't be able to use snackbars, dialogs ...
31 | return new WebviewScaffold(
32 | url: widget.url,
33 | appBar: new AppBar(
34 | ///TODO AppBar上的title设置为跑马灯效果
35 | title: new Text(widget.title),
36 | ///TODO 弹出式的Menu不能用,会被native WebView遮挡
37 | actions: [
38 | new GestureDetector(
39 | child: new Icon(isFavorite?Icons.favorite:Icons.favorite_border),
40 | onTap: (){
41 | setState(() {
42 | isFavorite=!isFavorite;
43 | });
44 | },
45 | ),
46 | new SizedBox(width: 8.0,),
47 | new GestureDetector(
48 | child: const Icon(Icons.explore),
49 | onTap: () async {
50 | if (await canLaunch(widget.url)) {
51 | await launch(widget.url);
52 | } else {
53 | ///这里使用Toast
54 | print('Could not launch ${widget.url}');
55 | }
56 | },
57 | ),
58 | new SizedBox(width: 8.0,),
59 | ],
60 | ),
61 | );
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_wanandroid/common/config.dart';
2 | import 'package:flutter_wanandroid/home/main_screen.dart';
3 | import 'package:stack_trace/stack_trace.dart';
4 |
5 | void main() {
6 |
7 | // https://pub.flutter-io.cn/packages/stack_trace
8 | // 这里使用是这个开源库,可以处理StackTrack(堆栈跟踪)信息,但是直接这样使用好像是没有效果的
9 | // Chain.capture(() {
10 | // runApp(new MyApp());
11 | // }, onError: (final error, final Chain chain) {
12 | // print("Caught error $error\n"
13 | // "${chain.terse}");
14 | // });
15 | runApp(new MyApp());
16 | }
17 |
18 | class MyApp extends StatelessWidget {
19 | // This widget is the root of your application.
20 | @override
21 | Widget build(BuildContext context) {
22 |
23 | ///调用这个可以让状态栏隐藏,下滑还是可以出现,一段时间后消失
24 | // SystemChrome.setEnabledSystemUIOverlays([]);
25 | return new MaterialApp(
26 | title: 'WanAndroid APP',
27 | //设置debug模式下是否展示banner
28 | debugShowCheckedModeBanner: false,
29 | theme: new ThemeData(
30 | primarySwatch: Colors.blue,
31 | backgroundColor: Colors.grey[100],
32 | ),
33 | home: MainScreen(),
34 | );
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/model/data_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:meta/meta.dart';
2 |
3 | ///DataModel 是基础返回数据Model
4 | class DataModel {
5 | int errorCode;
6 | String errorMsg;
7 | dynamic data;///动态类型,根据不同返回接口,数据类型不同
8 |
9 | DataModel(
10 | {@required this.data, @required this.errorCode, @required this.errorMsg});
11 |
12 | @override
13 | String toString() {
14 | return '{"errorCode": $errorCode,"errorMsg": $errorMsg,"data": $data}';
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/notifications/notifications.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/src/widgets/notification_listener.dart';
2 |
3 | class LoginChangeNotification extends Notification{
4 |
5 | }
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/startup/login_page.dart:
--------------------------------------------------------------------------------
1 | import '../common/config.dart';
2 | import '../notifications/notifications.dart';
3 | import '../startup/register_page.dart';
4 |
5 | ///登录页面
6 | class LoginPage extends StatefulWidget {
7 | @override
8 | _LoginPageState createState() => new _LoginPageState();
9 | }
10 |
11 | class _LoginPageState extends State {
12 | ///GlobalKey 全局Key,使用全局Key来唯一标志widget
13 | final GlobalKey _scaffoldKey = new GlobalKey();
14 |
15 | String username;
16 | String password;
17 |
18 | bool _autoValidate = false;
19 | bool _formWasEdited = false;
20 |
21 | bool _obscureText = true;
22 |
23 | final GlobalKey _formKey = new GlobalKey();
24 |
25 | ///处理表单的提交
26 | _handleSubmitted(BuildContext context) async {
27 | final FormState form = _formKey.currentState;
28 | if (!form.validate()) {
29 | ///FormState.validate()如果没有错误将会返回true
30 | _autoValidate = true;
31 | SnackBarUtil.showInSnackBar(_scaffoldKey, '填写信息有误.');
32 | } else {
33 | ///调用该方法,才会调用输入控件中的'onSave'方法
34 | form.save();
35 |
36 | showProgressDialog(context: context);
37 |
38 | Map map = {
39 | "username": username,
40 | "password": password,
41 | };
42 |
43 | ///TODO 这里不知为什么用户名和密码一直不对,明明是对的才对???
44 | print('post map: $map');
45 | await HttpUtil.post(login_api, map).then((dataModel) {
46 | if (!mounted) return;
47 |
48 | ///将前面那个Progress隐藏
49 | Navigator.pop(context, true);
50 |
51 | print(dataModel ?? 'dataModel is null');
52 |
53 | if (dataModel.errorCode == 0) {
54 | ///登录成功,跳转界面
55 | Navigator.of(context).pop();
56 |
57 | ///Notification 通知,Notification.dispatch()方法发起通知
58 | LoginChangeNotification notification = new LoginChangeNotification();
59 |
60 | ///在State<>类中的任意位置都可以获取到context对象
61 | notification.dispatch(context); //发起通知
62 |
63 | } else {
64 | SnackBarUtil.showInSnackBar(_scaffoldKey, dataModel.errorMsg);
65 | }
66 | });
67 | }
68 | }
69 |
70 | ///验证电话的有效性
71 | String _validateUserName(String value) {
72 | _formWasEdited = true;
73 | if (value.trim().isEmpty) return '用户名不能为空';
74 | return null;
75 | }
76 |
77 | ///验证密码
78 | String _validatePassword(String value) {
79 | _formWasEdited = true;
80 | if (value.trim().isEmpty) return '密码不能为空';
81 | return null;
82 | }
83 |
84 | ///new ListView(
85 | /// reverse: true,
86 | /// children: [
87 | /// // put your text fields here
88 | /// ].reversed.toList(),
89 | ///),
90 | @override
91 | Widget build(BuildContext context) {
92 | return new Scaffold(
93 | key: _scaffoldKey, //这里一定要加,因为只能在布局是Scaffold的Context中使用showSnacker()方法
94 | appBar: new AppBar(
95 | elevation: 0.0,
96 | title: const Text(
97 | "登录",
98 | ),
99 | ),
100 | body: new Form(
101 | key: _formKey,
102 | //是否每次在内容更改后,自动检测内容
103 | autovalidate: _autoValidate,
104 | //拦截返回按钮事件
105 | // onWillPop: _warnUserAboutInvalidData,
106 |
107 | child: new ListView(
108 | reverse: true,
109 | children: [
110 | const SizedBox(
111 | height: 24.0,
112 | ),
113 |
114 | ///跳转注册页面
115 | new Container(
116 | height: 24.0,
117 |
118 | ///alignment 对齐方式
119 | alignment: Alignment.bottomRight,
120 | padding: EdgeInsets.only(right: 24.0),
121 | child: new InkWell(
122 | child: new Text(
123 | '没有账号?去注册',
124 | style: new TextStyle(color: Colors.blue),
125 | ),
126 | onTap: () {
127 | Navigator.of(context)
128 |
129 | ///pushReplacement替换前一个界面
130 | .pushReplacement(
131 | new MaterialPageRoute(builder: (context) {
132 | return new RegisterPage();
133 | }));
134 | },
135 | ),
136 | ),
137 | const SizedBox(
138 | height: 12.0,
139 | ),
140 |
141 | new ConstrainedBox(
142 | constraints: const BoxConstraints.expand(height: 48.0),
143 | child: new Padding(
144 | padding: const EdgeInsets.symmetric(horizontal: 16.0),
145 | child: new RaisedButton(
146 | color: Colors.blue,
147 | child: new Text(
148 | '登录',
149 | style: new TextStyle(color: Colors.white, fontSize: 18.0),
150 | ),
151 | onPressed: () {
152 | //提交执行操作
153 | _handleSubmitted(context);
154 | }),
155 | ),
156 | ),
157 |
158 | const SizedBox(
159 | height: 24.0,
160 | ),
161 |
162 | new Padding(
163 | padding: const EdgeInsets.symmetric(horizontal: 16.0),
164 | child: new TextFormField(
165 | obscureText: _obscureText, //true代表不可见
166 | onSaved: (String value) {
167 | password = value.trim();
168 | },
169 | validator: _validatePassword,
170 | decoration: new InputDecoration(
171 | border: const UnderlineInputBorder(),
172 | hintText: '密码',
173 | filled: true,
174 | fillColor: Colors.transparent,
175 |
176 | ///suffixIcon 尾部的icon图标
177 | suffixIcon: new GestureDetector(
178 | onTap: () {
179 | setState(() {
180 | _obscureText = !_obscureText;
181 | });
182 | },
183 | child: new Icon(
184 | _obscureText ? Icons.visibility_off : Icons.visibility),
185 | ),
186 | ),
187 | ),
188 | ),
189 |
190 | const SizedBox(
191 | height: 24.0,
192 | ),
193 |
194 | new Padding(
195 | padding: const EdgeInsets.symmetric(horizontal: 16.0),
196 | child: new TextFormField(
197 | decoration: const InputDecoration(
198 | //输入控件底部的横线,border边框
199 | border: const UnderlineInputBorder(),
200 | // hintText:
201 | labelText: '用户名 *',
202 | //下面两个属性要结合使用
203 | filled: true,
204 | fillColor: Colors.transparent, //输入框的背景颜色
205 | ),
206 | onSaved: (String value) {
207 | //保存TextFormField输入的字段
208 | username = value.trim(); //保存输入数据
209 | },
210 | //validator 验证器
211 | validator: _validateUserName, //验证输入数据
212 | ),
213 | ),
214 |
215 | const SizedBox(
216 | height: 24.0,
217 | ),
218 |
219 | //登录页面顶部图片
220 | new SizedBox(
221 | height: 200.0,
222 | child: new Image.asset(
223 | 'assets/logo.png',
224 | fit: BoxFit.scaleDown,
225 | ),
226 | ),
227 | ],
228 | ),
229 | // ),
230 | ),
231 | );
232 | }
233 | }
234 |
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/startup/register_page.dart:
--------------------------------------------------------------------------------
1 | import '../common/config.dart';
2 | import '../startup/login_page.dart';
3 | import '../model/data_model.dart';
4 |
5 | ///注册页面
6 | class RegisterPage extends StatefulWidget {
7 | @override
8 | _RegisterPageState createState() => new _RegisterPageState();
9 | }
10 |
11 | class _RegisterPageState extends State {
12 | ///GlobalKey 全局Key,使用全局Key来唯一标志widget
13 | final GlobalKey _scaffoldKey = new GlobalKey();
14 |
15 | String username;
16 | String password;
17 | String repassword;
18 |
19 | bool _autoValidate = false;
20 | bool _formWasEdited = false;
21 |
22 | bool _obscureText = true;
23 |
24 | final GlobalKey _formKey = new GlobalKey();
25 |
26 | ///处理表单的提交
27 | _handleSubmitted(BuildContext context) async {
28 | final FormState form = _formKey.currentState;
29 | if (!form.validate()) {
30 | ///FormState.validate()如果没有错误将会返回true
31 | _autoValidate = true;
32 | SnackBarUtil.showInSnackBar(_scaffoldKey, '填写信息有误.');
33 | } else {
34 | ///调用该方法,才会调用输入控件中的'onSave'方法
35 | form.save();
36 |
37 | showProgressDialog(context: context);
38 |
39 | Map map = {
40 | 'username': username,
41 | "password": password,
42 | "repassword": repassword
43 | };
44 |
45 | DataModel dataModel = await HttpUtil.post(register_api, map);
46 |
47 | if (!mounted) return;
48 |
49 | ///将前面那个Progress隐藏
50 | Navigator.pop(context, true);
51 |
52 | if (dataModel.errorCode == 0) {
53 | ///登录成功,跳转界面
54 |
55 | ///SnackBar的默认展示时间是1500毫秒
56 | SnackBarUtil.showInSnackBar(_scaffoldKey, '注册成功,将跳转到登录界面');
57 |
58 | new Timer(const Duration(milliseconds: 1800), () {
59 | ///注册成功,将调整到登录界面
60 | Navigator
61 | .of(context)
62 | .pushReplacement(new MaterialPageRoute(builder: (context) {
63 | ///注册成功后就会跳转'登录'界面
64 | return new LoginPage();
65 | }));
66 | });
67 | } else {
68 | SnackBarUtil.showInSnackBar(_scaffoldKey, dataModel.errorMsg);
69 | }
70 | }
71 | }
72 |
73 | ///验证电话的有效性
74 | String _validateUserName(String value) {
75 | _formWasEdited = true;
76 | if (value.trim().isEmpty) return '用户名不能为空';
77 | return null;
78 | }
79 |
80 | ///验证密码
81 | String _validatePassword(String value) {
82 | _formWasEdited = true;
83 | value = value.trim();
84 | if (value.isEmpty) return '密码不能为空';
85 | password = value;
86 | return null;
87 | }
88 |
89 | String _validateSame(String value) {
90 | _formWasEdited = true;
91 | value = value.trim();
92 | if (value.isEmpty) return '确认密码不能为空.';
93 | if (value != password) return '确认密码与前面输入不一致.';
94 | return null;
95 | }
96 |
97 | @override
98 | Widget build(BuildContext context) {
99 | return new Scaffold(
100 | key: _scaffoldKey, //这里一定要加,因为只能在布局是Scaffold的Context中使用showSnackBar()方法
101 | appBar: new AppBar(
102 | elevation: 0.0,
103 | title: const Text(
104 | "注册",
105 | ),
106 | ),
107 | body: new Form(
108 | key: _formKey,
109 | //是否每次在内容更改后,自动检测内容
110 | autovalidate: _autoValidate,
111 |
112 | child: new ListView(
113 | reverse: true,
114 | children: [
115 | const SizedBox(
116 | height: 18.0,
117 | ),
118 |
119 | ///跳转注册页面
120 | new Container(
121 | height: 24.0,
122 |
123 | ///alignment 对齐方式
124 | alignment: Alignment.bottomRight,
125 | padding: EdgeInsets.only(right: 24.0),
126 | child: new InkWell(
127 | child: new Text(
128 | '已有账号?去登录',
129 | style: new TextStyle(color: Colors.blue),
130 | ),
131 | onTap: () {
132 | Navigator.of(context)
133 |
134 | ///pushReplacement替换前一个界面
135 | .pushReplacement(
136 | new MaterialPageRoute(builder: (context) {
137 | return new LoginPage();
138 | }));
139 | },
140 | ),
141 | ),
142 | const SizedBox(
143 | height: 12.0,
144 | ),
145 |
146 | new ConstrainedBox(
147 | constraints: const BoxConstraints.expand(height: 48.0),
148 | child: new Padding(
149 | padding: const EdgeInsets.symmetric(horizontal: 16.0),
150 | child: new RaisedButton(
151 | color: Colors.blue,
152 | child: new Text(
153 | '注册',
154 | style: new TextStyle(color: Colors.white, fontSize: 18.0),
155 | ),
156 | onPressed: () {
157 | //提交执行操作
158 | _handleSubmitted(context);
159 | }),
160 | ),
161 | ),
162 |
163 | const SizedBox(
164 | height: 12.0,
165 | ),
166 |
167 | new Padding(
168 | padding: const EdgeInsets.symmetric(horizontal: 16.0),
169 | child: new TextFormField(
170 | obscureText: _obscureText, //true代表不可见
171 | onSaved: (String value) {
172 | repassword = value.trim();
173 | },
174 | validator: _validateSame,
175 | decoration: new InputDecoration(
176 | border: const UnderlineInputBorder(),
177 | hintText: '确认密码 *',
178 | filled: true,
179 | fillColor: Colors.transparent,
180 |
181 | ///suffixIcon 尾部的icon图标
182 | suffixIcon: new GestureDetector(
183 | onTap: () {
184 | setState(() {
185 | _obscureText = !_obscureText;
186 | });
187 | },
188 | child: new Icon(
189 | _obscureText ? Icons.visibility_off : Icons.visibility),
190 | ),
191 | ),
192 | ),
193 | ),
194 | const SizedBox(
195 | height: 12.0,
196 | ),
197 |
198 | new Padding(
199 | padding: const EdgeInsets.symmetric(horizontal: 16.0),
200 | child: new TextFormField(
201 | obscureText: _obscureText, //true代表不可见
202 | onSaved: (String value) {
203 | password = value.trim();
204 | },
205 | validator: _validatePassword,
206 | decoration: new InputDecoration(
207 | border: const UnderlineInputBorder(),
208 | hintText: '密码 *',
209 | filled: true,
210 | fillColor: Colors.transparent,
211 |
212 | ///suffixIcon 尾部的icon图标
213 | suffixIcon: new GestureDetector(
214 | onTap: () {
215 | setState(() {
216 | _obscureText = !_obscureText;
217 | });
218 | },
219 | child: new Icon(
220 | _obscureText ? Icons.visibility_off : Icons.visibility),
221 | ),
222 | ),
223 | ),
224 | ),
225 |
226 | const SizedBox(
227 | height: 12.0,
228 | ),
229 |
230 | new Padding(
231 | padding: const EdgeInsets.symmetric(horizontal: 16.0),
232 | child: new TextFormField(
233 | decoration: const InputDecoration(
234 | //输入控件底部的横线,border边框
235 | border: const UnderlineInputBorder(),
236 | // hintText:
237 | labelText: '用户名 *',
238 | //下面两个属性要结合使用
239 | filled: true,
240 | fillColor: Colors.transparent, //输入框的背景颜色
241 | ),
242 | onSaved: (String value) {
243 | //保存TextFormField输入的字段
244 | username = value.trim(); //保存输入数据
245 | },
246 | //validator 验证器
247 | validator: _validateUserName, //验证输入数据
248 | ),
249 | ),
250 |
251 | const SizedBox(
252 | height: 12.0,
253 | ),
254 |
255 | new SizedBox(
256 | height: 200.0,
257 | child: new Image.asset(
258 | 'assets/logo.png',
259 | fit: BoxFit.scaleDown,
260 | ),
261 | ),
262 |
263 | //登录页面顶部图片
264 | ],
265 | ),
266 | ),
267 | );
268 | }
269 | }
270 |
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/test_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class DoubleHolder {
4 | double value = 0.0;
5 | }
6 |
7 | class StatefulListView extends StatefulWidget {
8 | final DoubleHolder offset = new DoubleHolder();
9 |
10 | StatefulListView(this._itemCount,
11 | this._indexedWidgetBuilder,
12 | {
13 | Key key
14 | })
15 | : super(key: key);
16 |
17 | double getOffsetMethod() {
18 | return offset.value;
19 | }
20 |
21 | void setOffsetMethod(double val) {
22 | offset.value = val;
23 | }
24 |
25 | final int _itemCount;
26 | final IndexedWidgetBuilder _indexedWidgetBuilder;
27 |
28 | @override
29 | _StatefulListViewState createState() =>
30 | new _StatefulListViewState(_itemCount, _indexedWidgetBuilder);
31 | }
32 |
33 | class _StatefulListViewState extends State {
34 |
35 | ScrollController scrollController;
36 | final int _itemCount;
37 | final IndexedWidgetBuilder _itemBuilder;
38 |
39 | _StatefulListViewState(this._itemCount, this._itemBuilder);
40 |
41 | @override
42 | void initState() {
43 | super.initState();
44 | scrollController = new ScrollController(
45 | initialScrollOffset: widget.getOffsetMethod()
46 | );
47 | }
48 |
49 | @override
50 | Widget build(BuildContext context) {
51 | return new NotificationListener(
52 | child: new ListView.builder(
53 | controller: scrollController,
54 | itemCount: _itemCount,
55 | itemBuilder: _itemBuilder
56 | ),
57 | onNotification: (notification) {
58 | if (notification is ScrollNotification) {
59 | widget.setOffsetMethod(notification.metrics.pixels);
60 | }
61 | },
62 | );
63 | }
64 | }
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/utils/dialog_util.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'dart:async';
3 |
4 | showProgressDialog({BuildContext context}) {
5 | final ThemeData theme = Theme.of(context);
6 | final TextStyle dialogTextStyle =
7 | theme.textTheme.subhead.copyWith(color: theme.textTheme.caption.color);
8 |
9 | showDialog(
10 | context: context,
11 | barrierDismissible: false,
12 |
13 | ///添加该属性,设置该属性为false,点击Dialog之外的区域就不会消失,按返回键还是会消失
14 | builder: (BuildContext context) =>
15 | new AlertDialog(
16 | content: new Row(
17 | mainAxisAlignment: MainAxisAlignment.start,
18 | crossAxisAlignment: CrossAxisAlignment.center,
19 | children: [
20 | new CircularProgressIndicator(),
21 | const SizedBox(
22 | width: 18.0,
23 | ),
24 | new Text(
25 | '请求中...',
26 | style: dialogTextStyle,
27 | ),
28 | ],
29 | ),
30 | ));
31 | }
32 |
33 | Future showAlertDialog(BuildContext context, String alert) {
34 | final ThemeData theme = Theme.of(context);
35 | final TextStyle dialogTextStyle =
36 | theme.textTheme.subhead.copyWith(color: theme.textTheme.caption.color);
37 |
38 | return showDialog(
39 | context: context,
40 | builder: (BuildContext context) =>
41 | new AlertDialog(
42 | content: new Text(
43 | alert,
44 | style: dialogTextStyle,
45 | ),
46 | actions: [
47 | new FlatButton(
48 | child: const Text('取消'),
49 | onPressed: () {
50 | Navigator.pop(context, false);
51 | }),
52 | new FlatButton(
53 | child: const Text('确认'),
54 | onPressed: () {
55 | Navigator.pop(context, true);
56 | })
57 | ]),
58 | );
59 | }
60 |
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/utils/http_util.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:dio/dio.dart';
4 |
5 | import '../common/config.dart';
6 | import '../model/data_model.dart';
7 |
8 | class HttpUtil {
9 |
10 | static Dio dio = new Dio(new Options(
11 | connectTimeout: timeOut, headers: {"content-Type": "application/json"}));
12 |
13 | ///get网络请求
14 | static Future get(String url, [List params]) async {
15 | ///页码
16 | if (params != null) {
17 | url = fillPage(url, params);
18 | }
19 |
20 | try {
21 | Response response = await dio.get(url);
22 |
23 | if (response.statusCode == HttpStatus.OK) {
24 | ///200,有两种情况,数据为空,数据不为空
25 | Map map = response.data;
26 | print('get response data ${response.data}');
27 |
28 | return new DataModel(
29 | data: map['data'],
30 |
31 | ///0
32 | errorCode: map['errorCode'],
33 | errorMsg: map['errorMsg']);
34 | } else {
35 | print('url :$url \n错误msg :\n${response.statusCode}');
36 | return getErrorModel();
37 | }
38 | } on DioError catch (e) {
39 |
40 | ///网络请求超时异常
41 | if(e.type==DioErrorType.CONNECT_TIMEOUT){
42 | print('---Error DioErrorType connect timeOut---');
43 | return getErrorModel(1);
44 | }
45 |
46 | ///DioError.response响应信息,如果发生在服务器返回数据之前,则为null
47 | if (e.response != null) {
48 | print('---Error response is not null------------');
49 | print(e.response.data);
50 | print(e.response.headers);
51 | print(e.response.request);
52 | return getErrorModel();
53 | } else {
54 | // Something happened in setting up or sending the request that triggered an Error
55 | print('---Error response is null------------------');
56 | print(e.message);
57 | return getErrorModel(2);
58 | }
59 | }
60 | }
61 |
62 | ///post网络请求
63 | static Future post(String url, Map map, [List params]) async {
64 | ///页码
65 | if (params != null) {
66 | url = fillPage(url, params);
67 | }
68 |
69 | try {
70 | Response response = await dio.post(url, data: map);
71 |
72 | if (response.statusCode == HttpStatus.OK) {
73 | print('post reponse.body ${response.data}');
74 |
75 | ///Response.data已经解析为Map数据类型了
76 | Map map = response.data;
77 |
78 | ///Response.headers 请求头数据
79 | print(response.headers['set-cookie']);///返回的是List的
80 |
81 | // ///登录请求,请求成功后保存cookie,后面可以根据cookie做自动登录
82 | // if (map['errorCode'] == 0 && url == login_api) {
83 | // print(response.headers.cookies);
84 | // setCookie(response.headers.cookies);
85 | // }
86 | return new DataModel(
87 | data: map['data'],
88 | errorCode: map['errorCode'],
89 | errorMsg: map['errorMsg']);
90 | } else {
91 | print('url :$url \n错误msg :\n${response.statusCode}');
92 | return getErrorModel();
93 | }
94 | } on DioError catch (e) {
95 | ///网络请求超时异常
96 | if (e.type == DioErrorType.CONNECT_TIMEOUT) {
97 | print('---Error DioErrorType connect timeOut---');
98 | return getErrorModel(1);
99 | }
100 |
101 | ///DioError.response响应信息,如果发生在服务器返回数据之前,则为null
102 | if (e.response != null) {
103 | print('---Error response is not null------------');
104 | print(e.response.data);
105 | print(e.response.headers);
106 | print(e.response.request);
107 | return getErrorModel();
108 | } else {
109 | ///无网络的请求下跳转到这里,抛出SocketException 异常
110 |
111 | ///事件发生在设置或发送请求,触发一个错误
112 | /// Something happened in setting up or sending the request that triggered an Error
113 | print('---Error response is null------------------');
114 | print(e.message);
115 | return getErrorModel(2);
116 | }
117 | }
118 | }
119 |
120 | static DataModel getErrorModel([int tag = 0]) {
121 | String errorMsg;
122 | switch (tag) {
123 | case 0:
124 | errorMsg = '网络请求错误,请稍后重试';
125 | break;
126 | case 1:
127 | errorMsg = '网络请求超时,请稍后重试';
128 | break;
129 | case 2:
130 | errorMsg = '网络未连接,请检查网络后重试';
131 | break;
132 | default:
133 | errorMsg = '未知异常';
134 | break;
135 | }
136 | return new DataModel(data: null, errorCode: -1, errorMsg: errorMsg);
137 | }
138 |
139 | static String fillPage(String url, List params) {
140 |
141 | int index=0;
142 | for(int param in params){
143 | if (url.contains('{param}',index)) {
144 | index=url.indexOf(new RegExp(r'{param}'))+'{param}'.length;
145 |
146 | url=url.replaceFirst(new RegExp(r'{param}'), param.toString());
147 | }
148 | }
149 |
150 | return url;
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/utils/log.dart:
--------------------------------------------------------------------------------
1 |
2 |
3 | class Log{
4 |
5 | static String fixInt(int num, [int fix = 2]) {
6 | String str = num.toString();
7 | fix -= str.length;
8 |
9 | return '${'0' * fix}$str';
10 | }
11 |
12 | static d(StackTrace stackTrace, Object obj) {
13 | // StackTrack中的第一行数据
14 | String st = stackTrace.toString().split('\n')[0];
15 | // 方法和文件
16 | String methodAFile=st.substring('#0'.length).trim();
17 | String method = methodAFile.substring(0,methodAFile.indexOf(' '));
18 | String file = methodAFile.substring(methodAFile.indexOf('(')+1,methodAFile.indexOf(')'));
19 | DateTime time = new DateTime.now();
20 | print('''
21 | --------------------------------------------------------------------------------
22 | Method : $method
23 | File : file://$file
24 | ${fixInt(time.hour)}:${fixInt(time.minute)}:${fixInt(time.second)}.${fixInt(time.millisecond,3)}
25 | Msg:${obj.toString()}
26 | --------------------------------------------------------------------------------
27 | '''
28 | );
29 | }
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/utils/shares_util.dart:
--------------------------------------------------------------------------------
1 | import '../common/config.dart';
2 | import 'package:shared_preferences/shared_preferences.dart';
3 |
4 | Future getCookie() async {
5 | SharedPreferences prefs = await SharedPreferences.getInstance();
6 | return prefs.getString('set-cookie');
7 | }
8 |
9 | setCookie(String cookie) async {
10 | SharedPreferences prefs = await SharedPreferences.getInstance();
11 | await prefs.setString('set-cookie', cookie);
12 | }
13 |
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/utils/snackbar_util.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 |
4 | class SnackBarUtil{
5 |
6 | static showInSnackBar(GlobalKey scaffoldKey,String value){
7 | ///隐藏之前要显示或将要显示的Snacker
8 | scaffoldKey.currentState
9 | .hideCurrentSnackBar(reason: SnackBarClosedReason.hide);
10 |
11 | ///这个currentState一定要是Scaffold,否则将找不到showSnacker方法
12 | scaffoldKey.currentState
13 | .showSnackBar(new SnackBar(content: new Text(value)));
14 |
15 | }
16 | }
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/widgets/pull_to_refresh/pull_to_refresh.dart:
--------------------------------------------------------------------------------
1 | /*
2 | Author: Jpeng
3 | Email: peng8350@gmail.com
4 | createTime:2018-05-03 15:39
5 | */
6 |
7 | library pulltorefresh;
8 |
9 | export 'src/smart_refresher.dart';
10 | export 'src/indicator/classic_indicator.dart';
11 | export 'src/internals/indicator_config.dart';
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/widgets/pull_to_refresh/src/indicator/classic_indicator.dart:
--------------------------------------------------------------------------------
1 | /**
2 | Author: Jpeng
3 | Email: peng8350@gmail.com
4 | createTime:2018-05-14 17:39
5 | */
6 |
7 | import 'package:flutter/material.dart' hide RefreshIndicator;
8 | import 'package:flutter/widgets.dart';
9 | import 'package:flutter_wanandroid/widgets/pull_to_refresh/pull_to_refresh.dart';
10 | import 'package:meta/meta.dart';
11 |
12 | enum IconPosition { left, right, top, bottom }
13 |
14 | class ClassicIndicator extends Indicator {
15 | final String releaseText,
16 | idleText,
17 | refreshingText,
18 | completeText,
19 | failedText,
20 | noDataText;
21 |
22 | final Widget releaseIcon,
23 | idleIcon,
24 | refreshingIcon,
25 | completeIcon,
26 | failedIcon,
27 | noMoreIcon;
28 |
29 | final double height;
30 |
31 | final double spacing;
32 |
33 | final IconPosition iconPos;
34 |
35 | final TextStyle textStyle;
36 |
37 | const ClassicIndicator(
38 | {@required int mode,
39 | Key key,
40 | this.textStyle: const TextStyle(color: const Color(0xff555555)),
41 | this.releaseText: 'Refresh when release',
42 | this.refreshingText: 'Refreshing...',
43 | this.completeText: 'Refresh complete!',
44 | this.noDataText: 'No more data',
45 | this.height: 60.0,
46 | this.noMoreIcon: const Icon(Icons.clear, color: Colors.grey),
47 | this.failedText: 'Refresh failed',
48 | this.idleText: 'Pull down to refresh',
49 | this.iconPos: IconPosition.left,
50 | this.spacing: 15.0,
51 | this.refreshingIcon: const CircularProgressIndicator(strokeWidth: 2.0),
52 | this.failedIcon: const Icon(Icons.clear, color: Colors.grey),
53 | this.completeIcon: const Icon(Icons.done, color: Colors.grey),
54 | this.idleIcon = const Icon(Icons.arrow_downward, color: Colors.grey),
55 | this.releaseIcon = const Icon(Icons.arrow_upward, color: Colors.grey),
56 | int completeTime: 800,
57 | double visibleRange: 60.0,
58 | double triggerDistance: 80.0})
59 | : super(key: key, mode: mode);
60 |
61 | @override
62 | State createState() {
63 | // TODO: implement createState
64 | return new _ClassicIndicatorState();
65 | }
66 | }
67 |
68 | class _ClassicIndicatorState extends State {
69 | Widget _buildText() {
70 | return new Text(
71 | widget.mode == RefreshStatus.canRefresh
72 | ? widget.releaseText
73 | : widget.mode == RefreshStatus.completed
74 | ? widget.completeText
75 | : widget.mode == RefreshStatus.failed
76 | ? widget.failedText
77 | : widget.mode == RefreshStatus.refreshing
78 | ? widget.refreshingText:widget.mode==RefreshStatus.noMore?widget.noDataText
79 | : widget.idleText,
80 | style: widget.textStyle);
81 | }
82 |
83 | Widget _buildIcon() {
84 | Widget icon = widget.mode == RefreshStatus.canRefresh
85 | ? widget.releaseIcon:widget.mode==RefreshStatus.noMore?widget.noMoreIcon
86 | : widget.mode == RefreshStatus.idle
87 | ? widget.idleIcon
88 | : widget.mode == RefreshStatus.completed
89 | ? widget.completeIcon
90 | : widget.mode == RefreshStatus.failed
91 | ? widget.failedIcon
92 | : new SizedBox(
93 | width: 25.0,
94 | height: 25.0,
95 | child:
96 | const CircularProgressIndicator(strokeWidth: 2.0),
97 | );
98 | return icon;
99 | }
100 |
101 | @override
102 | Widget build(BuildContext context) {
103 | // TODO: implement buildContent
104 | Widget textWidget = _buildText();
105 | Widget iconWidget = _buildIcon();
106 | List childrens = [
107 | iconWidget,
108 | new Container(
109 | width: widget.spacing,
110 | height: widget.spacing,
111 | ),
112 | textWidget
113 | ];
114 | Widget container = (widget.iconPos == IconPosition.top ||
115 | widget.iconPos == IconPosition.bottom)
116 | ? new Column(
117 | mainAxisAlignment: MainAxisAlignment.center,
118 | verticalDirection: widget.iconPos == IconPosition.top
119 | ? VerticalDirection.down
120 | : VerticalDirection.up,
121 | children: childrens,
122 | )
123 | : new Row(
124 | textDirection: widget.iconPos == IconPosition.right
125 | ? TextDirection.rtl
126 | : TextDirection.ltr,
127 | mainAxisAlignment: MainAxisAlignment.center,
128 | children: childrens,
129 | );
130 | return new Container(
131 | alignment: Alignment.center,
132 | height: widget.height,
133 | child: new Center(
134 | child: container,
135 | ),
136 | );
137 | }
138 |
139 | @override
140 | void initState() {
141 | // TODO: implement initState
142 | super.initState();
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/widgets/pull_to_refresh/src/internals/default_constants.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/widgets.dart';
2 |
3 | /*
4 | Author: Jpeng
5 | Email: peng8350@gmail.com
6 | createTime:2018-05-17 10:39
7 | */
8 |
9 |
10 | typedef void OnRefresh(bool up);
11 | typedef void OnOffsetChange(bool up, double offset);
12 | typedef Widget IndicatorBuilder(BuildContext context, int mode);
13 |
14 | const int default_completeDuration = 800;
15 |
16 | const double default_refresh_triggerDistance = 100.0;
17 |
18 | const double default_load_triggerDistance = 5.0;
19 |
20 | const double default_VisibleRange = 50.0;
21 |
22 | const bool default_AutoLoad = true;
23 |
24 | const bool default_enablePullDown = true;
25 |
26 | const bool default_enablePullUp = false;
27 |
28 | const bool default_BottomWhenBuild = true;
29 |
30 | const bool default_enableOverScroll = true;
31 |
32 | const int spaceAnimateMill=300;
33 |
34 | const double minSpace = 0.000001;
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/widgets/pull_to_refresh/src/internals/indicator_config.dart:
--------------------------------------------------------------------------------
1 | /*
2 | Author: Jpeng
3 | Email: peng8350@gmail.com
4 | createTime:2018-05-14 15:39
5 | */
6 |
7 | import 'default_constants.dart';
8 |
9 | /*
10 | * This will use to configure the Wrapper action
11 | */
12 | abstract class Config {
13 | // How many distances should be dragged to trigger refresh
14 | final double triggerDistance;
15 |
16 | const Config({this.triggerDistance});
17 | }
18 |
19 | class RefreshConfig extends Config {
20 | // display time of success or failed
21 | final int completeDuration;
22 | // emptySpace height
23 | final double visibleRange;
24 | const RefreshConfig(
25 | {this.visibleRange: default_VisibleRange,
26 | double triggerDistance: default_refresh_triggerDistance,
27 | this.completeDuration: default_completeDuration})
28 | : super(triggerDistance: triggerDistance);
29 | }
30 |
31 | class LoadConfig extends Config {
32 | // if autoLoad when touch outside
33 | final bool autoLoad;
34 | // Whether the interface is at the bottom when the interface is loaded
35 | final bool bottomWhenBuild;
36 |
37 | final bool enableOverScroll;
38 | const LoadConfig({
39 | this.autoLoad: default_AutoLoad,
40 | this.bottomWhenBuild:default_BottomWhenBuild,
41 | this.enableOverScroll :default_enableOverScroll,
42 | double triggerDistance: default_load_triggerDistance,
43 | }) : super(triggerDistance: triggerDistance);
44 | }
45 |
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/widgets/pull_to_refresh/src/internals/indicator_wrap.dart:
--------------------------------------------------------------------------------
1 | /*
2 | Author: Jpeng
3 | Email: peng8350@gmail.com
4 | createTime:2018-05-14 15:39
5 | */
6 |
7 | import 'dart:async';
8 | import 'package:flutter/widgets.dart';
9 | import 'package:flutter/material.dart';
10 | import 'default_constants.dart';
11 | import 'package:flutter_wanandroid/widgets/pull_to_refresh/pull_to_refresh.dart';
12 | import 'package:meta/meta.dart';
13 |
14 | abstract class Wrapper extends StatefulWidget {
15 | final ValueNotifier modeListener;
16 |
17 | final IndicatorBuilder builder;
18 |
19 | final bool up;
20 |
21 | final double triggerDistance;
22 |
23 | bool get _isRefreshing => this.mode == RefreshStatus.refreshing;
24 |
25 | bool get _isComplete =>
26 | this.mode != RefreshStatus.idle &&
27 | this.mode != RefreshStatus.refreshing &&
28 | this.mode != RefreshStatus.canRefresh;
29 |
30 | int get mode => this.modeListener.value;
31 |
32 | set mode(int mode) => this.modeListener.value = mode;
33 |
34 | Wrapper(
35 | {Key key,
36 | @required this.up,
37 | @required this.modeListener,
38 | this.builder,
39 | this.triggerDistance})
40 | : assert(up != null, modeListener != null),
41 | super(key: key);
42 |
43 | bool _isScrollToOutSide(ScrollNotification notification) {
44 | if (up) {
45 | if (notification.metrics.minScrollExtent - notification.metrics.pixels >
46 | 0) {
47 | return true;
48 | }
49 | } else {
50 | if (notification.metrics.pixels - notification.metrics.maxScrollExtent >
51 | 0) {
52 | return true;
53 | }
54 | }
55 | return false;
56 | }
57 | }
58 |
59 | //idle,refreshing,completed,failed,canRefresh
60 | class RefreshWrapper extends Wrapper {
61 | final int completeDuration;
62 |
63 | final Function onOffsetChange;
64 |
65 | final double visibleRange;
66 |
67 | RefreshWrapper({
68 | Key key,
69 | IndicatorBuilder builder,
70 | ValueNotifier modeLis,
71 | this.onOffsetChange,
72 | this.completeDuration: default_completeDuration,
73 | double triggerDistance,
74 | this.visibleRange: default_VisibleRange,
75 | bool up: true,
76 | }) : assert(up != null),
77 | super(
78 | up: up,
79 | key: key,
80 | modeListener: modeLis,
81 | builder: builder,
82 | triggerDistance: triggerDistance,
83 | );
84 |
85 | @override
86 | State createState() {
87 | // TODO: implement createState
88 | return new RefreshWrapperState();
89 | }
90 | }
91 |
92 | class RefreshWrapperState extends State
93 | with TickerProviderStateMixin
94 | implements GestureProcessor {
95 | AnimationController _sizeController;
96 |
97 | /*
98 | up indicate drag from top (pull down)
99 | */
100 | void _dismiss() {
101 | /*
102 | why the value is 0.00001?
103 | If this value is 0, no controls will
104 | cause Flutter to automatically retrieve widget.
105 | */
106 | _sizeController.animateTo(minSpace).then((Null val) {
107 | widget.mode = RefreshStatus.idle;
108 | });
109 | }
110 |
111 | int get mode => widget.modeListener.value;
112 |
113 | double _measure(ScrollNotification notification) {
114 | if (widget.up) {
115 | return (notification.metrics.minScrollExtent -
116 | notification.metrics.pixels) /
117 | widget.triggerDistance;
118 | } else {
119 | return (notification.metrics.pixels -
120 | notification.metrics.maxScrollExtent) /
121 | widget.triggerDistance;
122 | }
123 | }
124 |
125 | @override
126 | void onDragStart(ScrollStartNotification notification) {
127 | // TODO: implement onDragStart
128 | }
129 |
130 | @override
131 | void onDragMove(ScrollUpdateNotification notification) {
132 | // TODO: implement onDragMove
133 | if (!widget._isScrollToOutSide(notification)) {
134 | return;
135 | }
136 | if (widget._isComplete || widget._isRefreshing) return;
137 |
138 | double offset = _measure(notification);
139 | if (offset >= 1.0) {
140 | widget.mode = RefreshStatus.canRefresh;
141 | } else {
142 | widget.mode = RefreshStatus.idle;
143 | }
144 | }
145 |
146 | @override
147 | void onDragEnd(ScrollNotification notification) {
148 | // TODO: implement onDragEnd
149 | if (!widget._isScrollToOutSide(notification)) {
150 | return;
151 | }
152 | if (widget._isComplete || widget._isRefreshing) return;
153 | bool reachMax = _measure(notification) >= 1.0;
154 | if (!reachMax) {
155 | _sizeController.animateTo(0.0);
156 | return;
157 | } else {
158 | widget.mode = RefreshStatus.refreshing;
159 | }
160 | }
161 |
162 | @override
163 | void initState() {
164 | // TODO: implement initState
165 | super.initState();
166 | this._sizeController = new AnimationController(
167 | vsync: this,
168 | lowerBound: minSpace,
169 | duration: const Duration(milliseconds: spaceAnimateMill));
170 | widget.modeListener.addListener(() {
171 | switch (mode) {
172 | case RefreshStatus.refreshing:
173 | _sizeController.value = 1.0;
174 | break;
175 | case RefreshStatus.completed:
176 | new Future.delayed(
177 | new Duration(milliseconds: widget.completeDuration), () {
178 | _dismiss();
179 | });
180 | break;
181 | case RefreshStatus.failed:
182 | new Future.delayed(
183 | new Duration(milliseconds: widget.completeDuration), () {
184 | _dismiss();
185 | }).then((val) {
186 | widget.mode = RefreshStatus.idle;
187 | });
188 | break;
189 | }
190 | setState(() {});
191 | });
192 | }
193 |
194 | @override
195 | Widget build(BuildContext context) {
196 | // TODO: implement build
197 | if (widget.up) {
198 | return new Column(
199 | children: [
200 | new SizeTransition(
201 | sizeFactor: _sizeController,
202 | child: new Container(height: widget.visibleRange),
203 | ),
204 | widget.builder(context, widget.mode)
205 | ],
206 | );
207 | }
208 | return new Column(
209 | children: [
210 | widget.builder(context, widget.mode),
211 | new SizeTransition(
212 | sizeFactor: _sizeController,
213 | child: new Container(height: widget.visibleRange),
214 | )
215 | ],
216 | );
217 | }
218 | }
219 |
220 | //status: failed,nomore,idle,refreshing
221 | class LoadWrapper extends Wrapper {
222 | final bool autoLoad;
223 |
224 | LoadWrapper(
225 | {Key key,
226 | @required bool up,
227 | @required ValueNotifier modeListener,
228 | double triggerDistance,
229 | this.autoLoad,
230 | IndicatorBuilder builder})
231 | : assert(up != null, modeListener != null),
232 | super(
233 | key: key,
234 | up: up,
235 | builder: builder,
236 | modeListener: modeListener,
237 | triggerDistance: triggerDistance,
238 | );
239 |
240 | @override
241 | State createState() {
242 | // TODO: implement createState
243 | return new LoadWrapperState();
244 | }
245 | }
246 |
247 | class LoadWrapperState extends State implements GestureProcessor {
248 | @override
249 | Widget build(BuildContext context) {
250 | // TODO: implement build
251 | return widget.builder(context, widget.mode);
252 | }
253 |
254 | @override
255 | void initState() {
256 | // TODO: implement initState
257 | super.initState();
258 | widget.modeListener.addListener(() {
259 | setState(() {});
260 | });
261 | }
262 |
263 | @override
264 | void onDragStart(ScrollStartNotification notification) {
265 | // TODO: implement onDragStart
266 | }
267 |
268 | @override
269 | void onDragMove(ScrollUpdateNotification notification) {
270 | // TODO: implement onDragMove
271 | // if (!widget._isScrollToOutSide(notification)) {
272 | // return;
273 | // }
274 | if (widget._isRefreshing || widget._isComplete) return;
275 | if (widget.autoLoad) {
276 | if (widget.up && notification.metrics.extentBefore <= widget.triggerDistance)
277 | widget.mode = RefreshStatus.refreshing;
278 | if (!widget.up && notification.metrics.extentAfter <= widget.triggerDistance)
279 | widget.mode = RefreshStatus.refreshing;
280 | }
281 | }
282 |
283 | @override
284 | void onDragEnd(ScrollNotification notification) {
285 | // TODO: implement onDragEnd
286 | }
287 | }
288 |
289 | abstract class GestureProcessor {
290 | void onDragStart(ScrollStartNotification notification);
291 |
292 | void onDragMove(ScrollUpdateNotification notification);
293 |
294 | void onDragEnd(ScrollNotification notification);
295 | }
296 |
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/widgets/pull_to_refresh/src/internals/refresh_physics.dart:
--------------------------------------------------------------------------------
1 | /*
2 | Author: Jpeng
3 | Email: peng8350@gmail.com
4 | createTime:2018-05-02 14:39
5 | */
6 |
7 | import 'package:flutter/widgets.dart';
8 | import 'dart:math' as math;
9 | import 'package:flutter/material.dart';
10 |
11 | /*
12 | this class is copy from BouncingScrollPhysics,
13 | because it doesn't fit my idea,
14 | Fixed the problem that child parts could not be dragged without data.
15 | */
16 | class RefreshScrollPhysics extends ScrollPhysics {
17 |
18 | final bool enableOverScroll;
19 |
20 | /// Creates scroll physics that bounce back from the edge.
21 | const RefreshScrollPhysics({ScrollPhysics parent,this.enableOverScroll:true}) : super(parent: parent);
22 |
23 | @override
24 | RefreshScrollPhysics applyTo(ScrollPhysics ancestor) {
25 | return new RefreshScrollPhysics(parent: buildParent(ancestor),enableOverScroll: enableOverScroll);
26 | }
27 |
28 | /// The multiple applied to overscroll to make it appear that scrolling past
29 | /// the edge of the scrollable contents is harder than scrolling the list.
30 | /// This is done by reducing the ratio of the scroll effect output vs the
31 | /// scroll gesture input.
32 | ///
33 | /// This factor starts at 0.52 and progressively becomes harder to overscroll
34 | /// as more of the area past the edge is dragged in (represented by an increasing
35 | /// `overscrollFraction` which starts at 0 when there is no overscroll).
36 | double frictionFactor(double overscrollFraction) =>
37 | 0.52 * math.pow(1 - overscrollFraction, 2);
38 |
39 | @override
40 | bool shouldAcceptUserOffset(ScrollMetrics position) {
41 | // TODO: implement shouldAcceptUserOffset
42 | return true;
43 | }
44 |
45 | @override
46 | double applyPhysicsToUserOffset(ScrollMetrics position, double offset) {
47 | assert(offset != 0.0);
48 | assert(position.minScrollExtent <= position.maxScrollExtent);
49 |
50 | if (!position.outOfRange) return offset;
51 |
52 | final double overscrollPastStart =
53 | math.max(position.minScrollExtent - position.pixels, 0.0);
54 | final double overscrollPastEnd =
55 | math.max(position.pixels - position.maxScrollExtent, 0.0);
56 | final double overscrollPast =
57 | math.max(overscrollPastStart, overscrollPastEnd);
58 | final bool easing = (overscrollPastStart > 0.0 && offset < 0.0) ||
59 | (overscrollPastEnd > 0.0 && offset > 0.0);
60 |
61 | final double friction = easing
62 | // Apply less resistance when easing the overscroll vs tensioning.
63 | ? frictionFactor(
64 | (overscrollPast - offset.abs()) / position.viewportDimension)
65 | : frictionFactor(overscrollPast / position.viewportDimension);
66 | final double direction = offset.sign;
67 | return direction * _applyFriction(overscrollPast, offset.abs(), friction);
68 | }
69 |
70 | static double _applyFriction(
71 | double extentOutside, double absDelta, double gamma) {
72 |
73 | assert(absDelta > 0);
74 | double total = 0.0;
75 | if (extentOutside > 0) {
76 | final double deltaToLimit = extentOutside / gamma;
77 | if (absDelta < deltaToLimit) return absDelta * gamma;
78 | total += extentOutside;
79 | absDelta -= deltaToLimit;
80 | }
81 | return total + absDelta;
82 | }
83 |
84 | @override
85 | double applyBoundaryConditions(ScrollMetrics position, double value) {
86 | if(!enableOverScroll) {
87 | if (value < position.pixels &&
88 | position.pixels <= position.minScrollExtent) // underscroll
89 | return value - position.pixels;
90 | if (value < position.minScrollExtent &&
91 | position.minScrollExtent < position.pixels) // hit top edge
92 | return value - position.minScrollExtent;
93 | if (position.maxScrollExtent <= position.pixels &&
94 | position.pixels < value) // overscroll
95 | return value - position.pixels;
96 |
97 | if (position.pixels < position.maxScrollExtent &&
98 | position.maxScrollExtent < value) // hit bottom edge
99 | return value - position.maxScrollExtent;
100 | }
101 | return 0.0;
102 |
103 | }
104 |
105 | @override
106 | Simulation createBallisticSimulation(
107 | ScrollMetrics position, double velocity) {
108 | final Tolerance tolerance = this.tolerance;
109 | if (velocity.abs() >= tolerance.velocity || position.outOfRange) {
110 | return new BouncingScrollSimulation(
111 | spring: spring,
112 | position: position.pixels,
113 | velocity: velocity *
114 | 0.91, // TODO(abarth): We should move this constant closer to the drag end.
115 | leadingExtent: position.minScrollExtent,
116 | trailingExtent: position.maxScrollExtent,
117 | tolerance: tolerance,
118 | );
119 | }
120 | return null;
121 | }
122 |
123 | // The ballistic simulation here decelerates more slowly than the one for
124 | // ClampingScrollPhysics so we require a more deliberate input gesture
125 | // to trigger a fling.
126 | @override
127 | double get minFlingVelocity => 2.5 * 2.0;
128 |
129 | // Methodology:
130 | // 1- Use https://github.com/flutter/scroll_overlay to test with Flutter and
131 | // platform scroll views superimposed.
132 | // 2- Record incoming speed and make rapid flings in the test app.
133 | // 3- If the scrollables stopped overlapping at any moment, adjust the desired
134 | // output value of this function at that input speed.
135 | // 4- Feed new input/output set into a power curve fitter. Change function
136 | // and repeat from 2.
137 | // 5- Repeat from 2 with medium and slow flings.
138 | /// Momentum build-up function that mimics iOS's scroll speed increase with repeated flings.
139 | ///
140 | /// The velocity of the last fling is not an important factor. Existing speed
141 | /// and (related) time since last fling are factors for the velocity transfer
142 | /// calculations.
143 | @override
144 | double carriedMomentum(double existingVelocity) {
145 | return existingVelocity.sign *
146 | math.min(0.000816 * math.pow(existingVelocity.abs(), 1.967).toDouble(),
147 | 40000.0);
148 | }
149 |
150 | // Eyeballed from observation to counter the effect of an unintended scroll
151 | // from the natural motion of lifting the finger after a scroll.
152 | @override
153 | double get dragStartDistanceMotionThreshold => 3.5;
154 | }
155 |
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/widgets/pull_to_refresh/src/smart_refresher.dart:
--------------------------------------------------------------------------------
1 | /*
2 | Author: Jpeng
3 | Email: peng8350@gmail.com
4 | createTime:2018-05-01 11:39
5 | */
6 |
7 | import 'package:flutter/material.dart';
8 | import 'package:flutter/scheduler.dart';
9 | import 'internals/default_constants.dart';
10 | import 'package:flutter/widgets.dart';
11 | import 'package:flutter/foundation.dart';
12 | import '../src/internals/indicator_config.dart';
13 | import '../src/internals/indicator_wrap.dart';
14 | import '../src/internals/refresh_physics.dart';
15 |
16 |
17 |
18 | enum WrapperType { Refresh, Loading }
19 |
20 | class RefreshStatus {
21 | static const int idle = 0;
22 | static const int canRefresh = 1;
23 | static const int refreshing = 2;
24 | static const int completed = 3;
25 | static const int failed = 4;
26 | static const int noMore = 5;
27 | }
28 |
29 |
30 | /*
31 | This is the most important component that provides drop-down refresh and up loading.
32 | */
33 | class SmartRefresher extends StatefulWidget {
34 | //indicate your listView
35 | final Widget child;
36 |
37 | final IndicatorBuilder headerBuilder;
38 | final IndicatorBuilder footerBuilder;
39 | // configure your header and footer
40 | final Config headerConfig, footerConfig;
41 | // This bool will affect whether or not to have the function of drop-up load.
42 | final bool enablePullUp;
43 | //This bool will affect whether or not to have the function of drop-down refresh.
44 | final bool enablePullDown;
45 | // if open OverScroll if you use RefreshIndicator and LoadFooter
46 | final bool enableOverScroll;
47 | // upper and downer callback when you drag out of the distance
48 | final OnRefresh onRefresh;
49 | // This method will callback when the indicator changes from edge to edge.
50 | final OnOffsetChange onOffsetChange;
51 | //controll inner state
52 | final RefreshController controller;
53 |
54 | SmartRefresher({
55 | Key key,
56 | @required this.child,
57 | this.headerBuilder,
58 | this.footerBuilder,
59 | RefreshController controller,
60 | this.headerConfig: const RefreshConfig(),
61 | this.footerConfig: const LoadConfig(),
62 | this.enableOverScroll:default_enableOverScroll,
63 | this.enablePullDown: default_enablePullDown,
64 | this.enablePullUp: default_enablePullUp,
65 | this.onRefresh,
66 | this.onOffsetChange,
67 | }) : assert(child != null),
68 | controller = controller ?? new RefreshController(),super(key: key);
69 |
70 |
71 | @override
72 | _SmartRefresherState createState() => new _SmartRefresherState();
73 | }
74 |
75 | class _SmartRefresherState extends State {
76 | // listen the listen offset or on...
77 | ScrollController _scrollController;
78 | // the bool will check the user if dragging on the screen.
79 | bool _isDragging = false;
80 | // key to get height header of footer
81 | final GlobalKey _headerKey = new GlobalKey(), _footerKey = new GlobalKey();
82 | // the height must be equals your headerBuilder
83 | double _headerHeight = 0.0, _footerHeight = 0.0;
84 |
85 | ValueNotifier offsetLis = new ValueNotifier(0.0);
86 |
87 | ValueNotifier topModeLis = new ValueNotifier(0);
88 |
89 | ValueNotifier bottomModeLis =new ValueNotifier(0);
90 |
91 | //handle the scrollStartEvent
92 | bool _handleScrollStart(ScrollStartNotification notification) {
93 | // This is used to interupt useless callback when the pull up load rolls back.
94 | if ((notification.metrics.outOfRange)) {
95 | return false;
96 | }
97 | GestureProcessor topWrap = _headerKey.currentState as GestureProcessor;
98 | GestureProcessor bottomWrap = _footerKey.currentState as GestureProcessor;
99 | if (widget.enablePullUp) bottomWrap.onDragStart(notification);
100 | if (widget.enablePullDown) topWrap.onDragStart(notification);
101 | return false;
102 | }
103 |
104 | //handle the scrollMoveEvent
105 | bool _handleScrollMoving(ScrollUpdateNotification notification) {
106 | bool down = _isPullDown(notification);
107 | if (down) {
108 | if (widget.onOffsetChange != null)
109 | widget.onOffsetChange(notification.metrics.extentBefore == 0,
110 | notification.metrics.minScrollExtent - notification.metrics.pixels);
111 | } else {
112 | if (widget.onOffsetChange != null)
113 | widget.onOffsetChange(notification.metrics.extentAfter == 0,
114 | notification.metrics.pixels - notification.metrics.maxScrollExtent);
115 | }
116 | if (_measure(notification) != -1.0) offsetLis.value = _measure(notification);
117 | GestureProcessor topWrap = _headerKey.currentState as GestureProcessor;
118 | GestureProcessor bottomWrap = _footerKey.currentState as GestureProcessor;
119 | if (widget.enablePullUp) bottomWrap.onDragMove(notification);
120 | if (widget.enablePullDown) topWrap.onDragMove(notification);
121 | return false;
122 | }
123 |
124 | //handle the scrollEndEvent
125 | bool _handleScrollEnd(ScrollNotification notification) {
126 | GestureProcessor topWrap = _headerKey.currentState as GestureProcessor;
127 | GestureProcessor bottomWrap = _footerKey.currentState as GestureProcessor;
128 | if (widget.enablePullUp) bottomWrap.onDragEnd(notification);
129 | if (widget.enablePullDown) topWrap.onDragEnd(notification);
130 | return false;
131 | }
132 |
133 | bool _dispatchScrollEvent(ScrollNotification notification) {
134 | // when is scroll in the ScrollInside,nothing to do
135 | if ((!_isPullUp(notification) && !_isPullDown(notification))) return false;
136 | if (notification is ScrollStartNotification) {
137 | return _handleScrollStart(notification);
138 | }
139 | if (notification is ScrollUpdateNotification) {
140 | //if dragDetails is null,This represents the user's finger out of the screen
141 | if (notification.dragDetails == null) {
142 | return _handleScrollEnd(notification);
143 | } else if (notification.dragDetails != null) {
144 | return _handleScrollMoving(notification);
145 | }
146 | }
147 | if (notification is ScrollEndNotification) {
148 | print("end");
149 | _handleScrollEnd(notification);
150 | }
151 |
152 | return false;
153 | }
154 |
155 |
156 | //check user is pulling up
157 | bool _isPullUp(ScrollNotification noti) {
158 | return noti.metrics.pixels < 0;
159 | }
160 |
161 | //check user is pulling down
162 | bool _isPullDown(ScrollNotification noti) {
163 | return noti.metrics.pixels > 0;
164 | }
165 |
166 | double _measure(ScrollNotification notification) {
167 | if (notification.metrics.minScrollExtent - notification.metrics.pixels >
168 | 0) {
169 | return (notification.metrics.minScrollExtent -
170 | notification.metrics.pixels) /
171 | widget.headerConfig.triggerDistance;
172 | } else if (notification.metrics.pixels -
173 | notification.metrics.maxScrollExtent >
174 | 0) {
175 | return (notification.metrics.pixels -
176 | notification.metrics.maxScrollExtent) /
177 | widget.footerConfig.triggerDistance;
178 | }
179 | return -1.0;
180 | }
181 |
182 | void _init() {
183 | _scrollController = new ScrollController();
184 | widget.controller.scrollController =_scrollController;
185 | SchedulerBinding.instance.addPostFrameCallback((_) {
186 | _onAfterBuild();
187 | });
188 | widget.controller._headerMode = topModeLis;
189 | widget.controller._footerMode = bottomModeLis;
190 | }
191 |
192 | _didChangeMode(bool up, ValueNotifier mode) {
193 | switch (mode.value) {
194 | case RefreshStatus.refreshing:
195 | if (widget.onRefresh != null) {
196 | widget.onRefresh(up);
197 | }
198 | if(up&&widget.headerConfig is RefreshConfig){
199 | RefreshConfig config = widget.headerConfig as RefreshConfig;
200 | _scrollController.jumpTo(_scrollController.offset+config.visibleRange);
201 | }
202 | break;
203 | }
204 | }
205 |
206 | void _onAfterBuild() {
207 | if(widget.headerConfig is LoadConfig){
208 | if((widget.headerConfig as LoadConfig).bottomWhenBuild){
209 | _scrollController.jumpTo(-(_scrollController.position.pixels-_scrollController.position.maxScrollExtent));
210 | }
211 | }
212 |
213 | topModeLis.addListener(() {
214 | _didChangeMode(true, topModeLis);
215 | });
216 | bottomModeLis.addListener(() {
217 | _didChangeMode(false, bottomModeLis);
218 | });
219 | setState(() {
220 | if (widget.enablePullDown)
221 | _headerHeight = _headerKey.currentContext.size.height;
222 | if (widget.enablePullUp) {
223 | _footerHeight = _footerKey.currentContext.size.height;
224 | }
225 | });
226 | }
227 |
228 | @override
229 | void dispose() {
230 | // TODO: implement dispose
231 | _scrollController.dispose();
232 | super.dispose();
233 | }
234 |
235 | @override
236 | void initState() {
237 | // TODO: implement initState
238 | super.initState();
239 | _init();
240 | }
241 |
242 | Widget _buildWrapperByConfig(Config config, bool up) {
243 | if (config is LoadConfig) {
244 | return new LoadWrapper(
245 | key: up ? _headerKey : _footerKey,
246 | modeListener: up ? topModeLis : bottomModeLis,
247 | up: up,
248 | autoLoad: config.autoLoad,
249 | triggerDistance: config.triggerDistance,
250 | builder: up
251 | ? widget.headerBuilder
252 | : widget.footerBuilder,
253 | );
254 | } else if (config is RefreshConfig) {
255 | return new RefreshWrapper(
256 | key: up ? _headerKey : _footerKey,
257 | modeLis: up ? topModeLis : bottomModeLis,
258 | up: up,
259 | completeDuration: config.completeDuration,
260 | triggerDistance: config.triggerDistance,
261 | visibleRange: config.visibleRange,
262 | builder: up
263 | ? widget.headerBuilder
264 | : widget.footerBuilder,
265 | );
266 | }
267 | return new Container();
268 | }
269 |
270 | @override
271 | void didUpdateWidget(SmartRefresher oldWidget) {
272 | // TODO: implement didUpdateWidget
273 | widget.controller._headerMode = topModeLis;
274 | widget.controller._footerMode = bottomModeLis;
275 | widget.controller.scrollController =_scrollController;
276 | super.didUpdateWidget(oldWidget);
277 | }
278 |
279 |
280 | @override
281 | Widget build(BuildContext context) {
282 | return new LayoutBuilder(builder: (context, cons) {
283 | return new Stack(
284 | children: [
285 | new Positioned(
286 | top: !widget.enablePullDown||widget.headerConfig is LoadConfig ? 0.0 : -_headerHeight,
287 | bottom: !widget.enablePullUp||widget.footerConfig is LoadConfig ? 0.0 : -_footerHeight,
288 | left: 0.0,
289 | right: 0.0,
290 | child: new NotificationListener(
291 | child: new SingleChildScrollView(
292 | controller: _scrollController,
293 | physics: new RefreshScrollPhysics(enableOverScroll:widget.enableOverScroll ),
294 | child: new Column(
295 | children: [
296 | widget.headerBuilder != null && widget.enablePullDown
297 | ? _buildWrapperByConfig(widget.headerConfig, true)
298 | : new Container(),
299 | new ConstrainedBox(
300 | constraints: new BoxConstraints(
301 | maxHeight: double.MAX_FINITE-1.0,
302 | minHeight: cons.biggest.height),
303 | child: widget.child,
304 | ),
305 | widget.footerBuilder != null && widget.enablePullUp
306 | ? _buildWrapperByConfig(widget.footerConfig, false)
307 | : new Container()
308 | ],
309 | )),
310 | onNotification: _dispatchScrollEvent,
311 | )),
312 | ],
313 | );
314 | });
315 | }
316 | }
317 |
318 |
319 | abstract class Indicator extends StatefulWidget {
320 |
321 | final int mode;
322 |
323 | const Indicator({Key key,this.mode}):super(key:key);
324 | }
325 |
326 | class RefreshController{
327 |
328 | ValueNotifier _headerMode ;
329 | ValueNotifier _footerMode;
330 | ScrollController scrollController;
331 |
332 | void requestRefresh(bool up){
333 | if(up) {
334 | if (_headerMode.value == RefreshStatus.idle)
335 | _headerMode.value = RefreshStatus.refreshing;
336 | }
337 | else {
338 | if (_footerMode.value == RefreshStatus.idle) {
339 | _footerMode.value = RefreshStatus.refreshing;
340 | }
341 | }
342 | }
343 |
344 | void scrollTo(double offset){
345 | scrollController.jumpTo(offset);
346 | }
347 |
348 | void sendBack(bool up,int mode){
349 | if(up){
350 | _headerMode.value = mode;
351 | }
352 | else{
353 | _footerMode.value = mode;
354 | }
355 | }
356 |
357 | int get headerMode => _headerMode.value;
358 |
359 | int get footerMode => _footerMode.value;
360 |
361 | isRefresh(bool up){
362 | if(up){
363 | return _headerMode.value==RefreshStatus
364 | .refreshing;
365 | }
366 | else{
367 | return _footerMode.value==RefreshStatus
368 | .refreshing;
369 | }
370 | }
371 |
372 |
373 | }
--------------------------------------------------------------------------------
/flutter_wanandroid/lib/widgets/xbanner.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'dart:async';
3 |
4 | ///
5 | ///XBanner 中的点击事件
6 | typedef void PageClick(int i);
7 |
8 | /// Banner widget
9 | class XBanner extends StatefulWidget {
10 | final List _pages;
11 | final PageClick pageClick;
12 | final Duration bannerDuration;
13 | final Duration bannerAnimationDuration;
14 |
15 | /// [_pages] 为 Banner 中需要展示的页面集合。
16 | /// [pageClick] 为 Banner 中页面的点击事件回调。
17 | /// [bannerDuration] Banner 中页面切换的时间间隔,默认为 2 秒。
18 | /// [bannerAnimationDuration] Banner 中每次页面切换的动画时间,默认为 1 秒。
19 | XBanner(this._pages,
20 | {this.pageClick,
21 | this.bannerDuration = const Duration(seconds: 2),
22 | this.bannerAnimationDuration = const Duration(milliseconds: 1000)});
23 |
24 | @override
25 | State createState() {
26 | return new XBannerState();
27 | }
28 | }
29 |
30 | class XBannerState extends State with SingleTickerProviderStateMixin {
31 | PageController _pageController = new PageController();
32 | Timer _timer;
33 | int _currentPage = 0;
34 | bool reverse = false;
35 | GlobalKey<_XBannerTipState> _xBannerTipStateKey = new GlobalKey();
36 |
37 | @override
38 | void initState() {
39 | super.initState();
40 | _timer = new Timer.periodic(widget.bannerDuration, (timmer) {
41 | _pageController.animateToPage(_currentPage,
42 | duration: widget.bannerAnimationDuration, curve: Curves.linear);
43 | if (!reverse) {
44 | _currentPage += 1;
45 | if (_currentPage == widget._pages.length) {
46 | _currentPage -= 1;
47 | reverse = true;
48 | }
49 | } else {
50 | _currentPage -= 1;
51 | if (_currentPage < 0) {
52 | _currentPage += 1;
53 | reverse = false;
54 | }
55 | }
56 | });
57 | }
58 |
59 | @override
60 | void dispose() {
61 | super.dispose();
62 | _timer.cancel();
63 | }
64 |
65 | @override
66 | Widget build(BuildContext context) {
67 | List pageWithClick = [];
68 | for (var i = 0; i < widget._pages.length; i++) {
69 | pageWithClick.add(new InkWell(
70 | child: widget._pages[i],
71 | onTap: () {
72 | if (widget.pageClick != null) {
73 | widget.pageClick(i);
74 | }
75 | },
76 | ));
77 | }
78 |
79 | ///两层,PageView+XBannerTip
80 | return new Stack(
81 | alignment: Alignment.topLeft,
82 | children: [
83 | new NotificationListener(
84 | onNotification: (ScrollNotification scrollNotification) {
85 | // if (scrollNotification is ScrollUpdateNotification) {
86 | // print("offset : ${_pageController.offset}");
87 | // print("position : ${_pageController.position.pixels}");
88 | // print("page : ${_pageController.page}");
89 | // }
90 | return false;
91 | },
92 | child: new PageView(
93 | controller: _pageController,
94 | children: pageWithClick,
95 | onPageChanged: (index) {
96 | _currentPage = index;
97 |
98 | ///回调方法
99 | _xBannerTipStateKey.currentState.changeTipIndex(index);
100 | },
101 | )),
102 | new Align(
103 | child: new _XBannerTip(
104 | widget._pages.length,
105 | key: _xBannerTipStateKey,
106 | ),
107 | alignment: Alignment.bottomCenter,
108 | )
109 | ],
110 | );
111 | }
112 | }
113 |
114 | ///banner提示控件
115 | class _XBannerTip extends StatefulWidget {
116 | final int _count;
117 |
118 | _XBannerTip(this._count, {Key key}) : super(key: key);
119 |
120 | @override
121 | State createState() {
122 | return new _XBannerTipState();
123 | }
124 | }
125 |
126 | class _XBannerTipState extends State<_XBannerTip> {
127 | int _index = 0;
128 |
129 | changeTipIndex(int index) {
130 | setState(() {
131 | _index = index;
132 | });
133 | }
134 |
135 | @override
136 | Widget build(BuildContext context) {
137 | List childs = [];
138 | for (int i = 0; i < widget._count; i++) {
139 | childs.add(new SizedBox(
140 | width: 7.0,
141 | height: 7.0,
142 | child: new DecoratedBox(
143 | decoration: new BoxDecoration(
144 | shape: BoxShape.circle,
145 | color: i == _index ? Colors.black : Colors.grey,
146 | )),
147 | // child: new Container(
148 | // color: i == _index ? Colors.black : Colors.grey,
149 | // ),
150 | ));
151 | }
152 |
153 | return new SizedBox(
154 | width: widget._count * 15.0,
155 | height: 15.0,
156 | child: new Row(
157 | children: childs,
158 | mainAxisAlignment: MainAxisAlignment.spaceEvenly,
159 | ),
160 | );
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/flutter_wanandroid/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://www.dartlang.org/tools/pub/glossary#lockfile
3 | packages:
4 | after_layout:
5 | dependency: "direct main"
6 | description:
7 | name: after_layout
8 | url: "https://pub.flutter-io.cn"
9 | source: hosted
10 | version: "1.0.2"
11 | analyzer:
12 | dependency: transitive
13 | description:
14 | name: analyzer
15 | url: "https://pub.flutter-io.cn"
16 | source: hosted
17 | version: "0.31.2-alpha.2"
18 | args:
19 | dependency: transitive
20 | description:
21 | name: args
22 | url: "https://pub.flutter-io.cn"
23 | source: hosted
24 | version: "1.4.3"
25 | async:
26 | dependency: transitive
27 | description:
28 | name: async
29 | url: "https://pub.flutter-io.cn"
30 | source: hosted
31 | version: "2.0.7"
32 | boolean_selector:
33 | dependency: transitive
34 | description:
35 | name: boolean_selector
36 | url: "https://pub.flutter-io.cn"
37 | source: hosted
38 | version: "1.0.3"
39 | cached_network_image:
40 | dependency: "direct main"
41 | description:
42 | name: cached_network_image
43 | url: "https://pub.flutter-io.cn"
44 | source: hosted
45 | version: "0.4.1+1"
46 | charcode:
47 | dependency: transitive
48 | description:
49 | name: charcode
50 | url: "https://pub.flutter-io.cn"
51 | source: hosted
52 | version: "1.1.1"
53 | collection:
54 | dependency: transitive
55 | description:
56 | name: collection
57 | url: "https://pub.flutter-io.cn"
58 | source: hosted
59 | version: "1.14.6"
60 | convert:
61 | dependency: transitive
62 | description:
63 | name: convert
64 | url: "https://pub.flutter-io.cn"
65 | source: hosted
66 | version: "2.0.1"
67 | cookie_jar:
68 | dependency: transitive
69 | description:
70 | name: cookie_jar
71 | url: "https://pub.flutter-io.cn"
72 | source: hosted
73 | version: "0.0.3"
74 | crypto:
75 | dependency: transitive
76 | description:
77 | name: crypto
78 | url: "https://pub.flutter-io.cn"
79 | source: hosted
80 | version: "2.0.3"
81 | csslib:
82 | dependency: transitive
83 | description:
84 | name: csslib
85 | url: "https://pub.flutter-io.cn"
86 | source: hosted
87 | version: "0.14.4"
88 | cupertino_icons:
89 | dependency: "direct main"
90 | description:
91 | name: cupertino_icons
92 | url: "https://pub.flutter-io.cn"
93 | source: hosted
94 | version: "0.1.2"
95 | dio:
96 | dependency: "direct main"
97 | description:
98 | name: dio
99 | url: "https://pub.flutter-io.cn"
100 | source: hosted
101 | version: "0.0.12"
102 | flutter:
103 | dependency: "direct main"
104 | description: flutter
105 | source: sdk
106 | version: "0.0.0"
107 | flutter_cache_manager:
108 | dependency: transitive
109 | description:
110 | name: flutter_cache_manager
111 | url: "https://pub.flutter-io.cn"
112 | source: hosted
113 | version: "0.1.1"
114 | flutter_test:
115 | dependency: "direct dev"
116 | description: flutter
117 | source: sdk
118 | version: "0.0.0"
119 | flutter_webview_plugin:
120 | dependency: "direct main"
121 | description:
122 | name: flutter_webview_plugin
123 | url: "https://pub.flutter-io.cn"
124 | source: hosted
125 | version: "0.1.5"
126 | front_end:
127 | dependency: transitive
128 | description:
129 | name: front_end
130 | url: "https://pub.flutter-io.cn"
131 | source: hosted
132 | version: "0.1.0-alpha.12"
133 | glob:
134 | dependency: transitive
135 | description:
136 | name: glob
137 | url: "https://pub.flutter-io.cn"
138 | source: hosted
139 | version: "1.1.5"
140 | html:
141 | dependency: transitive
142 | description:
143 | name: html
144 | url: "https://pub.flutter-io.cn"
145 | source: hosted
146 | version: "0.13.3"
147 | http:
148 | dependency: transitive
149 | description:
150 | name: http
151 | url: "https://pub.flutter-io.cn"
152 | source: hosted
153 | version: "0.11.3+16"
154 | http_multi_server:
155 | dependency: transitive
156 | description:
157 | name: http_multi_server
158 | url: "https://pub.flutter-io.cn"
159 | source: hosted
160 | version: "2.0.4"
161 | http_parser:
162 | dependency: transitive
163 | description:
164 | name: http_parser
165 | url: "https://pub.flutter-io.cn"
166 | source: hosted
167 | version: "3.1.2"
168 | io:
169 | dependency: transitive
170 | description:
171 | name: io
172 | url: "https://pub.flutter-io.cn"
173 | source: hosted
174 | version: "0.3.2+1"
175 | js:
176 | dependency: transitive
177 | description:
178 | name: js
179 | url: "https://pub.flutter-io.cn"
180 | source: hosted
181 | version: "0.6.1"
182 | kernel:
183 | dependency: transitive
184 | description:
185 | name: kernel
186 | url: "https://pub.flutter-io.cn"
187 | source: hosted
188 | version: "0.3.0-alpha.12"
189 | logging:
190 | dependency: transitive
191 | description:
192 | name: logging
193 | url: "https://pub.flutter-io.cn"
194 | source: hosted
195 | version: "0.11.3+1"
196 | matcher:
197 | dependency: transitive
198 | description:
199 | name: matcher
200 | url: "https://pub.flutter-io.cn"
201 | source: hosted
202 | version: "0.12.2+1"
203 | meta:
204 | dependency: transitive
205 | description:
206 | name: meta
207 | url: "https://pub.flutter-io.cn"
208 | source: hosted
209 | version: "1.1.5"
210 | mime:
211 | dependency: transitive
212 | description:
213 | name: mime
214 | url: "https://pub.flutter-io.cn"
215 | source: hosted
216 | version: "0.9.6"
217 | multi_server_socket:
218 | dependency: transitive
219 | description:
220 | name: multi_server_socket
221 | url: "https://pub.flutter-io.cn"
222 | source: hosted
223 | version: "1.0.1"
224 | node_preamble:
225 | dependency: transitive
226 | description:
227 | name: node_preamble
228 | url: "https://pub.flutter-io.cn"
229 | source: hosted
230 | version: "1.4.1"
231 | package_config:
232 | dependency: transitive
233 | description:
234 | name: package_config
235 | url: "https://pub.flutter-io.cn"
236 | source: hosted
237 | version: "1.0.3"
238 | package_resolver:
239 | dependency: transitive
240 | description:
241 | name: package_resolver
242 | url: "https://pub.flutter-io.cn"
243 | source: hosted
244 | version: "1.0.2"
245 | path:
246 | dependency: transitive
247 | description:
248 | name: path
249 | url: "https://pub.flutter-io.cn"
250 | source: hosted
251 | version: "1.5.1"
252 | path_provider:
253 | dependency: transitive
254 | description:
255 | name: path_provider
256 | url: "https://pub.flutter-io.cn"
257 | source: hosted
258 | version: "0.4.0"
259 | plugin:
260 | dependency: transitive
261 | description:
262 | name: plugin
263 | url: "https://pub.flutter-io.cn"
264 | source: hosted
265 | version: "0.2.0+2"
266 | pool:
267 | dependency: transitive
268 | description:
269 | name: pool
270 | url: "https://pub.flutter-io.cn"
271 | source: hosted
272 | version: "1.3.4"
273 | pub_semver:
274 | dependency: transitive
275 | description:
276 | name: pub_semver
277 | url: "https://pub.flutter-io.cn"
278 | source: hosted
279 | version: "1.4.1"
280 | quiver:
281 | dependency: transitive
282 | description:
283 | name: quiver
284 | url: "https://pub.flutter-io.cn"
285 | source: hosted
286 | version: "0.29.0+1"
287 | rect_getter:
288 | dependency: "direct main"
289 | description:
290 | name: rect_getter
291 | url: "https://pub.flutter-io.cn"
292 | source: hosted
293 | version: "0.0.1"
294 | shared_preferences:
295 | dependency: "direct main"
296 | description:
297 | name: shared_preferences
298 | url: "https://pub.flutter-io.cn"
299 | source: hosted
300 | version: "0.4.1"
301 | shelf:
302 | dependency: transitive
303 | description:
304 | name: shelf
305 | url: "https://pub.flutter-io.cn"
306 | source: hosted
307 | version: "0.7.3"
308 | shelf_packages_handler:
309 | dependency: transitive
310 | description:
311 | name: shelf_packages_handler
312 | url: "https://pub.flutter-io.cn"
313 | source: hosted
314 | version: "1.0.3"
315 | shelf_static:
316 | dependency: transitive
317 | description:
318 | name: shelf_static
319 | url: "https://pub.flutter-io.cn"
320 | source: hosted
321 | version: "0.2.7"
322 | shelf_web_socket:
323 | dependency: transitive
324 | description:
325 | name: shelf_web_socket
326 | url: "https://pub.flutter-io.cn"
327 | source: hosted
328 | version: "0.2.2"
329 | sky_engine:
330 | dependency: transitive
331 | description: flutter
332 | source: sdk
333 | version: "0.0.99"
334 | source_map_stack_trace:
335 | dependency: transitive
336 | description:
337 | name: source_map_stack_trace
338 | url: "https://pub.flutter-io.cn"
339 | source: hosted
340 | version: "1.1.4"
341 | source_maps:
342 | dependency: transitive
343 | description:
344 | name: source_maps
345 | url: "https://pub.flutter-io.cn"
346 | source: hosted
347 | version: "0.10.5"
348 | source_span:
349 | dependency: transitive
350 | description:
351 | name: source_span
352 | url: "https://pub.flutter-io.cn"
353 | source: hosted
354 | version: "1.4.0"
355 | stack_trace:
356 | dependency: "direct main"
357 | description:
358 | name: stack_trace
359 | url: "https://pub.flutter-io.cn"
360 | source: hosted
361 | version: "1.9.2"
362 | stream_channel:
363 | dependency: transitive
364 | description:
365 | name: stream_channel
366 | url: "https://pub.flutter-io.cn"
367 | source: hosted
368 | version: "1.6.6"
369 | string_scanner:
370 | dependency: transitive
371 | description:
372 | name: string_scanner
373 | url: "https://pub.flutter-io.cn"
374 | source: hosted
375 | version: "1.0.2"
376 | synchronized:
377 | dependency: transitive
378 | description:
379 | name: synchronized
380 | url: "https://pub.flutter-io.cn"
381 | source: hosted
382 | version: "1.4.0"
383 | term_glyph:
384 | dependency: transitive
385 | description:
386 | name: term_glyph
387 | url: "https://pub.flutter-io.cn"
388 | source: hosted
389 | version: "1.0.0"
390 | test:
391 | dependency: transitive
392 | description:
393 | name: test
394 | url: "https://pub.flutter-io.cn"
395 | source: hosted
396 | version: "0.12.37"
397 | typed_data:
398 | dependency: transitive
399 | description:
400 | name: typed_data
401 | url: "https://pub.flutter-io.cn"
402 | source: hosted
403 | version: "1.1.5"
404 | url_launcher:
405 | dependency: "direct main"
406 | description:
407 | name: url_launcher
408 | url: "https://pub.flutter-io.cn"
409 | source: hosted
410 | version: "3.0.1"
411 | utf:
412 | dependency: transitive
413 | description:
414 | name: utf
415 | url: "https://pub.flutter-io.cn"
416 | source: hosted
417 | version: "0.9.0+4"
418 | uuid:
419 | dependency: transitive
420 | description:
421 | name: uuid
422 | url: "https://pub.flutter-io.cn"
423 | source: hosted
424 | version: "0.5.3"
425 | vector_math:
426 | dependency: transitive
427 | description:
428 | name: vector_math
429 | url: "https://pub.flutter-io.cn"
430 | source: hosted
431 | version: "2.0.6"
432 | watcher:
433 | dependency: transitive
434 | description:
435 | name: watcher
436 | url: "https://pub.flutter-io.cn"
437 | source: hosted
438 | version: "0.9.7+7"
439 | web_socket_channel:
440 | dependency: transitive
441 | description:
442 | name: web_socket_channel
443 | url: "https://pub.flutter-io.cn"
444 | source: hosted
445 | version: "1.0.7"
446 | yaml:
447 | dependency: transitive
448 | description:
449 | name: yaml
450 | url: "https://pub.flutter-io.cn"
451 | source: hosted
452 | version: "2.1.13"
453 | sdks:
454 | dart: ">=2.0.0-dev.52.0 <=2.0.0-dev.58.0.flutter-f981f09760"
455 | flutter: ">=0.1.4 <2.0.0"
456 |
--------------------------------------------------------------------------------
/flutter_wanandroid/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_wanandroid
2 | description: A new Flutter project for WanAndroid.
3 |
4 | dependencies:
5 | flutter:
6 | sdk: flutter
7 |
8 | # The following adds the Cupertino Icons font to your application.
9 | # Use with the CupertinoIcons class for iOS style icons.
10 | cupertino_icons: ^0.1.0
11 | cached_network_image: ^0.4.1
12 | shared_preferences: ^0.4.1
13 | stack_trace: ^1.9.2
14 | dio: ^0.0.12
15 | flutter_webview_plugin: ^0.1.5
16 | url_launcher: ^3.0.1
17 | # pull_to_refresh: ^1.1.3
18 | rect_getter: ^0.0.1
19 | after_layout: ^1.0.2
20 |
21 | dev_dependencies:
22 | flutter_test:
23 | sdk: flutter
24 |
25 |
26 | # For information on the generic Dart part of this file, see the
27 | # following page: https://www.dartlang.org/tools/pub/pubspec
28 |
29 | # The following section is specific to Flutter.
30 | flutter:
31 |
32 | # The following line ensures that the Material Icons font is
33 | # included with your application, so that you can use the icons in
34 | # the material Icons class.
35 | uses-material-design: true
36 |
37 |
38 | # To add assets to your application, add an assets section, like this:
39 | assets:
40 | - assets/logo.png
41 | - assets/drawer_header_bg.jpg
42 |
43 | # An image asset can refer to one or more resolution-specific "variants", see
44 | # https://flutter.io/assets-and-images/#resolution-aware.
45 |
46 | # For details regarding adding assets from package dependencies, see
47 | # https://flutter.io/assets-and-images/#from-packages
48 |
49 | # To add custom fonts to your application, add a fonts section here,
50 | # in this "flutter" section. Each entry in this list should have a
51 | # "family" key with the font family name, and a "fonts" key with a
52 | # list giving the asset and other descriptors for the font. For
53 | # example:
54 | # fonts:
55 | # - family: Schyler
56 | # fonts:
57 | # - asset: fonts/Schyler-Regular.ttf
58 | # - asset: fonts/Schyler-Italic.ttf
59 | # style: italic
60 | # - family: Trajan Pro
61 | # fonts:
62 | # - asset: fonts/TrajanPro.ttf
63 | # - asset: fonts/TrajanPro_Bold.ttf
64 | # weight: 700
65 | #
66 | # For details regarding fonts from package dependencies,
67 | # see https://flutter.io/custom-fonts/#from-packages
68 |
--------------------------------------------------------------------------------
/flutter_wanandroid/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | // To perform an interaction with a widget in your test, use the WidgetTester utility that Flutter
3 | // provides. For example, you can send tap and scroll gestures. You can also use WidgetTester to
4 | // find child widgets in the widget tree, read text, and verify that the values of widget properties
5 | // are correct.
6 |
7 |
8 |
9 | void main() {
10 | }
11 |
--------------------------------------------------------------------------------
/raw/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/raw/.DS_Store
--------------------------------------------------------------------------------
/raw/Screenshot_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/raw/Screenshot_1.png
--------------------------------------------------------------------------------
/raw/Screenshot_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/raw/Screenshot_2.png
--------------------------------------------------------------------------------
/raw/Screenshot_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/raw/Screenshot_3.png
--------------------------------------------------------------------------------
/raw/Screenshot_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/raw/Screenshot_4.png
--------------------------------------------------------------------------------
/raw/Screenshot_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/raw/Screenshot_5.png
--------------------------------------------------------------------------------
/raw/Screenshot_6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/raw/Screenshot_6.png
--------------------------------------------------------------------------------
/raw/app-release.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/raw/app-release.apk
--------------------------------------------------------------------------------
/raw/screenrecord.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/raw/screenrecord.gif
--------------------------------------------------------------------------------