├── .gitignore
├── .metadata
├── LICENSE
├── README.md
├── android
├── .gitignore
├── app
│ ├── build.gradle
│ ├── proguard-rules.pro
│ ├── src
│ │ └── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── java
│ │ │ └── com
│ │ │ │ └── canhuah
│ │ │ │ └── wanandroid
│ │ │ │ └── MainActivity.java
│ │ │ └── res
│ │ │ ├── drawable
│ │ │ ├── ic_logo.png
│ │ │ └── launch_background.xml
│ │ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── values
│ │ │ ├── colors.xml
│ │ │ └── styles.xml
│ │ │ └── xml
│ │ │ └── network_security_config.xml
│ └── wanandroid.jks
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── key.properties
└── settings.gradle
├── images
├── ic_launcher_round.png
└── ic_logo.png
├── ios
├── .gitignore
├── Flutter
│ ├── AppFrameworkInfo.plist
│ ├── Debug.xcconfig
│ └── Release.xcconfig
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ └── contents.xcworkspacedata
└── Runner
│ ├── AppDelegate.h
│ ├── AppDelegate.m
│ ├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── Icon-App-1024x1024@1x.png
│ │ ├── Icon-App-20x20@1x.png
│ │ ├── Icon-App-20x20@2x.png
│ │ ├── Icon-App-20x20@3x.png
│ │ ├── Icon-App-29x29@1x.png
│ │ ├── Icon-App-29x29@2x.png
│ │ ├── Icon-App-29x29@3x.png
│ │ ├── Icon-App-40x40@1x.png
│ │ ├── Icon-App-40x40@2x.png
│ │ ├── Icon-App-40x40@3x.png
│ │ ├── Icon-App-60x60@2x.png
│ │ ├── Icon-App-60x60@3x.png
│ │ ├── Icon-App-76x76@1x.png
│ │ ├── Icon-App-76x76@2x.png
│ │ └── Icon-App-83.5x83.5@2x.png
│ └── LaunchImage.imageset
│ │ ├── Contents.json
│ │ ├── LaunchImage@2x.png
│ │ └── README.md
│ ├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
│ ├── Info.plist
│ └── main.m
├── lib
├── constant
│ ├── app_colors.dart
│ └── constants.dart
├── event
│ └── login_event.dart
├── http
│ ├── api_manager.dart
│ ├── app_dio.dart
│ ├── app_urls.dart
│ ├── default_http_transformer.dart
│ ├── default_log_nterceptor.dart
│ ├── dio_new.dart
│ ├── http_client.dart
│ ├── http_config.dart
│ ├── http_exceptions.dart
│ ├── http_parse.dart
│ ├── http_response.dart
│ └── http_transformer.dart
├── item
│ └── article_item.dart
├── main.dart
├── model
│ ├── article_model.dart
│ ├── as_t.dart
│ ├── bannel_model.dart
│ ├── friend_model.dart
│ ├── hot_key_model.dart
│ └── tree_model.dart
├── pages
│ ├── about_us_page.dart
│ ├── article_detail_page.dart
│ ├── article_list_page.dart
│ ├── articles_page.dart
│ ├── collect_list_page.dart
│ ├── home_list_page.dart
│ ├── home_page.dart
│ ├── hot_page.dart
│ ├── login_page.dart
│ ├── myInfo_page.dart
│ ├── search_list_page.dart
│ ├── search_page.dart
│ ├── tree_page.dart
│ └── welcome_page.dart
├── util
│ ├── data_utils.dart
│ └── string_utils.dart
└── widget
│ ├── end_line.dart
│ └── slide_view.dart
├── pubspec.lock
├── pubspec.yaml
├── screenshot
├── canhuah_flutter_wanandriod_about.png
├── canhuah_flutter_wanandriod_collect.png
├── canhuah_flutter_wanandriod_find.png
├── canhuah_flutter_wanandriod_home.png
├── canhuah_flutter_wanandriod_hot.png
├── canhuah_flutter_wanandriod_search.png
├── canhuah_flutter_wanandriod_tree.png
└── canhuah_flutter_wanandriod_webdetail.png
├── test
└── widget_test.dart
├── wanandroid.iml
└── wanandroid_android.iml
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .dart_tool/
3 |
4 | .packages
5 | .pub/
6 |
7 | build/
8 |
9 | .flutter-plugins
10 | /.idea
11 | .idea
12 | .idea/
13 |
--------------------------------------------------------------------------------
/.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: de332ec78292c2d79fdb76034328f902c9087ee9
8 | channel: dev
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 canhuah
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WanAndroid客户端Flutter版本
2 |
3 |
4 | ## 前言
5 | 被张鸿洋微信公众号推荐为优质Flutter开源项目啦 已适配null safety
6 |
7 | [推荐几个优质Flutter 开源项目](https://mp.weixin.qq.com/s/1pIPymEHbRY0qktBuCJALA)
8 |
9 |
10 | 可以扫码(使用浏览器扫码,不要使用qq或者微信)直接下载Release版本APK文件体验一下流畅度
11 |
12 |
13 | 
14 |
15 |
16 | ## 项目截图
17 |
18 |
19 |
25 |
31 |
32 |
33 |
34 | ## 知识点
35 |
36 | - WanAndroidPage.dart
37 | - BottomNavigationBar的基本使用
38 | - Navigator的简单使用
39 | - HomePageList.dart
40 | - 上拉加载更多
41 | - 添加头布局(SlideView)
42 | - HotePage.dart
43 | - 热门和搜索列表的切换
44 | - ArticlePage.dart
45 | - TabBarView的基本使用
46 | - ArticleDetailPage.dart
47 | - 插件flutter_webview_plugin的使用
48 | - SearchPage.dart
49 | - Widget构造函数中key的意义
50 | - ArticleItem.dart
51 | - Dart的普通构造及命名构造函数
52 | - HttpUtil.dart
53 | - [Flutter网络请求之简单封装](http://www.canhuah.com/2018/05/29/Flutter%E7%BD%91%E7%BB%9C%E8%AF%B7%E6%B1%82%E4%B9%8B%E7%AE%80%E5%8D%95%E5%B0%81%E8%A3%85/)及cookie的添加
54 |
55 |
56 |
57 | 可以看到整个项目相对还是非常简单的,希望能帮到更多的人
58 |
59 | ## 项目中碰到并解决的问题
60 |
61 | - 这个问题比较多,单独写了一篇博客
62 |
63 | [Flutter实战之WanAndroid项目中碰到的问题](http://www.canhuah.com/2018/06/14/Flutter%E5%AE%9E%E6%88%98%E4%B9%8BWanAndroid%E9%A1%B9%E7%9B%AE%E4%B8%AD%E7%A2%B0%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98/)
64 |
65 |
66 | ## 待解决的问题
67 |
68 | - 键盘遮挡
69 | - 下拉刷新,上拉加载更多的统一封装
70 | - 加载中、空数据、错误数据的界面的统一处理
71 | - 简单动画的使用(正在学习..)
72 |
73 | ## 学习资料
74 | - 官方的 [Flutter官方地址](https://flutter.io/get-started/install/)
75 | - 国内翻译版本 [Flutter中文网](https://flutterchina.club/)
76 | - 阿里闲鱼技术微信公众号(搜索 '闲鱼技术')
77 |
78 | 官方的Demo及各个Widget的效果在安装了Flutter SDK之后在 Flutter SDK安装目录/flutter/examples下,可以自己一一尝试。
79 |
80 | 我的博客 [canhuah的博客](http://www.canhuah.com)
81 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | *.class
3 | .gradle
4 | /local.properties
5 | /.idea/workspace.xml
6 | /.idea/libraries
7 | .DS_Store
8 | /build
9 | /captures
10 | GeneratedPluginRegistrant.java
11 |
--------------------------------------------------------------------------------
/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 | def keystorePropertiesFile = rootProject.file("key.properties")
18 | def keystoreProperties = new Properties()
19 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
20 | android {
21 | compileSdkVersion 29
22 |
23 | lintOptions {
24 | disable 'InvalidPackage'
25 | }
26 |
27 | defaultConfig {
28 | applicationId "com.canhuah.wanandroid"
29 | minSdkVersion 21
30 | targetSdkVersion 29
31 | versionCode 111
32 | versionName "1.1.1"
33 | }
34 |
35 |
36 |
37 |
38 | signingConfigs {
39 | release {
40 | keyAlias keystoreProperties['keyAlias']
41 | keyPassword keystoreProperties['keyPassword']
42 | storeFile file(keystoreProperties['storeFile'])
43 | storePassword keystoreProperties['storePassword']
44 | }
45 | }
46 | buildTypes {
47 | release {
48 | signingConfig signingConfigs.release
49 |
50 | // minifyEnabled true
51 | // useProguard true
52 | //
53 | // proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
54 | }
55 | }
56 | }
57 |
58 | flutter {
59 | source '../..'
60 | }
61 |
62 | dependencies {
63 | implementation 'androidx.appcompat:appcompat:1.0.2'
64 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
65 | }
66 |
--------------------------------------------------------------------------------
/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | #Flutter Wrapper
2 | -keep class io.flutter.app.** { *; }
3 | -keep class io.flutter.plugin.** { *; }
4 | -keep class io.flutter.util.** { *; }
5 | -keep class io.flutter.view.** { *; }
6 | -keep class io.flutter.** { *; }
7 | -keep class io.flutter.plugins.** { *; }
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
15 |
21 |
28 |
32 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/android/app/src/main/java/com/canhuah/wanandroid/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.canhuah.wanandroid;
2 |
3 | import io.flutter.embedding.android.FlutterActivity;
4 |
5 | public class MainActivity extends FlutterActivity {
6 | }
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/ic_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/android/app/src/main/res/drawable/ic_logo.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #5b88ff
4 |
5 |
6 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/android/app/src/main/res/xml/network_security_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/app/wanandroid.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/android/app/wanandroid.jks
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | jcenter()
5 | }
6 |
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:3.5.0'
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 |
31 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.enableR8=true
3 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/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-6.1.1-all.zip
7 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/android/key.properties:
--------------------------------------------------------------------------------
1 | storePassword=a1111111
2 | keyPassword=a1111111
3 | keyAlias=key
4 | storeFile=wanandroid.jks
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/images/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/images/ic_launcher_round.png
--------------------------------------------------------------------------------
/images/ic_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/images/ic_logo.png
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 9.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
11 | 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */ = {isa = PBXBuildFile; fileRef = 2D5378251FAA1A9400D5DBA9 /* flutter_assets */; };
12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
13 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
14 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
15 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
16 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
17 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
18 | 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; };
19 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
20 | 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
21 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
22 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
23 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
24 | C41780FF9C22B5DA4C91A057 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9EAF75F88FD856F91BB5A7B0 /* libPods-Runner.a */; };
25 | /* End PBXBuildFile section */
26 |
27 | /* Begin PBXCopyFilesBuildPhase section */
28 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
29 | isa = PBXCopyFilesBuildPhase;
30 | buildActionMask = 2147483647;
31 | dstPath = "";
32 | dstSubfolderSpec = 10;
33 | files = (
34 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
35 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
36 | );
37 | name = "Embed Frameworks";
38 | runOnlyForDeploymentPostprocessing = 0;
39 | };
40 | /* End PBXCopyFilesBuildPhase section */
41 |
42 | /* Begin PBXFileReference section */
43 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
44 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
45 | 2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; };
46 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
47 | 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; };
48 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
49 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
50 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
51 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
52 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
53 | 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; };
54 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
55 | 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
56 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
57 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
58 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
59 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
60 | 9EAF75F88FD856F91BB5A7B0 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
61 | /* End PBXFileReference section */
62 |
63 | /* Begin PBXFrameworksBuildPhase section */
64 | 97C146EB1CF9000F007C117D /* Frameworks */ = {
65 | isa = PBXFrameworksBuildPhase;
66 | buildActionMask = 2147483647;
67 | files = (
68 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
69 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
70 | C41780FF9C22B5DA4C91A057 /* libPods-Runner.a in Frameworks */,
71 | );
72 | runOnlyForDeploymentPostprocessing = 0;
73 | };
74 | /* End PBXFrameworksBuildPhase section */
75 |
76 | /* Begin PBXGroup section */
77 | 58202AEE2276597126343165 /* Frameworks */ = {
78 | isa = PBXGroup;
79 | children = (
80 | 9EAF75F88FD856F91BB5A7B0 /* libPods-Runner.a */,
81 | );
82 | name = Frameworks;
83 | sourceTree = "";
84 | };
85 | 9740EEB11CF90186004384FC /* Flutter */ = {
86 | isa = PBXGroup;
87 | children = (
88 | 2D5378251FAA1A9400D5DBA9 /* flutter_assets */,
89 | 3B80C3931E831B6300D905FE /* App.framework */,
90 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
91 | 9740EEBA1CF902C7004384FC /* Flutter.framework */,
92 | 9740EEB21CF90195004384FC /* Debug.xcconfig */,
93 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
94 | 9740EEB31CF90195004384FC /* Generated.xcconfig */,
95 | );
96 | name = Flutter;
97 | sourceTree = "";
98 | };
99 | 97C146E51CF9000F007C117D = {
100 | isa = PBXGroup;
101 | children = (
102 | 9740EEB11CF90186004384FC /* Flutter */,
103 | 97C146F01CF9000F007C117D /* Runner */,
104 | 97C146EF1CF9000F007C117D /* Products */,
105 | AE05D344300683DFF0F76D26 /* Pods */,
106 | 58202AEE2276597126343165 /* Frameworks */,
107 | );
108 | sourceTree = "";
109 | };
110 | 97C146EF1CF9000F007C117D /* Products */ = {
111 | isa = PBXGroup;
112 | children = (
113 | 97C146EE1CF9000F007C117D /* Runner.app */,
114 | );
115 | name = Products;
116 | sourceTree = "";
117 | };
118 | 97C146F01CF9000F007C117D /* Runner */ = {
119 | isa = PBXGroup;
120 | children = (
121 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
122 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
123 | 97C146FA1CF9000F007C117D /* Main.storyboard */,
124 | 97C146FD1CF9000F007C117D /* Assets.xcassets */,
125 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
126 | 97C147021CF9000F007C117D /* Info.plist */,
127 | 97C146F11CF9000F007C117D /* Supporting Files */,
128 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
129 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
130 | );
131 | path = Runner;
132 | sourceTree = "";
133 | };
134 | 97C146F11CF9000F007C117D /* Supporting Files */ = {
135 | isa = PBXGroup;
136 | children = (
137 | 97C146F21CF9000F007C117D /* main.m */,
138 | );
139 | name = "Supporting Files";
140 | sourceTree = "";
141 | };
142 | AE05D344300683DFF0F76D26 /* Pods */ = {
143 | isa = PBXGroup;
144 | children = (
145 | );
146 | name = Pods;
147 | sourceTree = "";
148 | };
149 | /* End PBXGroup section */
150 |
151 | /* Begin PBXNativeTarget section */
152 | 97C146ED1CF9000F007C117D /* Runner */ = {
153 | isa = PBXNativeTarget;
154 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
155 | buildPhases = (
156 | CDBCAE67524DB6514D369BED /* [CP] Check Pods Manifest.lock */,
157 | 9740EEB61CF901F6004384FC /* Run Script */,
158 | 97C146EA1CF9000F007C117D /* Sources */,
159 | 97C146EB1CF9000F007C117D /* Frameworks */,
160 | 97C146EC1CF9000F007C117D /* Resources */,
161 | 9705A1C41CF9048500538489 /* Embed Frameworks */,
162 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
163 | 943C18EEEDBE3DEAD02E7BF7 /* [CP] Embed Pods Frameworks */,
164 | );
165 | buildRules = (
166 | );
167 | dependencies = (
168 | );
169 | name = Runner;
170 | productName = Runner;
171 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
172 | productType = "com.apple.product-type.application";
173 | };
174 | /* End PBXNativeTarget section */
175 |
176 | /* Begin PBXProject section */
177 | 97C146E61CF9000F007C117D /* Project object */ = {
178 | isa = PBXProject;
179 | attributes = {
180 | LastUpgradeCheck = 0910;
181 | ORGANIZATIONNAME = "The Chromium Authors";
182 | TargetAttributes = {
183 | 97C146ED1CF9000F007C117D = {
184 | CreatedOnToolsVersion = 7.3.1;
185 | };
186 | };
187 | };
188 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
189 | compatibilityVersion = "Xcode 3.2";
190 | developmentRegion = English;
191 | hasScannedForEncodings = 0;
192 | knownRegions = (
193 | en,
194 | Base,
195 | );
196 | mainGroup = 97C146E51CF9000F007C117D;
197 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
198 | projectDirPath = "";
199 | projectRoot = "";
200 | targets = (
201 | 97C146ED1CF9000F007C117D /* Runner */,
202 | );
203 | };
204 | /* End PBXProject section */
205 |
206 | /* Begin PBXResourcesBuildPhase section */
207 | 97C146EC1CF9000F007C117D /* Resources */ = {
208 | isa = PBXResourcesBuildPhase;
209 | buildActionMask = 2147483647;
210 | files = (
211 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
212 | 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */,
213 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
214 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */,
215 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
216 | 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */,
217 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
218 | );
219 | runOnlyForDeploymentPostprocessing = 0;
220 | };
221 | /* End PBXResourcesBuildPhase section */
222 |
223 | /* Begin PBXShellScriptBuildPhase section */
224 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
225 | isa = PBXShellScriptBuildPhase;
226 | buildActionMask = 2147483647;
227 | files = (
228 | );
229 | inputPaths = (
230 | );
231 | name = "Thin Binary";
232 | outputPaths = (
233 | );
234 | runOnlyForDeploymentPostprocessing = 0;
235 | shellPath = /bin/sh;
236 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
237 | };
238 | 943C18EEEDBE3DEAD02E7BF7 /* [CP] Embed Pods Frameworks */ = {
239 | isa = PBXShellScriptBuildPhase;
240 | buildActionMask = 2147483647;
241 | files = (
242 | );
243 | inputFileListPaths = (
244 | );
245 | inputPaths = (
246 | "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
247 | "${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework",
248 | );
249 | name = "[CP] Embed Pods Frameworks";
250 | outputFileListPaths = (
251 | );
252 | outputPaths = (
253 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework",
254 | );
255 | runOnlyForDeploymentPostprocessing = 0;
256 | shellPath = /bin/sh;
257 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
258 | showEnvVarsInLog = 0;
259 | };
260 | 9740EEB61CF901F6004384FC /* Run Script */ = {
261 | isa = PBXShellScriptBuildPhase;
262 | buildActionMask = 2147483647;
263 | files = (
264 | );
265 | inputPaths = (
266 | );
267 | name = "Run Script";
268 | outputPaths = (
269 | );
270 | runOnlyForDeploymentPostprocessing = 0;
271 | shellPath = /bin/sh;
272 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
273 | };
274 | CDBCAE67524DB6514D369BED /* [CP] Check Pods Manifest.lock */ = {
275 | isa = PBXShellScriptBuildPhase;
276 | buildActionMask = 2147483647;
277 | files = (
278 | );
279 | inputFileListPaths = (
280 | );
281 | inputPaths = (
282 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
283 | "${PODS_ROOT}/Manifest.lock",
284 | );
285 | name = "[CP] Check Pods Manifest.lock";
286 | outputFileListPaths = (
287 | );
288 | outputPaths = (
289 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
290 | );
291 | runOnlyForDeploymentPostprocessing = 0;
292 | shellPath = /bin/sh;
293 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
294 | showEnvVarsInLog = 0;
295 | };
296 | /* End PBXShellScriptBuildPhase section */
297 |
298 | /* Begin PBXSourcesBuildPhase section */
299 | 97C146EA1CF9000F007C117D /* Sources */ = {
300 | isa = PBXSourcesBuildPhase;
301 | buildActionMask = 2147483647;
302 | files = (
303 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
304 | 97C146F31CF9000F007C117D /* main.m in Sources */,
305 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
306 | );
307 | runOnlyForDeploymentPostprocessing = 0;
308 | };
309 | /* End PBXSourcesBuildPhase section */
310 |
311 | /* Begin PBXVariantGroup section */
312 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
313 | isa = PBXVariantGroup;
314 | children = (
315 | 97C146FB1CF9000F007C117D /* Base */,
316 | );
317 | name = Main.storyboard;
318 | sourceTree = "";
319 | };
320 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
321 | isa = PBXVariantGroup;
322 | children = (
323 | 97C147001CF9000F007C117D /* Base */,
324 | );
325 | name = LaunchScreen.storyboard;
326 | sourceTree = "";
327 | };
328 | /* End PBXVariantGroup section */
329 |
330 | /* Begin XCBuildConfiguration section */
331 | 97C147031CF9000F007C117D /* Debug */ = {
332 | isa = XCBuildConfiguration;
333 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
334 | buildSettings = {
335 | ALWAYS_SEARCH_USER_PATHS = NO;
336 | CLANG_ANALYZER_NONNULL = YES;
337 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
338 | CLANG_CXX_LIBRARY = "libc++";
339 | CLANG_ENABLE_MODULES = YES;
340 | CLANG_ENABLE_OBJC_ARC = YES;
341 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
342 | CLANG_WARN_BOOL_CONVERSION = YES;
343 | CLANG_WARN_COMMA = YES;
344 | CLANG_WARN_CONSTANT_CONVERSION = YES;
345 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
346 | CLANG_WARN_EMPTY_BODY = YES;
347 | CLANG_WARN_ENUM_CONVERSION = YES;
348 | CLANG_WARN_INFINITE_RECURSION = YES;
349 | CLANG_WARN_INT_CONVERSION = YES;
350 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
351 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
352 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
353 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
354 | CLANG_WARN_STRICT_PROTOTYPES = YES;
355 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
356 | CLANG_WARN_UNREACHABLE_CODE = YES;
357 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
358 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
359 | COPY_PHASE_STRIP = NO;
360 | DEBUG_INFORMATION_FORMAT = dwarf;
361 | ENABLE_STRICT_OBJC_MSGSEND = YES;
362 | ENABLE_TESTABILITY = YES;
363 | GCC_C_LANGUAGE_STANDARD = gnu99;
364 | GCC_DYNAMIC_NO_PIC = NO;
365 | GCC_NO_COMMON_BLOCKS = YES;
366 | GCC_OPTIMIZATION_LEVEL = 0;
367 | GCC_PREPROCESSOR_DEFINITIONS = (
368 | "DEBUG=1",
369 | "$(inherited)",
370 | );
371 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
372 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
373 | GCC_WARN_UNDECLARED_SELECTOR = YES;
374 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
375 | GCC_WARN_UNUSED_FUNCTION = YES;
376 | GCC_WARN_UNUSED_VARIABLE = YES;
377 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
378 | MTL_ENABLE_DEBUG_INFO = YES;
379 | ONLY_ACTIVE_ARCH = YES;
380 | SDKROOT = iphoneos;
381 | TARGETED_DEVICE_FAMILY = "1,2";
382 | };
383 | name = Debug;
384 | };
385 | 97C147041CF9000F007C117D /* Release */ = {
386 | isa = XCBuildConfiguration;
387 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
388 | buildSettings = {
389 | ALWAYS_SEARCH_USER_PATHS = NO;
390 | CLANG_ANALYZER_NONNULL = YES;
391 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
392 | CLANG_CXX_LIBRARY = "libc++";
393 | CLANG_ENABLE_MODULES = YES;
394 | CLANG_ENABLE_OBJC_ARC = YES;
395 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
396 | CLANG_WARN_BOOL_CONVERSION = YES;
397 | CLANG_WARN_COMMA = YES;
398 | CLANG_WARN_CONSTANT_CONVERSION = YES;
399 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
400 | CLANG_WARN_EMPTY_BODY = YES;
401 | CLANG_WARN_ENUM_CONVERSION = YES;
402 | CLANG_WARN_INFINITE_RECURSION = YES;
403 | CLANG_WARN_INT_CONVERSION = YES;
404 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
405 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
406 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
407 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
408 | CLANG_WARN_STRICT_PROTOTYPES = YES;
409 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
410 | CLANG_WARN_UNREACHABLE_CODE = YES;
411 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
412 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
413 | COPY_PHASE_STRIP = NO;
414 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
415 | ENABLE_NS_ASSERTIONS = NO;
416 | ENABLE_STRICT_OBJC_MSGSEND = YES;
417 | GCC_C_LANGUAGE_STANDARD = gnu99;
418 | GCC_NO_COMMON_BLOCKS = YES;
419 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
420 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
421 | GCC_WARN_UNDECLARED_SELECTOR = YES;
422 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
423 | GCC_WARN_UNUSED_FUNCTION = YES;
424 | GCC_WARN_UNUSED_VARIABLE = YES;
425 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
426 | MTL_ENABLE_DEBUG_INFO = NO;
427 | SDKROOT = iphoneos;
428 | TARGETED_DEVICE_FAMILY = "1,2";
429 | VALIDATE_PRODUCT = YES;
430 | };
431 | name = Release;
432 | };
433 | 97C147061CF9000F007C117D /* Debug */ = {
434 | isa = XCBuildConfiguration;
435 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
436 | buildSettings = {
437 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
438 | CURRENT_PROJECT_VERSION = 1;
439 | ENABLE_BITCODE = NO;
440 | FRAMEWORK_SEARCH_PATHS = (
441 | "$(inherited)",
442 | "$(PROJECT_DIR)/Flutter",
443 | );
444 | INFOPLIST_FILE = Runner/Info.plist;
445 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
446 | LIBRARY_SEARCH_PATHS = (
447 | "$(inherited)",
448 | "$(PROJECT_DIR)/Flutter",
449 | );
450 | PRODUCT_BUNDLE_IDENTIFIER = com.canhuah.wanandroid;
451 | PRODUCT_NAME = "$(TARGET_NAME)";
452 | VERSIONING_SYSTEM = "apple-generic";
453 | };
454 | name = Debug;
455 | };
456 | 97C147071CF9000F007C117D /* Release */ = {
457 | isa = XCBuildConfiguration;
458 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
459 | buildSettings = {
460 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
461 | CURRENT_PROJECT_VERSION = 1;
462 | ENABLE_BITCODE = NO;
463 | FRAMEWORK_SEARCH_PATHS = (
464 | "$(inherited)",
465 | "$(PROJECT_DIR)/Flutter",
466 | );
467 | INFOPLIST_FILE = Runner/Info.plist;
468 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
469 | LIBRARY_SEARCH_PATHS = (
470 | "$(inherited)",
471 | "$(PROJECT_DIR)/Flutter",
472 | );
473 | PRODUCT_BUNDLE_IDENTIFIER = com.canhuah.wanandroid;
474 | PRODUCT_NAME = "$(TARGET_NAME)";
475 | VERSIONING_SYSTEM = "apple-generic";
476 | };
477 | name = Release;
478 | };
479 | /* End XCBuildConfiguration section */
480 |
481 | /* Begin XCConfigurationList section */
482 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
483 | isa = XCConfigurationList;
484 | buildConfigurations = (
485 | 97C147031CF9000F007C117D /* Debug */,
486 | 97C147041CF9000F007C117D /* Release */,
487 | );
488 | defaultConfigurationIsVisible = 0;
489 | defaultConfigurationName = Release;
490 | };
491 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
492 | isa = XCConfigurationList;
493 | buildConfigurations = (
494 | 97C147061CF9000F007C117D /* Debug */,
495 | 97C147071CF9000F007C117D /* Release */,
496 | );
497 | defaultConfigurationIsVisible = 0;
498 | defaultConfigurationName = Release;
499 | };
500 | /* End XCConfigurationList section */
501 | };
502 | rootObject = 97C146E61CF9000F007C117D /* Project object */;
503 | }
504 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
33 |
34 |
40 |
41 |
42 |
43 |
44 |
45 |
56 |
58 |
64 |
65 |
66 |
67 |
68 |
69 |
75 |
77 |
83 |
84 |
85 |
86 |
88 |
89 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | @interface AppDelegate : FlutterAppDelegate
5 |
6 | @end
7 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.m:
--------------------------------------------------------------------------------
1 | #include "AppDelegate.h"
2 | #include "GeneratedPluginRegistrant.h"
3 |
4 | @implementation AppDelegate
5 |
6 | - (BOOL)application:(UIApplication *)application 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 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | 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 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationPortrait
32 | UIInterfaceOrientationLandscapeLeft
33 | UIInterfaceOrientationLandscapeRight
34 |
35 | UISupportedInterfaceOrientations~ipad
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationPortraitUpsideDown
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UIViewControllerBasedStatusBarAppearance
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/ios/Runner/main.m:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 | #import "AppDelegate.h"
4 |
5 | int main(int argc, char * argv[]) {
6 | @autoreleasepool {
7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/lib/constant/app_colors.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class AppColors {
4 | static Color colorPrimary = const Color(0xFF5B88FF);
5 |
6 | static Color colorFFFFFFFF = const Color(0xFFFFFFFF);
7 |
8 | static Color colorTextTitle = const Color(0xFF10212A);
9 | static Color colorTextContent = const Color(0xFF505A66);
10 | static Color colorTextAuthor= const Color(0xFF878F9A);
11 |
12 | static Color colorCBCBD4= const Color(0xFFCBCBD4);
13 | }
14 |
--------------------------------------------------------------------------------
/lib/constant/constants.dart:
--------------------------------------------------------------------------------
1 | import 'package:event_bus/event_bus.dart';
2 |
3 |
4 | class Constants {
5 |
6 | static final String END_LINE_TAG = "COMPLETE";
7 |
8 | static EventBus eventBus = new EventBus();
9 |
10 | }
--------------------------------------------------------------------------------
/lib/event/login_event.dart:
--------------------------------------------------------------------------------
1 |
2 | class LoginEvent{
3 |
4 | }
--------------------------------------------------------------------------------
/lib/http/api_manager.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:path_provider/path_provider.dart';
4 | import 'package:wanAndroid/http/app_urls.dart';
5 | import 'package:wanAndroid/http/dio_new.dart';
6 | import 'package:wanAndroid/model/article_model.dart';
7 | import 'package:wanAndroid/model/bannel_model.dart';
8 | import 'package:wanAndroid/model/friend_model.dart';
9 | import 'package:wanAndroid/model/hot_key_model.dart';
10 | import 'package:wanAndroid/model/tree_model.dart';
11 |
12 | import 'package:dio_cookie_manager/dio_cookie_manager.dart';
13 | import 'package:cookie_jar/cookie_jar.dart';
14 |
15 | class ApiManager {
16 | factory ApiManager() => _getInstance();
17 |
18 | static ApiManager get instance => _getInstance();
19 |
20 | static ApiManager? _instance;
21 |
22 | ApiManager._internal();
23 |
24 | static ApiManager _getInstance() {
25 | if (_instance == null) {
26 | _instance = ApiManager._internal();
27 | }
28 | return _instance!;
29 | }
30 |
31 | HttpClient? client;
32 |
33 | initClient() async {
34 | Directory appDocDir = await getApplicationDocumentsDirectory();
35 | String appDocPath = appDocDir.path;
36 |
37 | PersistCookieJar persistCookieJar = PersistCookieJar(
38 | ignoreExpires: true, storage: FileStorage(appDocPath + "/.cookies/"));
39 |
40 | HttpConfig dioConfig = HttpConfig(
41 | baseUrl: AppUrls.BaseUrl,
42 | interceptors: [CookieManager(persistCookieJar)]);
43 |
44 | client = HttpClient(dioConfig: dioConfig);
45 | }
46 |
47 | HttpClient getClient() {
48 | if (client == null) {
49 | throw Exception("请在第一个网络请求之前先调用initClient()方法初始化clint");
50 | }
51 | return client!;
52 | }
53 |
54 | ///获取收藏文章列表
55 | getBanner({Function(List)? successCallback}) async {
56 | HttpResponse httpResponse = await getClient().get(AppUrls.BANNER);
57 |
58 | if (httpResponse.ok) {
59 | List list = httpResponse.data;
60 | List banners =
61 | list.map((e) => BannerModel.fromJson(e)).toList();
62 |
63 | successCallback?.call(banners);
64 | } else {
65 | handleErrorResponse(httpResponse);
66 | }
67 | }
68 |
69 | ///文章收藏 或者 取消收藏
70 | articleCollectOrUnCollect(ArticleModel articleModel,
71 | {Function? successCallback}) async {
72 | String url;
73 | if (articleModel.collect ?? false) {
74 | url = AppUrls.UNCOLLECT_ORIGINID;
75 | } else {
76 | url = AppUrls.COLLECT;
77 | }
78 | url += '${articleModel.id}/json';
79 |
80 | HttpResponse httpResponse = await getClient().post(url);
81 |
82 | if (httpResponse.ok) {
83 | successCallback?.call();
84 | } else {
85 | handleErrorResponse(httpResponse);
86 | }
87 | }
88 |
89 | ///获取收藏文章列表
90 | getCollectList(int curPage,
91 | {Function(ArticleListModel)? successCallback}) async {
92 | String url = "${AppUrls.COLLECT_LIST}$curPage/json";
93 |
94 | HttpResponse httpResponse = await getClient().get(url);
95 |
96 | if (httpResponse.ok) {
97 | ArticleListModel articleListModel =
98 | ArticleListModel.fromJson(httpResponse.data);
99 |
100 | successCallback?.call(articleListModel);
101 | } else {
102 | handleErrorResponse(httpResponse);
103 | }
104 | }
105 |
106 | ///收藏列表取消收藏
107 | unCollect(ArticleModel articleModel, {Function? successCallback}) async {
108 | String url = "${AppUrls.UNCOLLECT_LIST}${articleModel.id}/json";
109 |
110 | HttpResponse httpResponse =
111 | await getClient().post(url, data: {"originId": articleModel.originId});
112 | if (httpResponse.ok) {
113 | successCallback?.call();
114 | } else {
115 | handleErrorResponse(httpResponse);
116 | }
117 | }
118 |
119 | ///获取文章列表
120 | void getArticleList(int curPage,
121 | {String? cid, Function(ArticleListModel)? successCallback}) async {
122 | String url = "${AppUrls.ARTICLE_LIST}$curPage/json";
123 |
124 | HttpResponse httpResponse = await getClient()
125 | .get(url, queryParameters: cid == null ? {} : {"cid": cid});
126 |
127 | if (httpResponse.ok) {
128 | ArticleListModel articleListModel =
129 | ArticleListModel.fromJson(httpResponse.data);
130 |
131 | successCallback?.call(articleListModel);
132 | } else {
133 | handleErrorResponse(httpResponse);
134 | }
135 | }
136 |
137 | ///根据id查询文章列表
138 | void queryArticleList(int curPage, String id,
139 | {Function(ArticleListModel)? successCallback}) async {
140 | String url = "${AppUrls.ARTICLE_QUERY}$curPage/json";
141 |
142 | HttpResponse httpResponse = await getClient().post(url, data: {"k": id});
143 |
144 | if (httpResponse.ok) {
145 | ArticleListModel articleListModel =
146 | ArticleListModel.fromJson(httpResponse.data);
147 |
148 | successCallback?.call(articleListModel);
149 | } else {
150 | handleErrorResponse(httpResponse);
151 | }
152 | }
153 |
154 | ///获取常用网站
155 | getFriendList({Function(List)? successCallback}) async {
156 | HttpResponse httpResponse = await getClient().get(AppUrls.FRIEND);
157 |
158 | if (httpResponse.ok) {
159 | List list = httpResponse.data;
160 | List friends =
161 | list.map((e) => FriendModel.fromJson(e)).toList();
162 |
163 | successCallback?.call(friends);
164 | } else {
165 | handleErrorResponse(httpResponse);
166 | }
167 | }
168 |
169 | ///获取搜索热词
170 | getHotKeyList({Function(List)? successCallback}) async {
171 | HttpResponse httpResponse = await getClient().get(AppUrls.HOTKEY);
172 |
173 | if (httpResponse.ok) {
174 | List list = httpResponse.data;
175 | List hotKeys =
176 | list.map((e) => HotKeyModel.fromJson(e)).toList();
177 |
178 | successCallback?.call(hotKeys);
179 | } else {
180 | handleErrorResponse(httpResponse);
181 | }
182 | }
183 |
184 | ///
185 | getTreeList({Function(List)? successCallback}) async {
186 | HttpResponse httpResponse = await getClient().get(AppUrls.TREE);
187 |
188 | if (httpResponse.ok) {
189 | List list = httpResponse.data;
190 | List treeList =
191 | list.map((e) => TreeModel.fromJson(e)).toList();
192 |
193 | successCallback?.call(treeList);
194 | } else {
195 | handleErrorResponse(httpResponse);
196 | }
197 | }
198 |
199 | login(String username, String password, {Function? successCallback}) async {
200 | HttpResponse httpResponse = await getClient().post(AppUrls.LOGIN,
201 | data: {"username": username, "password": password});
202 |
203 | if (httpResponse.ok) {
204 | successCallback?.call();
205 | } else {
206 | handleErrorResponse(httpResponse);
207 | }
208 | }
209 |
210 | register(String username, String password,
211 | {Function? successCallback}) async {
212 | HttpResponse httpResponse = await getClient().post(AppUrls.REGISTER, data: {
213 | "username": username,
214 | "password": password,
215 | "repassword": password
216 | });
217 |
218 | if (httpResponse.ok) {
219 | successCallback?.call();
220 | } else {
221 | handleErrorResponse(httpResponse);
222 | }
223 | }
224 |
225 | handleErrorResponse(HttpResponse response) {
226 | print(response.error?.message);
227 | }
228 | }
229 |
--------------------------------------------------------------------------------
/lib/http/app_dio.dart:
--------------------------------------------------------------------------------
1 | import 'package:cookie_jar/cookie_jar.dart';
2 | import 'package:dio/adapter.dart';
3 | import 'package:dio/dio.dart';
4 | import 'package:dio_cookie_manager/dio_cookie_manager.dart';
5 | import 'package:flutter/foundation.dart';
6 | import 'default_log_nterceptor.dart';
7 | import 'http_config.dart';
8 |
9 | class AppDio with DioMixin implements Dio {
10 | AppDio({BaseOptions? options, HttpConfig? dioConfig}) {
11 | options ??= BaseOptions(
12 | baseUrl: dioConfig?.baseUrl ?? "",
13 | contentType: 'application/x-www-form-urlencoded',
14 | connectTimeout: dioConfig?.connectTimeout,
15 | sendTimeout: dioConfig?.sendTimeout,
16 | receiveTimeout: dioConfig?.receiveTimeout,
17 | )..headers = dioConfig?.headers;
18 | this.options = options;
19 |
20 | // DioCacheManager
21 | // final cacheOptions = CacheOptions(
22 | // // A default store is required for interceptor.
23 | // store: MemCacheStore(),
24 | // // Optional. Returns a cached response on error but for statuses 401 & 403.
25 | // hitCacheOnErrorExcept: [401, 403],
26 | // // Optional. Overrides any HTTP directive to delete entry past this duration.
27 | // maxStale: const Duration(days: 7),
28 | // );
29 | // interceptors.add(DioCacheInterceptor(options: cacheOptions));
30 | // Cookie管理
31 | if (dioConfig?.cookiesPath?.isNotEmpty ?? false) {
32 | interceptors.add(CookieManager(
33 | PersistCookieJar(storage: FileStorage(dioConfig!.cookiesPath))));
34 | }
35 |
36 | if (kDebugMode) {
37 | interceptors.add(DefaultLogInterceptor(
38 | responseBody: true,
39 | error: true,
40 | requestHeader: true,
41 | responseHeader: false,
42 | request: true,
43 | requestBody: true));
44 | }
45 | if (dioConfig?.interceptors?.isNotEmpty ?? false) {
46 | interceptors.addAll(dioConfig!.interceptors!);
47 | }
48 | httpClientAdapter = DefaultHttpClientAdapter();
49 | if (dioConfig?.proxy?.isNotEmpty ?? false) {
50 | setProxy(dioConfig!.proxy!);
51 | }
52 | }
53 |
54 | setProxy(String proxy) {
55 | (httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
56 | (client) {
57 | // config the http client
58 | client.findProxy = (uri) {
59 | // proxy all request to localhost:8888
60 | return "PROXY $proxy";
61 | };
62 | // you can also create a HttpClient to dio
63 | // return HttpClient();
64 | };
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/lib/http/app_urls.dart:
--------------------------------------------------------------------------------
1 | class AppUrls{
2 | static const String BaseUrl = "https://www.wanandroid.com/";
3 |
4 | //首页banner
5 | static const String BANNER = "banner/json";
6 |
7 | //首页文章列表 http://www.wanandroid.com/article/list/0/json
8 | // 知识体系下的文章http://www.wanandroid.com/article/list/0/json?cid=60
9 | static const String ARTICLE_LIST = "article/list/";
10 |
11 | //收藏文章列表
12 | static const String COLLECT_LIST = "lg/collect/list/";
13 |
14 | //搜索
15 | static const String ARTICLE_QUERY = "article/query/";
16 |
17 | //收藏,取消收藏
18 | static const String COLLECT = "lg/collect/";
19 | static const String UNCOLLECT_ORIGINID = "lg/uncollect_originId/";
20 |
21 | //我的收藏列表中取消收藏
22 | static const String UNCOLLECT_LIST = "lg/uncollect/";
23 |
24 | //登录,注册
25 | static const String LOGIN = "user/login";
26 | static const String REGISTER = "user/register";
27 |
28 | //知识体系
29 | static const String TREE = "tree/json";
30 |
31 | //常用网站
32 | static const String FRIEND = "friend/json";
33 |
34 | //搜索热词
35 | static const String HOTKEY = "hotkey/json";
36 | }
--------------------------------------------------------------------------------
/lib/http/default_http_transformer.dart:
--------------------------------------------------------------------------------
1 | import 'package:dio/dio.dart';
2 | import 'dio_new.dart';
3 | import 'http_transformer.dart';
4 |
5 | class DefaultHttpTransformer extends HttpTransformer {
6 | @override
7 | HttpResponse parse(Response response) {
8 | if (response.data["errorCode"] == 0) {
9 | return HttpResponse.success(response.data["data"]);
10 | } else {
11 | return HttpResponse.failure(
12 | errorMsg: response.data["errorMsg"] ?? "",
13 | errorCode: response.data["errorCode"]);
14 | }
15 | }
16 |
17 | /// 单例对象
18 | static DefaultHttpTransformer _instance = DefaultHttpTransformer._internal();
19 |
20 | /// 内部构造方法,可避免外部暴露构造函数,进行实例化
21 | DefaultHttpTransformer._internal();
22 |
23 | /// 工厂构造方法,这里使用命名构造函数方式进行声明
24 | factory DefaultHttpTransformer.getInstance() => _instance;
25 | }
26 |
--------------------------------------------------------------------------------
/lib/http/default_log_nterceptor.dart:
--------------------------------------------------------------------------------
1 | import 'dio_new.dart';
2 |
3 | class DefaultLogInterceptor extends Interceptor {
4 | DefaultLogInterceptor({
5 | this.request = true,
6 | this.requestHeader = true,
7 | this.requestBody = false,
8 | this.responseHeader = true,
9 | this.responseBody = false,
10 | this.error = true,
11 | this.logPrint = print,
12 | });
13 |
14 | /// Print request [Options]
15 | bool request;
16 |
17 | /// Print request header [Options.headers]
18 | bool requestHeader;
19 |
20 | /// Print request data [Options.data]
21 | bool requestBody;
22 |
23 | /// Print [Response.data]
24 | bool responseBody;
25 |
26 | /// Print [Response.headers]
27 | bool responseHeader;
28 |
29 | /// Print error message
30 | bool error;
31 |
32 | /// Log printer; defaults print log to console.
33 | /// In flutter, you'd better use debugPrint.
34 | /// you can also write log in a file, for example:
35 | ///```dart
36 | /// var file=File("./log.txt");
37 | /// var sink=file.openWrite();
38 | /// dio.interceptors.add(LogInterceptor(logPrint: sink.writeln));
39 | /// ...
40 | /// await sink.close();
41 | ///```
42 | void Function(Object object) logPrint;
43 |
44 | @override
45 | void onRequest(
46 | RequestOptions options, RequestInterceptorHandler handler) async {
47 | logPrint('*** Request ***');
48 | _printKV('uri', options.uri);
49 | //options.headers;
50 |
51 | if (request) {
52 | _printKV('method', options.method);
53 | _printKV('responseType', options.responseType.toString());
54 | _printKV('followRedirects', options.followRedirects);
55 | _printKV('connectTimeout', options.connectTimeout);
56 | _printKV('contentType', options.contentType);
57 | _printKV('sendTimeout', options.sendTimeout);
58 | _printKV('receiveTimeout', options.receiveTimeout);
59 | _printKV(
60 | 'receiveDataWhenStatusError', options.receiveDataWhenStatusError);
61 | _printKV('extra', options.extra);
62 | }
63 | if (requestHeader) {
64 | logPrint('headers:');
65 | options.headers.forEach((key, v) => _printKV(' $key', v));
66 | }
67 | if (requestBody) {
68 | logPrint('data:');
69 | _printAll(options.data);
70 | }
71 | logPrint('');
72 |
73 | handler.next(options);
74 | }
75 |
76 | @override
77 | void onResponse(Response response, ResponseInterceptorHandler handler) async {
78 | logPrint('*** Response ***');
79 | _printResponse(response);
80 | handler.next(response);
81 | }
82 |
83 | @override
84 | void onError(DioError err, ErrorInterceptorHandler handler) async {
85 | if (error) {
86 | logPrint('*** DioError ***:');
87 | logPrint('uri: ${err.requestOptions.uri}');
88 | logPrint('$err');
89 | if (err.response != null) {
90 | _printResponse(err.response!);
91 | }
92 | logPrint('');
93 | }
94 |
95 | handler.next(err);
96 | }
97 |
98 | void _printResponse(Response response) {
99 | _printKV('uri', response.requestOptions.uri);
100 | if (responseHeader) {
101 | _printKV('statusCode', response.statusCode);
102 | if (response.isRedirect == true) {
103 | _printKV('redirect', response.realUri);
104 | }
105 |
106 | logPrint('headers:');
107 | response.headers.forEach((key, v) => _printKV(' $key', v.join('\r\n\t')));
108 | }
109 | if (responseBody) {
110 | logPrint('Response Text:');
111 | _printAll(response.toString());
112 | }
113 | logPrint('');
114 | }
115 |
116 | void _printKV(String key, Object? v) {
117 | logPrint('$key: $v');
118 | }
119 |
120 | void _printAll(msg) {
121 | msg.toString().split('\n').forEach(logPrint);
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/lib/http/dio_new.dart:
--------------------------------------------------------------------------------
1 | library dio_new;
2 |
3 | export 'http_client.dart';
4 | export 'http_config.dart';
5 | export 'http_response.dart';
6 | export 'http_exceptions.dart';
7 | export 'http_transformer.dart';
8 | export 'package:dio/dio.dart';
9 |
10 |
11 | ///https://juejin.cn/post/6962739332922736676
--------------------------------------------------------------------------------
/lib/http/http_client.dart:
--------------------------------------------------------------------------------
1 | import 'package:dio/dio.dart';
2 |
3 | import 'dio_new.dart';
4 | import 'app_dio.dart';
5 | import 'http_response.dart';
6 | import 'http_config.dart';
7 | import 'http_parse.dart';
8 |
9 | class HttpClient {
10 | late AppDio _dio;
11 |
12 | HttpClient({BaseOptions? options, HttpConfig? dioConfig})
13 | : _dio = AppDio(options: options, dioConfig: dioConfig);
14 |
15 | Future get(String uri,
16 | {Map? queryParameters,
17 | Options? options,
18 | CancelToken? cancelToken,
19 | ProgressCallback? onReceiveProgress,
20 | HttpTransformer? httpTransformer}) async {
21 | try {
22 | var response = await _dio.get(
23 | uri,
24 | queryParameters: queryParameters,
25 | options: options,
26 | cancelToken: cancelToken,
27 | onReceiveProgress: onReceiveProgress,
28 | );
29 | return handleResponse(response, httpTransformer: httpTransformer);
30 | } on Exception catch (e) {
31 | return handleException(e);
32 | }
33 | }
34 |
35 | Future post(String uri,
36 | {data,
37 | Map? queryParameters,
38 | Options? options,
39 | CancelToken? cancelToken,
40 | ProgressCallback? onSendProgress,
41 | ProgressCallback? onReceiveProgress,
42 | HttpTransformer? httpTransformer}) async {
43 | try {
44 | var response = await _dio.post(
45 | uri,
46 | data: data,
47 | queryParameters: queryParameters,
48 | options: options,
49 | cancelToken: cancelToken,
50 | onSendProgress: onSendProgress,
51 | onReceiveProgress: onReceiveProgress,
52 | );
53 | return handleResponse(response, httpTransformer: httpTransformer);
54 | } on Exception catch (e) {
55 | return handleException(e);
56 | }
57 | }
58 |
59 | Future patch(String uri,
60 | {data,
61 | Map? queryParameters,
62 | Options? options,
63 | CancelToken? cancelToken,
64 | ProgressCallback? onSendProgress,
65 | ProgressCallback? onReceiveProgress,
66 | HttpTransformer? httpTransformer}) async {
67 | try {
68 | var response = await _dio.patch(
69 | uri,
70 | data: data,
71 | queryParameters: queryParameters,
72 | options: options,
73 | cancelToken: cancelToken,
74 | onSendProgress: onSendProgress,
75 | onReceiveProgress: onReceiveProgress,
76 | );
77 | return handleResponse(response, httpTransformer: httpTransformer);
78 | } on Exception catch (e) {
79 | return handleException(e);
80 | }
81 | }
82 |
83 | Future delete(String uri,
84 | {data,
85 | Map? queryParameters,
86 | Options? options,
87 | CancelToken? cancelToken,
88 | HttpTransformer? httpTransformer}) async {
89 | try {
90 | var response = await _dio.delete(
91 | uri,
92 | data: data,
93 | queryParameters: queryParameters,
94 | options: options,
95 | cancelToken: cancelToken,
96 | );
97 | return handleResponse(response, httpTransformer: httpTransformer);
98 | } on Exception catch (e) {
99 | return handleException(e);
100 | }
101 | }
102 |
103 | Future put(String uri,
104 | {data,
105 | Map? queryParameters,
106 | Options? options,
107 | CancelToken? cancelToken,
108 | HttpTransformer? httpTransformer}) async {
109 | try {
110 | var response = await _dio.put(
111 | uri,
112 | data: data,
113 | queryParameters: queryParameters,
114 | options: options,
115 | cancelToken: cancelToken,
116 | );
117 | return handleResponse(response, httpTransformer: httpTransformer);
118 | } on Exception catch (e) {
119 | return handleException(e);
120 | }
121 | }
122 |
123 | Future download(String urlPath, savePath,
124 | {ProgressCallback? onReceiveProgress,
125 | Map? queryParameters,
126 | CancelToken? cancelToken,
127 | bool deleteOnError = true,
128 | String lengthHeader = Headers.contentLengthHeader,
129 | data,
130 | Options? options,
131 | HttpTransformer? httpTransformer}) async {
132 | try {
133 | var response = await _dio.download(
134 | urlPath,
135 | savePath,
136 | onReceiveProgress: onReceiveProgress,
137 | queryParameters: queryParameters,
138 | cancelToken: cancelToken,
139 | deleteOnError: deleteOnError,
140 | lengthHeader: lengthHeader,
141 | data: data,
142 | options: data,
143 | );
144 | return response;
145 | } catch (e) {
146 | throw e;
147 | }
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/lib/http/http_config.dart:
--------------------------------------------------------------------------------
1 | import 'package:dio/dio.dart';
2 | /// dio 配置项
3 | class HttpConfig {
4 | final String? baseUrl;
5 | final String? proxy;
6 | final String? cookiesPath;
7 | final List? interceptors;
8 | final int connectTimeout;
9 | final int sendTimeout;
10 | final int receiveTimeout;
11 | final Map? headers;
12 |
13 | HttpConfig({
14 | this.baseUrl,
15 | this.headers,
16 | this.proxy,
17 | this.cookiesPath,
18 | this.interceptors,
19 | this.connectTimeout = Duration.millisecondsPerMinute,
20 | this.sendTimeout = Duration.millisecondsPerMinute,
21 | this.receiveTimeout = Duration.millisecondsPerMinute,
22 | });
23 |
24 | // static DioConfig of() => Get.find();
25 | }
--------------------------------------------------------------------------------
/lib/http/http_exceptions.dart:
--------------------------------------------------------------------------------
1 | class HttpException implements Exception {
2 | final String? _message;
3 |
4 | String get message => _message ?? this.runtimeType.toString();
5 |
6 | final int? _code;
7 |
8 | int get code => _code ?? -1;
9 |
10 | HttpException([this._message, this._code]);
11 |
12 | String toString() {
13 | return "code:$code--message=$message";
14 | }
15 | }
16 |
17 | /// 客户端请求错误
18 | class BadRequestException extends HttpException {
19 | BadRequestException({String? message, int? code}) : super(message, code);
20 | }
21 | /// 服务端响应错误
22 | class BadServiceException extends HttpException {
23 | BadServiceException({String? message, int? code}) : super(message, code);
24 | }
25 |
26 |
27 |
28 | class UnknownException extends HttpException {
29 | UnknownException([String? message]) : super(message);
30 | }
31 |
32 | class CancelException extends HttpException {
33 | CancelException([String? message]) : super(message);
34 | }
35 |
36 | class NetworkException extends HttpException {
37 | NetworkException({String? message, int? code}) : super(message, code);
38 | }
39 |
40 | /// 401
41 | class UnauthorisedException extends HttpException {
42 | UnauthorisedException({String? message, int? code = 401}) : super(message);
43 | }
44 |
45 | class BadResponseException extends HttpException {
46 | dynamic? data;
47 |
48 | BadResponseException([this.data]) : super();
49 | }
50 |
--------------------------------------------------------------------------------
/lib/http/http_parse.dart:
--------------------------------------------------------------------------------
1 | // 成功回调
2 | import 'dart:io';
3 |
4 | import 'package:dio/dio.dart';
5 |
6 | import 'default_http_transformer.dart';
7 | import 'dio_new.dart';
8 | import 'http_transformer.dart';
9 |
10 | HttpResponse handleResponse(Response? response,
11 | {HttpTransformer? httpTransformer}) {
12 | httpTransformer ??= DefaultHttpTransformer.getInstance();
13 |
14 | // 返回值异常
15 | if (response == null) {
16 | return HttpResponse.failureFromError();
17 | }
18 |
19 | // token失效
20 | if (_isTokenTimeout(response.statusCode)) {
21 | return HttpResponse.failureFromError(
22 | UnauthorisedException(message: "没有权限", code: response.statusCode));
23 | }
24 | // 接口调用成功
25 | if (_isRequestSuccess(response.statusCode)) {
26 | return httpTransformer.parse(response);
27 | } else {
28 | // 接口调用失败
29 | return HttpResponse.failure(
30 | errorMsg: response.statusMessage, errorCode: response.statusCode);
31 | }
32 | }
33 |
34 | HttpResponse handleException(Exception exception) {
35 | var parseException = _parseException(exception);
36 | return HttpResponse.failureFromError(parseException);
37 | }
38 |
39 | /// 鉴权失败
40 | bool _isTokenTimeout(int? code) {
41 | return code == 401;
42 | }
43 |
44 | /// 请求成功
45 | bool _isRequestSuccess(int? statusCode) {
46 | return (statusCode != null && statusCode >= 200 && statusCode < 300);
47 | }
48 |
49 | HttpException _parseException(Exception error) {
50 | if (error is DioError) {
51 | switch (error.type) {
52 | case DioErrorType.connectTimeout:
53 | case DioErrorType.receiveTimeout:
54 | case DioErrorType.sendTimeout:
55 | return NetworkException(message: error.message);
56 | case DioErrorType.cancel:
57 | return CancelException(error.message);
58 | case DioErrorType.response:
59 | try {
60 | int? errCode = error.response?.statusCode;
61 | switch (errCode) {
62 | case 400:
63 | return BadRequestException(message: "请求语法错误", code: errCode);
64 | case 401:
65 | return UnauthorisedException(message: "没有权限", code: errCode);
66 | case 403:
67 | return BadRequestException(message: "服务器拒绝执行", code: errCode);
68 | case 404:
69 | return BadRequestException(message: "无法连接服务器", code: errCode);
70 | case 405:
71 | return BadRequestException(message: "请求方法被禁止", code: errCode);
72 | case 500:
73 | return BadServiceException(message: "服务器内部错误", code: errCode);
74 | case 502:
75 | return BadServiceException(message: "无效的请求", code: errCode);
76 | case 503:
77 | return BadServiceException(message: "服务器挂了", code: errCode);
78 | case 505:
79 | return UnauthorisedException(
80 | message: "不支持HTTP协议请求", code: errCode);
81 | default:
82 | return UnknownException(error.message);
83 | }
84 | } on Exception catch (_) {
85 | return UnknownException(error.message);
86 | }
87 |
88 | case DioErrorType.other:
89 | if (error.error is SocketException) {
90 | return NetworkException(message: error.message);
91 | } else {
92 | return UnknownException(error.message);
93 | }
94 | default:
95 | return UnknownException(error.message);
96 | }
97 | } else {
98 | return UnknownException(error.toString());
99 | }
100 | }
--------------------------------------------------------------------------------
/lib/http/http_response.dart:
--------------------------------------------------------------------------------
1 | import 'http_exceptions.dart';
2 |
3 | class HttpResponse {
4 | late bool ok;
5 | dynamic? data;
6 | HttpException? error;
7 |
8 | HttpResponse._internal({this.ok = false});
9 |
10 | HttpResponse.success(this.data) {
11 | this.ok = true;
12 | }
13 |
14 | HttpResponse.failure({String? errorMsg, int? errorCode}) {
15 | this.error = BadRequestException(message: errorMsg, code: errorCode);
16 | this.ok = false;
17 | }
18 |
19 | HttpResponse.failureFormResponse({dynamic? data}) {
20 | this.error = BadResponseException(data);
21 | this.ok = false;
22 | }
23 |
24 | HttpResponse.failureFromError([HttpException? error]) {
25 | this.error = error ?? UnknownException();
26 | this.ok = false;
27 | }
28 | }
--------------------------------------------------------------------------------
/lib/http/http_transformer.dart:
--------------------------------------------------------------------------------
1 | import 'package:dio/dio.dart';
2 |
3 | import 'dio_new.dart';
4 |
5 |
6 | /// Response 解析
7 | abstract class HttpTransformer {
8 | HttpResponse parse(Response response);
9 | }
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/lib/item/article_item.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:wanAndroid/constant/app_colors.dart';
3 | import 'package:wanAndroid/http/api_manager.dart';
4 | import 'package:wanAndroid/model/article_model.dart';
5 | import 'package:wanAndroid/pages/article_detail_page.dart';
6 | import 'package:wanAndroid/pages/login_page.dart';
7 | import 'package:wanAndroid/util/data_utils.dart';
8 | import 'package:wanAndroid/util/string_utils.dart';
9 |
10 | ///个人感觉条目比较复杂的话可以单独拿出来,而且可以复用.可以对比CollectListPage.dart中的item哪个更合理
11 | class ArticleItem extends StatefulWidget {
12 | final ArticleModel articleModel;
13 |
14 | //是否来自搜索列表
15 | final bool? isSearch;
16 |
17 | final bool? isFromCollect;
18 |
19 | final Function? onClickCollect;
20 |
21 | //搜索列表的id
22 | final String? id;
23 |
24 | ArticleItem(
25 | {required this.articleModel,
26 | this.isSearch = false,
27 | this.id,
28 | this.isFromCollect = false,
29 | this.onClickCollect});
30 |
31 | @override
32 | State createState() {
33 | return _ArticleItemState();
34 | }
35 | }
36 |
37 | class _ArticleItemState extends State {
38 | void _handleOnItemCollect(itemData) {
39 | DataUtils.isLogin().then((isLogin) {
40 | if (!isLogin) {
41 | _login();
42 | } else {
43 | _itemCollect(itemData);
44 | }
45 | });
46 | }
47 |
48 | _login() {
49 | Navigator.of(context).push(MaterialPageRoute(builder: (context) {
50 | return LoginPage();
51 | }));
52 | }
53 |
54 | void _itemClick(ArticleModel articleModel) async {
55 | await Navigator.of(context).push(MaterialPageRoute(builder: (context) {
56 | return ArticleDetailPage(
57 | title: articleModel.title ?? "",
58 | url: articleModel.link ?? "",
59 | );
60 | }));
61 | }
62 |
63 | //收藏/取消收藏
64 | void _itemCollect(ArticleModel articleModel) {
65 | ApiManager.instance.articleCollectOrUnCollect(articleModel,successCallback: () {
66 | setState(() {
67 | articleModel.collect = !(articleModel.collect ?? false);
68 | });
69 | });
70 | }
71 |
72 | @override
73 | Widget build(BuildContext context) {
74 | bool isCollect = (widget.isFromCollect ?? false) ||
75 | (widget.articleModel.collect ?? false);
76 |
77 | String authorTitle;
78 | String author;
79 |
80 | if (widget.articleModel.author == null ||
81 | widget.articleModel.author!.isEmpty) {
82 | authorTitle = "分享人: ";
83 | author = widget.articleModel.shareUser ?? "";
84 | } else {
85 | authorTitle = "作者: ";
86 | author = widget.articleModel.author ?? "";
87 | }
88 |
89 | Row row = Row(
90 | mainAxisAlignment: MainAxisAlignment.start,
91 | children: [
92 | Expanded(
93 | child: Text("$authorTitle$author",
94 | style:
95 | TextStyle(color: AppColors.colorTextAuthor, fontSize: 13))),
96 | Text(
97 | widget.articleModel.niceDate ?? "",
98 | style: TextStyle(color: AppColors.colorTextAuthor, fontSize: 13),
99 | )
100 | ],
101 | );
102 |
103 | Row title = Row(
104 | children: [
105 | Expanded(
106 | child: Text.rich(
107 | widget.isSearch ?? false
108 | ? StringUtils.getTextSpan(widget.articleModel.title, widget.id)
109 | : TextSpan(text: widget.articleModel.title),
110 | softWrap: true,
111 | style: TextStyle(fontSize: 16.0, color: AppColors.colorTextTitle),
112 | textAlign: TextAlign.left,
113 | ),
114 | )
115 | ],
116 | );
117 |
118 | Row chapterName = Row(
119 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
120 | children: [
121 | Container(
122 | padding: EdgeInsets.only(left: 4, right: 4, top: 1, bottom: 2),
123 | decoration: BoxDecoration(
124 | color: AppColors.colorCBCBD4,
125 | borderRadius: BorderRadius.circular(2)),
126 | child: Text(
127 | (widget.isSearch ?? false)
128 | ? ""
129 | : widget.articleModel.chapterName ?? "",
130 | softWrap: true,
131 | style: TextStyle(color: AppColors.colorTextAuthor, fontSize: 12),
132 | textAlign: TextAlign.left,
133 | ),
134 | ),
135 | GestureDetector(
136 | child: Icon(
137 | isCollect ? Icons.favorite : Icons.favorite_border,
138 | color: isCollect ? Colors.red : null,
139 | ),
140 | onTap: () {
141 | if (widget.onClickCollect != null) {
142 | widget.onClickCollect!.call();
143 | return;
144 | }
145 | _handleOnItemCollect(widget.articleModel);
146 | },
147 | )
148 | ],
149 | );
150 |
151 | Column column = Column(
152 | children: [
153 | title,
154 | SizedBox(height: 5),
155 | row,
156 | SizedBox(height: 5),
157 | chapterName,
158 | ],
159 | );
160 |
161 | return InkWell(
162 | onTap: () {
163 | _itemClick(widget.articleModel);
164 | },
165 | child: Container(
166 | padding: EdgeInsets.symmetric(vertical: 8, horizontal: 12),
167 | decoration: BoxDecoration(
168 | color: Colors.white,
169 | borderRadius: BorderRadius.circular(4),
170 | ),
171 | margin: EdgeInsets.all(4),
172 | child: column,
173 | ),
174 | );
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flutter/cupertino.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:wanAndroid/pages/welcome_page.dart';
6 |
7 | import 'constant/app_colors.dart';
8 |
9 | void main() async {
10 |
11 | final navigatorKey = GlobalKey();
12 |
13 | runApp(MaterialApp(
14 | navigatorKey: navigatorKey,
15 | theme: ThemeData(
16 | primaryColor: AppColors.colorPrimary,
17 | accentColor: AppColors.colorPrimary),
18 | home: WelcomePage(),
19 | ));
20 | }
21 |
--------------------------------------------------------------------------------
/lib/model/article_model.dart:
--------------------------------------------------------------------------------
1 | import 'as_t.dart';
2 |
3 | class ArticleListModel {
4 | int? curPage;
5 | List? datas;
6 |
7 | ArticleListModel({this.curPage, this.datas});
8 |
9 | ArticleListModel.fromJson(Map json) {
10 | curPage = json['curPage'];
11 | if (json['datas'] != null) {
12 | datas = [];
13 | json['datas'].forEach((v) {
14 | datas!.add(ArticleModel.fromJson(v));
15 | });
16 | }
17 | }
18 |
19 | Map toJson() {
20 | final Map data = new Map();
21 | data['curPage'] = this.curPage;
22 | if (this.datas != null) {
23 | data['datas'] = this.datas!.map((v) => v.toJson()).toList();
24 | }
25 | return data;
26 | }
27 | }
28 |
29 | class ArticleModel {
30 | String? apkLink;
31 | int? audit;
32 | String? author;
33 | bool? canEdit;
34 | int? chapterId;
35 | String? chapterName;
36 | bool? collect;
37 | int? courseId;
38 | String? desc;
39 | String? descMd;
40 | String? envelopePic;
41 | bool? fresh;
42 | String? host;
43 | int? id;
44 | int? originId;
45 | String? link;
46 | String? niceDate;
47 | String? niceShareDate;
48 | String? origin;
49 | String? prefix;
50 | String? projectLink;
51 | int? publishTime;
52 | int? realSuperChapterId;
53 | int? selfVisible;
54 | int? shareDate;
55 | String? shareUser;
56 | int? superChapterId;
57 | String? superChapterName;
58 |
59 | String? title;
60 | int? type;
61 | int? userId;
62 | int? visible;
63 | int? zan;
64 |
65 | ArticleModel(
66 | {this.apkLink,
67 | this.audit,
68 | this.author,
69 | this.canEdit,
70 | this.chapterId,
71 | this.chapterName,
72 | this.collect,
73 | this.courseId,
74 | this.desc,
75 | this.descMd,
76 | this.envelopePic,
77 | this.fresh,
78 | this.host,
79 | this.id,
80 | this.originId,
81 | this.link,
82 | this.niceDate,
83 | this.niceShareDate,
84 | this.origin,
85 | this.prefix,
86 | this.projectLink,
87 | this.publishTime,
88 | this.realSuperChapterId,
89 | this.selfVisible,
90 | this.shareDate,
91 | this.shareUser,
92 | this.superChapterId,
93 | this.superChapterName,
94 | this.title,
95 | this.type,
96 | this.userId,
97 | this.visible,
98 | this.zan});
99 |
100 | ArticleModel.fromJson(Map json) {
101 | apkLink = asT(json['apkLink']);
102 | audit = asT(json['audit']);
103 | author = asT(json['author']);
104 | canEdit = asT(json['canEdit']);
105 | chapterId = asT(json['chapterId']);
106 | chapterName = asT(json['chapterName']);
107 | collect = asT(json['collect']);
108 | courseId = asT(json['courseId']);
109 | desc = asT(json['desc']);
110 | descMd = asT(json['descMd']);
111 | envelopePic = asT(json['envelopePic']);
112 | fresh = asT(json['fresh']);
113 | host = asT(json['host']);
114 | id = asT(json['id']);
115 | originId = asT(json['originId']);
116 | link = asT(json['link']);
117 | niceDate = asT(json['niceDate']);
118 | niceShareDate = asT(json['niceShareDate']);
119 | origin = asT(json['origin']);
120 | prefix = asT(json['prefix']);
121 | projectLink = asT(json['projectLink']);
122 | publishTime = asT(json['publishTime']);
123 | realSuperChapterId = asT(json['realSuperChapterId']);
124 | selfVisible = asT(json['selfVisible']);
125 | shareDate = asT(json['shareDate']);
126 | shareUser = asT(json['shareUser']);
127 | superChapterId = asT(json['superChapterId']);
128 | superChapterName = asT(json['superChapterName']);
129 |
130 | title = asT(json['title']);
131 | type = asT(json['type']);
132 | userId = asT(json['userId']);
133 | visible = asT(json['visible']);
134 | zan = asT(json['zan']);
135 | }
136 |
137 | Map toJson() {
138 | final Map data = {};
139 | data['apkLink'] = this.apkLink;
140 | data['audit'] = this.audit;
141 | data['author'] = this.author;
142 | data['canEdit'] = this.canEdit;
143 | data['chapterId'] = this.chapterId;
144 | data['chapterName'] = this.chapterName;
145 | data['collect'] = this.collect;
146 | data['courseId'] = this.courseId;
147 | data['desc'] = this.desc;
148 | data['descMd'] = this.descMd;
149 | data['envelopePic'] = this.envelopePic;
150 | data['fresh'] = this.fresh;
151 | data['host'] = this.host;
152 | data['id'] = this.id;
153 | data['originId'] = this.originId;
154 | data['link'] = this.link;
155 | data['niceDate'] = this.niceDate;
156 | data['niceShareDate'] = this.niceShareDate;
157 | data['origin'] = this.origin;
158 | data['prefix'] = this.prefix;
159 | data['projectLink'] = this.projectLink;
160 | data['publishTime'] = this.publishTime;
161 | data['realSuperChapterId'] = this.realSuperChapterId;
162 | data['selfVisible'] = this.selfVisible;
163 | data['shareDate'] = this.shareDate;
164 | data['shareUser'] = this.shareUser;
165 | data['superChapterId'] = this.superChapterId;
166 | data['superChapterName'] = this.superChapterName;
167 |
168 | data['title'] = this.title;
169 | data['type'] = this.type;
170 | data['userId'] = this.userId;
171 | data['visible'] = this.visible;
172 | data['zan'] = this.zan;
173 | return data;
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/lib/model/as_t.dart:
--------------------------------------------------------------------------------
1 | T? asT(dynamic value) {
2 | if (value is T) {
3 | return value;
4 | }
5 | return null;
6 | }
--------------------------------------------------------------------------------
/lib/model/bannel_model.dart:
--------------------------------------------------------------------------------
1 | //JsonToDart 生成
2 | import 'as_t.dart';
3 |
4 | class BannerModel {
5 | String? desc;
6 | int? id;
7 | String? imagePath;
8 | int? isVisible;
9 | int? order;
10 | String? title;
11 | int? type;
12 | String? url;
13 |
14 | BannerModel(
15 | {this.desc,
16 | this.id,
17 | this.imagePath,
18 | this.isVisible,
19 | this.order,
20 | this.title,
21 | this.type,
22 | this.url});
23 |
24 | BannerModel.fromJson(Map json) {
25 | desc = asT(json['desc']);
26 | id = asT(json['id']);
27 | imagePath = asT(json['imagePath']);
28 | isVisible = asT(json['isVisible']);
29 | order = asT(json['order']);
30 | title = asT(json['title']);
31 | type = asT(json['type']);
32 | url = asT(json['url']);
33 | }
34 |
35 | Map toJson() {
36 | final Map data = new Map();
37 | data['desc'] = this.desc;
38 | data['id'] = this.id;
39 | data['imagePath'] = this.imagePath;
40 | data['isVisible'] = this.isVisible;
41 | data['order'] = this.order;
42 | data['title'] = this.title;
43 | data['type'] = this.type;
44 | data['url'] = this.url;
45 | return data;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/lib/model/friend_model.dart:
--------------------------------------------------------------------------------
1 | import 'as_t.dart';
2 |
3 | class FriendModel {
4 | String? category;
5 | String? icon;
6 | int? id;
7 | String? link;
8 | String? name;
9 | int? order;
10 | int? visible;
11 |
12 | FriendModel(
13 | {this.category,
14 | this.icon,
15 | this.id,
16 | this.link,
17 | this.name,
18 | this.order,
19 | this.visible});
20 |
21 | FriendModel.fromJson(Map json) {
22 | category = asT(json['category']);
23 | icon = asT(json['icon']);
24 | id = asT(json['id']);
25 | link = asT(json['link']);
26 | name = asT(json['name']);
27 | order = asT(json['order']);
28 | visible = asT(json['visible']);
29 | }
30 |
31 | Map toJson() {
32 | final Map data = new Map();
33 | data['category'] = this.category;
34 | data['icon'] = this.icon;
35 | data['id'] = this.id;
36 | data['link'] = this.link;
37 | data['name'] = this.name;
38 | data['order'] = this.order;
39 | data['visible'] = this.visible;
40 | return data;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/lib/model/hot_key_model.dart:
--------------------------------------------------------------------------------
1 |
2 | import 'as_t.dart';
3 |
4 | class HotKeyModel {
5 | int? id;
6 | String? link;
7 | String? name;
8 | int? order;
9 | int? visible;
10 |
11 | HotKeyModel({this.id, this.link, this.name, this.order, this.visible});
12 |
13 | HotKeyModel.fromJson(Map json) {
14 | id = asT(json['id']);
15 | link = asT(json['link']);
16 | name = asT(json['name']);
17 | order = asT(json['order']);
18 | visible = asT(json['visible']);
19 | }
20 |
21 | Map toJson() {
22 | final Map data = {};
23 | data['id'] = this.id;
24 | data['link'] = this.link;
25 | data['name'] = this.name;
26 | data['order'] = this.order;
27 | data['visible'] = this.visible;
28 | return data;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lib/model/tree_model.dart:
--------------------------------------------------------------------------------
1 | import 'as_t.dart';
2 |
3 | class TreeModel {
4 | List? children;
5 | int? courseId;
6 | int? id;
7 | String? name;
8 | int? order;
9 | int? parentChapterId;
10 | bool? userControlSetTop;
11 | int? visible;
12 |
13 | TreeModel(
14 | {this.children,
15 | this.courseId,
16 | this.id,
17 | this.name,
18 | this.order,
19 | this.parentChapterId,
20 | this.userControlSetTop,
21 | this.visible});
22 |
23 | TreeModel.fromJson(Map json) {
24 | if (json['children'] != null) {
25 | children = [];
26 | json['children'].forEach((v) {
27 | children!.add(TreeModel.fromJson(v));
28 | });
29 | }
30 | courseId = asT(json['courseId']);
31 | id = asT(json['id']);
32 | name = asT(json['name']);
33 | order = asT(json['order']);
34 | parentChapterId = asT(json['parentChapterId']);
35 | userControlSetTop = asT(json['userControlSetTop']);
36 | visible = asT(json['visible']);
37 | }
38 |
39 | Map toJson() {
40 | final Map data = {};
41 | if (this.children != null) {
42 | data['children'] = this.children!.map((v) => v.toJson()).toList();
43 | }
44 | data['courseId'] = this.courseId;
45 | data['id'] = this.id;
46 | data['name'] = this.name;
47 | data['order'] = this.order;
48 | data['parentChapterId'] = this.parentChapterId;
49 | data['userControlSetTop'] = this.userControlSetTop;
50 | data['visible'] = this.visible;
51 | return data;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/lib/pages/about_us_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:wanAndroid/pages/article_detail_page.dart';
3 |
4 | //关于我们
5 | class AboutUsPage extends StatefulWidget {
6 | @override
7 | State createState() {
8 | return _AboutUsPageState();
9 | }
10 | }
11 |
12 | class _AboutUsPageState extends State {
13 | @override
14 | Widget build(BuildContext context) {
15 | Widget icon = Image.asset(
16 | 'images/ic_launcher_round.png',
17 | width: 100.0,
18 | height: 100.0,
19 | );
20 |
21 | return Scaffold(
22 | appBar: AppBar(
23 | title: Text('关于'),
24 | ),
25 | body: ListView(
26 | padding: EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0),
27 | children: [
28 | icon,
29 | ListTile(
30 | title: const Text('关于项目'),
31 | subtitle: const Text(
32 | '项目是自己在学习Flutter的时候写的demo,模仿WanAndroid客户端,实现了完整的功能'),
33 | trailing: Icon(Icons.chevron_right,
34 | color: Theme.of(context).accentColor),
35 | onTap: () {
36 | Navigator.of(context)
37 | .push(MaterialPageRoute(builder: (context) {
38 | return ArticleDetailPage(
39 | title: 'WanAndroid_Flutter版',
40 | url: 'https://github.com/canhuah/WanAndroid',
41 | );
42 | }));
43 | }),
44 | ListTile(
45 | title: const Text('关于我'),
46 | subtitle: const Text('一个Android程序猿,初学Flutter,博客地址是..'),
47 | trailing: Icon(Icons.chevron_right,
48 | color: Theme.of(context).accentColor),
49 | onTap: () {
50 | Navigator.of(context)
51 | .push(MaterialPageRoute(builder: (context) {
52 | return ArticleDetailPage(
53 | title: 'canhuah的博客',
54 | url: 'http://www.canhuah.com',
55 | );
56 | }));
57 | }),
58 | ],
59 | ),
60 | );
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/lib/pages/article_detail_page.dart:
--------------------------------------------------------------------------------
1 |
2 | import 'package:flutter/material.dart';
3 | import 'package:webview_flutter/webview_flutter.dart';
4 | //文章详情界面
5 | class ArticleDetailPage extends StatefulWidget {
6 | final String title;
7 | final String url;
8 |
9 | ArticleDetailPage({
10 | Key? key,
11 | required this.title,
12 | required this.url,
13 | }) : super(key: key);
14 |
15 | @override
16 | State createState() {
17 | return ArticleDetailPageState();
18 | }
19 | }
20 |
21 | class ArticleDetailPageState extends State {
22 |
23 |
24 | @override
25 | Widget build(BuildContext context) {
26 | return Scaffold(
27 | appBar: AppBar(
28 | title: Text(widget.title),
29 | ),
30 | body: Container(
31 | color: Colors.white,
32 | child: WebView(
33 | initialUrl: widget.url,
34 | //JS执行模式 是否允许JS执行
35 | javascriptMode: JavascriptMode.unrestricted,
36 | onPageStarted: (String url) {
37 |
38 | },
39 | onPageFinished: (String url) {
40 |
41 | },
42 | onWebResourceError: (error) {
43 |
44 | },
45 |
46 | navigationDelegate: (NavigationRequest request) {
47 | ///拦截了简书文章里乱七八糟的跳转
48 | if (request.url.startsWith('http:') ||
49 | request.url.startsWith('https:')) {
50 |
51 | return NavigationDecision.navigate;
52 | }
53 | return NavigationDecision.prevent;
54 | },
55 |
56 | gestureNavigationEnabled: true,
57 | ),
58 | ),
59 | );
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/lib/pages/article_list_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:wanAndroid/http/api_manager.dart';
5 | import 'package:wanAndroid/item/article_item.dart';
6 |
7 | class ArticleListPage extends StatefulWidget {
8 | final String id;
9 |
10 | ArticleListPage(this.id);
11 |
12 | @override
13 | State createState() {
14 | return _ArticleListPageState();
15 | }
16 | }
17 |
18 | class _ArticleListPageState extends State
19 | with AutomaticKeepAliveClientMixin {
20 | @override
21 | bool get wantKeepAlive => true;
22 | int curPage = 0;
23 |
24 | Map map = Map();
25 | List listData = [];
26 | int listTotalSize = 0;
27 | ScrollController _controller = ScrollController();
28 |
29 | @override
30 | void initState() {
31 | super.initState();
32 |
33 | _getArticleList();
34 |
35 | _controller.addListener(() {
36 | double maxScroll = _controller.position.maxScrollExtent;
37 | double pixels = _controller.position.pixels;
38 |
39 | if (maxScroll == pixels && listData.length < listTotalSize) {
40 | _getArticleList();
41 | }
42 | });
43 | }
44 |
45 | // bool isDisposed = false;
46 |
47 | @override
48 | void dispose() {
49 | _controller.dispose();
50 | super.dispose();
51 | }
52 |
53 | @override
54 | Widget build(BuildContext context) {
55 | super.build(context);
56 | if (listData.isEmpty) {
57 | return Center(
58 | child: CircularProgressIndicator(),
59 | );
60 | } else {
61 | Widget listView = ListView.builder(
62 | key: ValueKey(widget.id),
63 | itemCount: listData.length,
64 | itemBuilder: (context, i) => ArticleItem(articleModel: listData[i]),
65 | controller: _controller,
66 | );
67 |
68 | return RefreshIndicator(child: listView, onRefresh: _pullToRefresh);
69 | }
70 | }
71 |
72 | void _getArticleList() async {
73 | ApiManager.instance.getArticleList(curPage,cid: widget.id,
74 | successCallback: (articleListModel) {
75 | setState(() {
76 | if (curPage == 0) {
77 | listData.clear();
78 | }
79 | curPage++;
80 |
81 | listData.addAll(articleListModel.datas ?? []);
82 | });
83 | });
84 | }
85 |
86 | Future _pullToRefresh() async {
87 | curPage = 0;
88 | _getArticleList();
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/lib/pages/articles_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:wanAndroid/model/tree_model.dart';
3 | import 'package:wanAndroid/pages/article_list_page.dart';
4 | import 'package:flutter/scheduler.dart' show timeDilation;
5 |
6 | class ArticlesPage extends StatefulWidget {
7 | final TreeModel treeModel;
8 |
9 | ArticlesPage(this.treeModel);
10 |
11 | @override
12 | State createState() {
13 | return _ArticlesPageState();
14 | }
15 | }
16 |
17 | class _ArticlesPageState extends State
18 | with SingleTickerProviderStateMixin {
19 | late TabController _tabControl;
20 | List tabs = [];
21 | late List list;
22 |
23 | @override
24 | void initState() {
25 | super.initState();
26 |
27 | list = widget.treeModel.children ?? [];
28 |
29 | for (TreeModel value in list) {
30 | tabs.add(Tab(text: value.name));
31 | }
32 |
33 | _tabControl = TabController(length: list.length, vsync: this);
34 | }
35 |
36 | @override
37 | void dispose() {
38 | _tabControl.dispose();
39 | super.dispose();
40 | }
41 |
42 | @override
43 | Widget build(BuildContext context) {
44 | timeDilation = 1.0;
45 |
46 | return Scaffold(
47 | appBar: AppBar(
48 | title: Text(widget.treeModel.name??""),
49 | ),
50 | body: DefaultTabController(
51 | length: list.length,
52 | child: Scaffold(
53 | appBar: TabBar(
54 | isScrollable: true,
55 | controller: _tabControl,
56 | labelColor: Theme.of(context).accentColor,
57 | unselectedLabelColor: Colors.black,
58 | indicatorColor: Theme.of(context).accentColor,
59 | tabs: tabs,
60 | ),
61 | body: TabBarView(
62 | controller: _tabControl,
63 | children: list.map((treeModel) {
64 | return ArticleListPage(treeModel.id.toString());
65 | }).toList(),
66 | )),
67 | ));
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/lib/pages/collect_list_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:wanAndroid/http/api_manager.dart';
5 | import 'package:wanAndroid/item/article_item.dart';
6 | import 'package:wanAndroid/model/article_model.dart';
7 | import 'package:wanAndroid/pages/login_page.dart';
8 | import 'package:wanAndroid/util/data_utils.dart';
9 |
10 | //收藏文章界面
11 | class CollectPage extends StatelessWidget {
12 | @override
13 | Widget build(BuildContext context) {
14 | return Scaffold(
15 | appBar: AppBar(
16 | title: Text('喜欢的文章'),
17 | ),
18 | body: CollectListPage(),
19 | );
20 | }
21 | }
22 |
23 | class CollectListPage extends StatefulWidget {
24 | CollectListPage();
25 |
26 | @override
27 | State createState() {
28 | return CollectListPageState();
29 | }
30 | }
31 |
32 | class CollectListPageState extends State {
33 | int curPage = 0;
34 |
35 | List listData = [];
36 | int listTotalSize = 0;
37 | ScrollController _control = ScrollController();
38 |
39 | CollectListPageState();
40 |
41 | @override
42 | void initState() {
43 | super.initState();
44 |
45 | _getCollectList();
46 |
47 | _control.addListener(() {
48 | double maxScroll = _control.position.maxScrollExtent;
49 | double pixels = _control.position.pixels;
50 |
51 | if (maxScroll == pixels && listData.length < listTotalSize) {
52 | _getCollectList();
53 | }
54 | });
55 | }
56 |
57 | @override
58 | void dispose() {
59 | _control.dispose();
60 | super.dispose();
61 | }
62 |
63 | @override
64 | Widget build(BuildContext context) {
65 | if (listData == null || listData.isEmpty) {
66 | return Center(
67 | child: CircularProgressIndicator(),
68 | );
69 | } else {
70 | Widget listView = ListView.builder(
71 | //
72 | physics: AlwaysScrollableScrollPhysics(),
73 | itemCount: listData.length,
74 | itemBuilder: (context, i) => ArticleItem(
75 | articleModel: listData[i],
76 | isFromCollect: true,
77 | onClickCollect: () {
78 | _handleListItemCollect(listData[i]);
79 | },
80 | ),
81 | controller: _control,
82 | );
83 |
84 | return RefreshIndicator(child: listView, onRefresh: _pullToRefresh);
85 | }
86 | }
87 |
88 | void _getCollectList() {
89 | ApiManager.instance.getCollectList(curPage,
90 | successCallback: (articleListModel) {
91 | setState(() {
92 | if (curPage == 0) {
93 | listData.clear();
94 | }
95 | curPage++;
96 |
97 | listData.addAll(articleListModel.datas ?? []);
98 | });
99 | });
100 | }
101 |
102 | Future _pullToRefresh() async {
103 | curPage = 0;
104 | _getCollectList();
105 | }
106 |
107 | void _handleListItemCollect(itemData) {
108 | DataUtils.isLogin().then((isLogin) {
109 | if (!isLogin) {
110 | // 未登录
111 | _login();
112 | } else {
113 | _itemUnCollect(itemData);
114 | }
115 | });
116 | }
117 |
118 | _login() {
119 | Navigator.of(context).push(MaterialPageRoute(builder: (context) {
120 | return LoginPage();
121 | }));
122 | }
123 |
124 | //取消收藏
125 | void _itemUnCollect(ArticleModel articleModel) {
126 | ApiManager.instance.unCollect(articleModel, successCallback: () {
127 | setState(() {
128 | listData.remove(articleModel);
129 | });
130 | });
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/lib/pages/home_list_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:wanAndroid/http/api_manager.dart';
5 | import 'package:wanAndroid/item/article_item.dart';
6 | import 'package:wanAndroid/model/bannel_model.dart';
7 | import 'package:wanAndroid/widget/slide_view.dart';
8 |
9 | class HomeListPage extends StatefulWidget {
10 | @override
11 | State createState() {
12 | return HomeListPageState();
13 | }
14 | }
15 |
16 | class HomeListPageState extends State {
17 | List listData = [];
18 |
19 | int curPage = 0;
20 | int listTotalSize = 0;
21 |
22 | ScrollController _controller = ScrollController();
23 | TextStyle titleTextStyle = TextStyle(fontSize: 15.0);
24 | TextStyle subtitleTextStyle = TextStyle(color: Colors.blue, fontSize: 12.0);
25 |
26 | List banners = [];
27 |
28 | @override
29 | void initState() {
30 | super.initState();
31 |
32 | _controller.addListener(() {
33 | double maxScroll = _controller.position.maxScrollExtent;
34 | double pixels = _controller.position.pixels;
35 |
36 | if (maxScroll == pixels && listData.length < listTotalSize) {
37 | getHomeArticleList();
38 | }
39 | });
40 |
41 |
42 | getBanner();
43 | getHomeArticleList();
44 | }
45 |
46 | @override
47 | void dispose() {
48 | _controller.dispose();
49 | super.dispose();
50 | }
51 |
52 | Future _pullToRefresh() async {
53 | curPage = 0;
54 | getBanner();
55 | getHomeArticleList();
56 | }
57 |
58 | @override
59 | Widget build(BuildContext context) {
60 | if (listData.isEmpty) {
61 | return Center(
62 | child: CircularProgressIndicator(),
63 | );
64 | } else {
65 | Widget listView = ListView.builder(
66 | itemCount: listData.length + 1,
67 | itemBuilder: (context, i) => buildItem(i),
68 | controller: _controller,
69 | );
70 |
71 | return RefreshIndicator(child: listView, onRefresh: _pullToRefresh);
72 | }
73 | }
74 |
75 | void getBanner() {
76 | ApiManager.instance.getBanner(successCallback: (banners) {
77 | setState(() {
78 | this.banners = banners;
79 | });
80 | });
81 | }
82 |
83 | void getHomeArticleList() {
84 | ApiManager.instance.getArticleList(curPage,
85 | successCallback: (articleListModel) {
86 | setState(() {
87 | if (curPage == 0) {
88 | listData.clear();
89 | }
90 | curPage++;
91 |
92 | listData.addAll(articleListModel.datas ?? []);
93 | });
94 | });
95 | }
96 |
97 | Widget buildItem(int i) {
98 | if (i == 0) {
99 | return Container(
100 | height: MediaQuery.of(context).size.width*5/9,
101 | child: SlideView(banners),
102 | );
103 | }
104 |
105 | return ArticleItem(
106 | articleModel: listData[i - 1],
107 | );
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/lib/pages/home_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:wanAndroid/constant/app_colors.dart';
3 | import 'package:wanAndroid/pages/home_list_page.dart';
4 | import 'package:wanAndroid/pages/myInfo_page.dart';
5 | import 'package:wanAndroid/pages/search_page.dart';
6 | import 'package:wanAndroid/pages/tree_page.dart';
7 |
8 | //主页
9 | class HomePage extends StatefulWidget {
10 | @override
11 | _HomePageState createState() => _HomePageState();
12 | }
13 |
14 | class _HomePageState extends State
15 | with TickerProviderStateMixin {
16 | int _tabIndex = 0;
17 |
18 | late List _navigationViews;
19 |
20 | List appBarTitles = ['首页', '发现', '我的'];
21 |
22 | Widget? _body;
23 |
24 | @override
25 | void initState() {
26 | super.initState();
27 |
28 | _navigationViews = [
29 | BottomNavigationBarItem(
30 | icon: const Icon(Icons.home),
31 | label: appBarTitles[0],
32 | backgroundColor: AppColors.colorPrimary,
33 | ),
34 | BottomNavigationBarItem(
35 | icon: const Icon(Icons.widgets),
36 | label: appBarTitles[1],
37 | backgroundColor: AppColors.colorPrimary,
38 | ),
39 | BottomNavigationBarItem(
40 | icon: const Icon(Icons.person),
41 | label: appBarTitles[2],
42 | backgroundColor: AppColors.colorPrimary,
43 | ),
44 | ];
45 | }
46 | @override
47 | Widget build(BuildContext context) {
48 | _body = IndexedStack(
49 | children: [HomeListPage(), TreePage(), MyInfoPage()],
50 | index: _tabIndex,
51 | );
52 |
53 | return Scaffold(
54 | appBar: AppBar(
55 | title: Text(
56 | appBarTitles[_tabIndex],
57 | style: TextStyle(color: Colors.white),
58 | ),
59 | actions: [
60 | IconButton(
61 | icon: Icon(Icons.search),
62 | onPressed: () {
63 |
64 | Navigator.of(context).push(MaterialPageRoute(builder: (context) {
65 | return SearchPage(searchStr: "");
66 | }));
67 |
68 | })
69 | ],
70 | ),
71 | body: _body,
72 | bottomNavigationBar: BottomNavigationBar(
73 | items: _navigationViews
74 | .map((BottomNavigationBarItem navigationView) => navigationView)
75 | .toList(),
76 | currentIndex: _tabIndex,
77 | type: BottomNavigationBarType.fixed,
78 | selectedFontSize: 10,
79 | unselectedFontSize: 10,
80 | onTap: (index) {
81 | setState(() {
82 | _tabIndex = index;
83 | });
84 | },
85 | ),
86 | );
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/lib/pages/hot_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:wanAndroid/http/api_manager.dart';
3 | import 'package:wanAndroid/model/friend_model.dart';
4 | import 'package:wanAndroid/model/hot_key_model.dart';
5 | import 'package:wanAndroid/pages/article_detail_page.dart';
6 | import 'package:wanAndroid/pages/search_page.dart';
7 |
8 | class HotPage extends StatefulWidget {
9 | @override
10 | State createState() {
11 | return _HotPageState();
12 | }
13 | }
14 |
15 | class _HotPageState extends State {
16 | List friends = [];
17 | List hotKeys = [];
18 |
19 | @override
20 | void initState() {
21 | super.initState();
22 | _getFriendList();
23 | _getHotKeyList();
24 | }
25 |
26 | @override
27 | Widget build(BuildContext context) {
28 | return ListView(
29 | children: [
30 | Padding(
31 | padding: EdgeInsets.all(8.0),
32 | child: Text('大家都在搜',
33 | style: TextStyle(
34 | color: Theme.of(context).primaryColor, fontSize: 16.0))),
35 | Wrap(
36 | spacing: 5.0,
37 | runSpacing: 1.0,
38 | children: hotKeys
39 | .map((hotKeyModel) => HotKeyCell(
40 | text: hotKeyModel.name ?? "",
41 | onPressed: () {
42 | Navigator.of(context).pushReplacement(
43 | MaterialPageRoute(builder: (context) {
44 | return SearchPage(searchStr: hotKeyModel.name ?? "");
45 | }));
46 | },
47 | ))
48 | .toList(),
49 | ),
50 | Padding(
51 | padding: EdgeInsets.all(8.0),
52 | child: Text('常用网站',
53 | style: TextStyle(
54 | color: Theme.of(context).primaryColor, fontSize: 16.0))),
55 | Wrap(
56 | spacing: 5.0,
57 | runSpacing: 1.0,
58 | children: friends
59 | .map((friendModel) => HotKeyCell(
60 | text: friendModel.name ?? "",
61 | onPressed: () {
62 | Navigator.of(context)
63 | .push(MaterialPageRoute(builder: (context) {
64 | return ArticleDetailPage(
65 | title: friendModel.name ?? "",
66 | url: friendModel.link ?? "");
67 | }));
68 | },
69 | ))
70 | .toList(),
71 | ),
72 | ],
73 | );
74 | }
75 |
76 | void _getFriendList() {
77 | ApiManager.instance.getFriendList(successCallback: (friendList) {
78 | setState(() {
79 | friends = friendList;
80 | });
81 | });
82 | }
83 |
84 | void _getHotKeyList() {
85 | ApiManager.instance.getHotKeyList(successCallback: (hotKeyList) {
86 | setState(() {
87 | hotKeys = hotKeyList;
88 | });
89 | });
90 | }
91 | }
92 |
93 | class HotKeyCell extends StatelessWidget {
94 | final String text;
95 |
96 | final VoidCallback onPressed;
97 |
98 | const HotKeyCell({Key? key, required this.text, required this.onPressed})
99 | : super(key: key);
100 |
101 | @override
102 | Widget build(BuildContext context) {
103 | return ActionChip(
104 | backgroundColor: Theme.of(context).primaryColor,
105 | label: Text(
106 | text,
107 | style: TextStyle(color: Colors.white, fontSize: 14),
108 | ),
109 | onPressed: onPressed);
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/lib/pages/login_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/cupertino.dart';
3 |
4 | import 'package:wanAndroid/constant/constants.dart';
5 | import 'package:wanAndroid/event/login_event.dart';
6 | import 'package:wanAndroid/http/api_manager.dart';
7 | import 'package:wanAndroid/util/data_utils.dart';
8 |
9 | //登录 键盘遮挡问题还没有解决 0_0
10 | class LoginPage extends StatefulWidget {
11 | @override
12 | State createState() {
13 | return LoginPageState();
14 | }
15 | }
16 |
17 | class LoginPageState extends State {
18 | TextEditingController _nameController =
19 | TextEditingController(text: 'canhuah');
20 | TextEditingController _passwordContro = TextEditingController(text: '111111');
21 | late GlobalKey scaffoldKey;
22 |
23 | @override
24 | void initState() {
25 | super.initState();
26 | scaffoldKey = GlobalKey();
27 | }
28 |
29 | @override
30 | Widget build(BuildContext context) {
31 | Row avatar = Row(
32 | mainAxisAlignment: MainAxisAlignment.center,
33 | children: [
34 | Icon(
35 | Icons.account_circle,
36 | color: Theme.of(context).accentColor,
37 | size: 80.0,
38 | ),
39 | ],
40 | );
41 |
42 | // CupertinoButton(child: null, onPressed: null);
43 |
44 | TextField name = TextField(
45 | autofocus: true,
46 | decoration: InputDecoration(
47 | labelText: "用户名",
48 | ),
49 | controller: _nameController,
50 | );
51 |
52 | TextField password = TextField(
53 | decoration: InputDecoration(labelText: "密码"),
54 | obscureText: true,
55 |
56 | controller: _passwordContro,
57 | // onChanged: ,
58 | );
59 |
60 | Row loginAndRegister = Row(
61 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
62 | children: [
63 | RaisedButton(
64 | child: Text(
65 | "登录",
66 | style: TextStyle(
67 | color: Colors.white,
68 | ),
69 | ),
70 | color: Theme.of(context).accentColor,
71 | disabledColor: Colors.blue,
72 | textColor: Colors.white,
73 | onPressed: () {
74 | _login();
75 | },
76 | ),
77 | RaisedButton(
78 | child: Text(
79 | "注册",
80 | style: TextStyle(
81 | color: Colors.white,
82 | ),
83 | ),
84 | color: Colors.blue,
85 | disabledColor: Colors.blue,
86 | textColor: Colors.white,
87 | onPressed: () {
88 | _register();
89 | },
90 | ),
91 | ],
92 | );
93 |
94 | return Scaffold(
95 | key: scaffoldKey,
96 | appBar: AppBar(
97 | title: Text('登录'),
98 | ),
99 | body: Padding(
100 | padding: EdgeInsets.fromLTRB(40.0, 10.0, 40.0, 0.0),
101 | child: ListView(
102 | children: [
103 | avatar,
104 | name,
105 | password,
106 | Padding(
107 | padding: EdgeInsets.fromLTRB(40.0, 10.0, 40.0, 0.0),
108 | ),
109 | loginAndRegister,
110 | ],
111 | ),
112 | ),
113 | );
114 | }
115 |
116 | void _login() {
117 | String name = _nameController.text;
118 | String password = _passwordContro.text;
119 | if (name.isEmpty) {
120 | _showMessage('请先输入姓名');
121 | return;
122 | }
123 | if (password.isEmpty) {
124 | _showMessage('请先输入密码');
125 | return;
126 | }
127 |
128 | ApiManager.instance.login(name, password, successCallback: () {
129 | DataUtils.saveLoginInfo(name).then((r) {
130 | Constants.eventBus.fire(LoginEvent());
131 | Navigator.of(context).pop();
132 | });
133 | });
134 | }
135 |
136 | void _register() {
137 | String name = _nameController.text;
138 | String password = _passwordContro.text;
139 | if (name.isEmpty) {
140 | _showMessage('请先输入姓名');
141 | return;
142 | }
143 | if (password.isEmpty) {
144 | _showMessage('请先输入密码');
145 | return;
146 | }
147 | Map map = Map();
148 | map['username'] = name;
149 | map['password'] = password;
150 | map['repassword'] = password;
151 |
152 | ApiManager.instance.register(name, password, successCallback: () {
153 | DataUtils.saveLoginInfo(name).then((r) {
154 | Constants.eventBus.fire(LoginEvent());
155 | Navigator.of(context).pop();
156 | });
157 | });
158 | }
159 |
160 | void _showMessage(String msg) {
161 | scaffoldKey.currentState!.showSnackBar(SnackBar(content: Text(msg)));
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/lib/pages/myInfo_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:wanAndroid/constant/constants.dart';
3 | import 'package:wanAndroid/event/login_event.dart';
4 |
5 | import 'package:wanAndroid/pages/about_us_page.dart';
6 | import 'package:wanAndroid/pages/collect_list_page.dart';
7 | import 'package:wanAndroid/pages/login_page.dart';
8 | import 'package:wanAndroid/util/data_utils.dart';
9 |
10 | class MyInfoPage extends StatefulWidget {
11 | @override
12 | State createState() {
13 | return MyInfoPageState();
14 | }
15 | }
16 |
17 | class MyInfoPageState extends State with WidgetsBindingObserver {
18 | String? userName;
19 |
20 | @override
21 | void initState() {
22 | super.initState();
23 |
24 | _getName();
25 |
26 | Constants.eventBus.on().listen((event) {
27 | _getName();
28 | });
29 | }
30 |
31 | void _getName() async {
32 | DataUtils.getUserName().then((username) {
33 | setState(() {
34 | userName = username;
35 | print('name:' + userName.toString());
36 | });
37 | });
38 | }
39 |
40 | @override
41 | Widget build(BuildContext context) {
42 | Widget image = Image.asset(
43 | 'images/ic_launcher_round.png',
44 | width: 100.0,
45 | height: 100.0,
46 | );
47 |
48 | Widget raisedButton = RaisedButton(
49 | child: Text(
50 | userName ?? "请登录" ,
51 | style: TextStyle(color: Colors.white),
52 | ),
53 | color: Theme.of(context).accentColor,
54 | onPressed: () async {
55 | //登录
56 |
57 | await DataUtils.isLogin().then((isLogin) {
58 | if (!isLogin) {
59 | Navigator.of(context).push(MaterialPageRoute(builder: (context) {
60 | return LoginPage();
61 | }));
62 | } else {
63 | print('已登录!');
64 | }
65 | });
66 | },
67 | );
68 |
69 | Widget listLike = ListTile(
70 | leading: const Icon(Icons.favorite),
71 | title: const Text('喜欢的文章'),
72 | trailing:
73 | Icon(Icons.chevron_right, color: Theme.of(context).accentColor),
74 | onTap: () async {
75 | await DataUtils.isLogin().then((isLogin) {
76 | if (isLogin) {
77 | Navigator.of(context).push(MaterialPageRoute(builder: (context) {
78 | return CollectPage();
79 | }));
80 | } else {
81 | print('已登录!');
82 | Navigator.of(context).push(MaterialPageRoute(builder: (context) {
83 | return LoginPage();
84 | }));
85 | }
86 | });
87 | });
88 |
89 | Widget listAbout = ListTile(
90 | leading: const Icon(Icons.info),
91 | title: const Text('关于我们'),
92 | trailing:
93 | Icon(Icons.chevron_right, color: Theme.of(context).accentColor),
94 | onTap: () {
95 | Navigator.of(context).push(MaterialPageRoute(builder: (context) {
96 | return AboutUsPage();
97 | }));
98 | });
99 |
100 | Widget listLogout = ListTile(
101 | leading: const Icon(Icons.info),
102 | title: const Text('退出登录'),
103 | trailing:
104 | Icon(Icons.chevron_right, color: Theme.of(context).accentColor),
105 | onTap: () async {
106 | DataUtils.clearLoginInfo();
107 | setState(() {
108 | userName = null;
109 | });
110 | });
111 |
112 | return ListView(
113 | padding: const EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0),
114 | children: [
115 | image,
116 | raisedButton,
117 | listLike,
118 | listAbout,
119 | listLogout,
120 | ],
121 | );
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/lib/pages/search_list_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:wanAndroid/http/api_manager.dart';
5 | import 'package:wanAndroid/item/article_item.dart';
6 | import 'package:wanAndroid/model/article_model.dart';
7 |
8 | class SearchListPage extends StatefulWidget {
9 | final String id;
10 |
11 | //这里为什么用含有key的这个构造,大家可以试一下不带key 直接SearchListPage(this.id) ,看看会有什么bug;
12 |
13 | SearchListPage(this.id) : super(key: ValueKey(id));
14 |
15 | @override
16 | State createState() {
17 | return SearchListPageState();
18 | }
19 | }
20 |
21 | class SearchListPageState extends State {
22 | int curPage = 0;
23 |
24 | Map map = Map();
25 | List listData = [];
26 | int listTotalSize = 0;
27 | ScrollController _controller = ScrollController();
28 |
29 | @override
30 | void initState() {
31 | super.initState();
32 |
33 | _controller.addListener(() {
34 | double maxScroll = _controller.position.maxScrollExtent;
35 | double pixels = _controller.position.pixels;
36 |
37 | if (maxScroll == pixels && listData.length < listTotalSize) {
38 | _articleQuery();
39 | }
40 | });
41 |
42 | _articleQuery();
43 | }
44 |
45 | @override
46 | void dispose() {
47 | _controller.dispose();
48 | super.dispose();
49 | }
50 |
51 | @override
52 | Widget build(BuildContext context) {
53 | if (listData.isEmpty) {
54 | return Center(
55 | child: CircularProgressIndicator(),
56 | );
57 | } else {
58 | Widget listView = ListView.builder(
59 | itemCount: listData.length,
60 | itemBuilder: (context, i) => ArticleItem(
61 | articleModel: listData[i], id: widget.id, isSearch: true),
62 | controller: _controller,
63 | );
64 |
65 | return RefreshIndicator(child: listView, onRefresh: pullToRefresh);
66 | }
67 | }
68 |
69 | void _articleQuery() {
70 | ApiManager.instance.queryArticleList(curPage, widget.id,
71 | successCallback: (articleListModel) {
72 | setState(() {
73 | if (curPage == 0) {
74 | listData.clear();
75 | }
76 | curPage++;
77 |
78 | listData.addAll(articleListModel.datas ?? []);
79 | });
80 | });
81 | }
82 |
83 | Future pullToRefresh() async {
84 | curPage = 0;
85 | _articleQuery();
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/lib/pages/search_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:wanAndroid/pages/hot_page.dart';
3 | import 'package:wanAndroid/pages/search_list_page.dart';
4 |
5 | class SearchPage extends StatefulWidget {
6 | final String searchStr;
7 |
8 | const SearchPage({Key? key, required this.searchStr}) : super(key: key);
9 |
10 | @override
11 | _SearchPageState createState() => _SearchPageState();
12 | }
13 |
14 | class _SearchPageState extends State {
15 | late TextEditingController _searchController;
16 |
17 | @override
18 | void initState() {
19 | super.initState();
20 |
21 | _searchController = TextEditingController(text: widget.searchStr);
22 | }
23 |
24 | void changeContent() {
25 | setState(() {});
26 | }
27 |
28 | @override
29 | Widget build(BuildContext context) {
30 | TextField searchField = TextField(
31 | autofocus: true,
32 | textInputAction: TextInputAction.search,
33 | onSubmitted: (string) {
34 | changeContent();
35 | },
36 | style: TextStyle(color: Colors.white),
37 | cursorColor: Colors.white,
38 | decoration: InputDecoration(
39 | border: InputBorder.none,
40 | hintText: '搜索关键词',
41 | hintStyle: TextStyle(color: Colors.white)),
42 | controller: _searchController,
43 | );
44 |
45 | return Scaffold(
46 | appBar: AppBar(
47 | title: searchField,
48 | actions: [
49 | IconButton(
50 | icon: Icon(Icons.search),
51 | onPressed: () {
52 | changeContent();
53 | }),
54 | IconButton(
55 | icon: Icon(Icons.close),
56 | onPressed: () {
57 | setState(() {
58 | _searchController.clear();
59 | });
60 | }),
61 | ],
62 | ),
63 | body: (_searchController.text.isEmpty)
64 | ? Center(
65 | child: HotPage(),
66 | )
67 | : SearchListPage(_searchController.text),
68 | );
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/lib/pages/tree_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:wanAndroid/http/api_manager.dart';
3 | import 'package:wanAndroid/model/tree_model.dart';
4 | import 'package:wanAndroid/pages/articles_page.dart';
5 |
6 | //知识体系
7 | class TreePage extends StatefulWidget {
8 | @override
9 | State createState() {
10 | return TreePageState();
11 | }
12 | }
13 |
14 | class TreePageState extends State {
15 | List? treeList;
16 |
17 | @override
18 | void initState() {
19 | super.initState();
20 | _getTree();
21 | }
22 |
23 | @override
24 | Widget build(BuildContext context) {
25 | if (treeList == null) {
26 | return Center(
27 | child: CircularProgressIndicator(),
28 | );
29 | } else {
30 | Widget listView = ListView.builder(
31 | itemCount: treeList!.length,
32 | itemBuilder: (context, i) => TreeCell(treeModel: treeList![i]),
33 | );
34 |
35 | return listView;
36 | }
37 | }
38 |
39 | _getTree() async {
40 |
41 | ApiManager.instance.getTreeList(successCallback: (trees){
42 | setState(() {
43 | treeList = trees;
44 | });
45 | });
46 | }
47 | }
48 |
49 | class TreeCell extends StatelessWidget {
50 | final TreeModel treeModel;
51 |
52 | const TreeCell({Key? key,required this.treeModel}) : super(key: key);
53 |
54 | @override
55 | Widget build(BuildContext context) {
56 | Text name = Text(
57 | treeModel.name ?? "",
58 | softWrap: true,
59 | style: TextStyle(
60 | fontSize: 16.0, color: Colors.black, fontWeight: FontWeight.bold),
61 | textAlign: TextAlign.left,
62 | );
63 |
64 | List list = treeModel.children ?? [];
65 |
66 | String strContent = '';
67 |
68 | for (TreeModel value in list) {
69 | strContent += '${value.name} ';
70 | }
71 |
72 | Text content = Text(
73 | strContent,
74 | softWrap: true,
75 | style: TextStyle(color: Colors.black),
76 | textAlign: TextAlign.left,
77 | );
78 |
79 | return InkWell(
80 | onTap: () {
81 | _handOnItemClick(context);
82 | },
83 | child: Container(
84 | padding: EdgeInsets.all(15.0),
85 | margin: EdgeInsets.all(4),
86 | decoration: BoxDecoration(
87 | color: Colors.white, borderRadius: BorderRadius.circular(4)),
88 | child: Row(
89 | children: [
90 | Expanded(
91 | child: Column(
92 | crossAxisAlignment: CrossAxisAlignment.start,
93 | children: [
94 | Container(
95 | padding: const EdgeInsets.only(bottom: 8.0),
96 | child: name,
97 | ),
98 | content,
99 | ],
100 | ),
101 | ),
102 | Icon(
103 | Icons.chevron_right,
104 | color: Colors.black,
105 | ),
106 | ],
107 | ),
108 | ),
109 | );
110 | }
111 |
112 | void _handOnItemClick(BuildContext context) {
113 | Navigator.of(context).push(MaterialPageRoute(builder: (context) {
114 | return ArticlesPage(treeModel);
115 | }));
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/lib/pages/welcome_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:wanAndroid/constant/app_colors.dart';
3 | import 'package:wanAndroid/http/api_manager.dart';
4 | import 'package:wanAndroid/pages/home_page.dart';
5 |
6 | class WelcomePage extends StatefulWidget {
7 | const WelcomePage({Key? key}) : super(key: key);
8 |
9 | @override
10 | _WelcomePageState createState() => _WelcomePageState();
11 | }
12 |
13 | class _WelcomePageState extends State {
14 | @override
15 | void initState() {
16 | super.initState();
17 |
18 | init();
19 | }
20 |
21 | init() async {
22 | WidgetsBinding.instance!.addPostFrameCallback((timeStamp) async {
23 | await ApiManager.instance.initClient();
24 |
25 | Future.delayed(Duration(milliseconds: 1000), () {
26 | Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (context) {
27 | return HomePage();
28 | }));
29 | });
30 | });
31 | }
32 |
33 | @override
34 | Widget build(BuildContext context) {
35 | return Container(
36 | alignment: Alignment.center,
37 | color: AppColors.colorPrimary,
38 | child: Image.asset(
39 | "images/ic_logo.png",
40 | fit: BoxFit.fill,
41 | width: MediaQuery.of(context).size.width,
42 | ));
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/lib/util/data_utils.dart:
--------------------------------------------------------------------------------
1 | import 'package:shared_preferences/shared_preferences.dart';
2 | import 'dart:async';
3 |
4 | class DataUtils {
5 | static const String IS_LOGIN = "isLogin";
6 | static const String USERNAME = "userName";
7 |
8 | // 保存用户登录信息,data中包含了userName
9 | static Future saveLoginInfo(String userName) async {
10 | print('isLogin');
11 | SharedPreferences sp = await SharedPreferences.getInstance();
12 | await sp.setString(USERNAME, userName);
13 | await sp.setBool(IS_LOGIN, true);
14 | }
15 |
16 | static Future clearLoginInfo() async {
17 | SharedPreferences sp = await SharedPreferences.getInstance();
18 | print('clean');
19 | return sp.clear();
20 | }
21 |
22 | static Future getUserName() async {
23 | SharedPreferences sp = await SharedPreferences.getInstance();
24 | return sp.getString(USERNAME) ?? "";
25 | }
26 |
27 | static Future isLogin() async {
28 | SharedPreferences sp = await SharedPreferences.getInstance();
29 | bool b = sp.getBool(IS_LOGIN) ?? false;
30 | return true == b;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/lib/util/string_utils.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/widgets.dart';
2 | import 'package:wanAndroid/constant/app_colors.dart';
3 |
4 | class StringUtils {
5 |
6 | static TextSpan getTextSpan(String? text, String? key) {
7 | if (text == null || key == null) {
8 | return TextSpan(text: "",children: []);
9 | }
10 |
11 |
12 | String splitString1 = "";
13 | String splitString2 = "";
14 |
15 | String textOrigin =
16 | text.replaceAll(splitString1, '').replaceAll(splitString2, '');
17 |
18 | TextSpan textSpan = new TextSpan(
19 | text: key, style: new TextStyle(color: AppColors.colorPrimary));
20 |
21 | List split = textOrigin.split(key);
22 |
23 | List list = [];
24 |
25 | for (int i = 0; i < split.length; i++) {
26 | list.add(new TextSpan(text: split[i]));
27 | list.add(textSpan);
28 | }
29 |
30 | list.removeAt(list.length - 1);
31 |
32 | return new TextSpan(children: list);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/lib/widget/end_line.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class EndLine extends StatelessWidget {
4 | @override
5 | Widget build(BuildContext context) {
6 | return new Container(
7 | color: const Color(0xFFEEEEEE),
8 | padding: const EdgeInsets.fromLTRB(5.0, 15.0, 5.0, 15.0),
9 | child: new Row(
10 | children: [
11 | new Expanded(
12 | child: new Divider(height: 10.0,),
13 | flex: 1,
14 | ),
15 | new Text("我是有底线的",style: new TextStyle(color: Theme.of(context).accentColor),),
16 | new Expanded(
17 | child: new Divider(height: 10.0,),
18 | flex: 1,
19 | ),
20 | ],
21 | ),
22 | );
23 | }
24 | }
25 | class NoData extends StatelessWidget {
26 | @override
27 | Widget build(BuildContext context) {
28 | return new Container(
29 | color: const Color(0xFFEEEEEE),
30 | padding: const EdgeInsets.fromLTRB(5.0, 15.0, 5.0, 15.0),
31 | child: new Row(
32 | children: [
33 | new Expanded(
34 | child: new Divider(height: 10.0,),
35 | flex: 1,
36 | ),
37 | new Text("暂无数据",style: new TextStyle(color: Theme.of(context).accentColor),),
38 | new Expanded(
39 | child: new Divider(height: 10.0,),
40 | flex: 1,
41 | ),
42 | ],
43 | ),
44 | );
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/lib/widget/slide_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:wanAndroid/model/bannel_model.dart';
3 | import 'package:wanAndroid/pages/article_detail_page.dart';
4 |
5 | class SlideView extends StatefulWidget {
6 | final List list;
7 |
8 | SlideView(this.list);
9 |
10 | @override
11 | State createState() {
12 | return _SlideViewState();
13 | }
14 | }
15 |
16 | class _SlideViewState extends State
17 | with SingleTickerProviderStateMixin {
18 | late TabController tabController;
19 |
20 | @override
21 | void initState() {
22 | super.initState();
23 |
24 | tabController = TabController(
25 | length: widget.list.length, vsync: this);
26 | }
27 |
28 | @override
29 | void dispose() {
30 | tabController.dispose();
31 | super.dispose();
32 | }
33 |
34 | @override
35 | Widget build(BuildContext context) {
36 | List items = [];
37 | if (widget.list.isNotEmpty) {
38 | for (int i = 0; i < widget.list.length; i++) {
39 | BannerModel item = widget.list[i];
40 | String imgUrl = item.imagePath??"";
41 | String title = item.title??"";
42 | items.add(GestureDetector(
43 | onTap: () {
44 | _handOnItemClick(item);
45 | },
46 | child: AspectRatio(
47 | aspectRatio: 9.0 / 5.0,
48 | child: Stack(
49 | children: [
50 | Image.network(
51 | imgUrl,
52 | fit: BoxFit.fill,
53 | width: 1000.0,
54 | ),
55 | Align(
56 | alignment: FractionalOffset.bottomCenter,
57 | child: Container(
58 | width: 1000.0,
59 | color: const Color(0x50000000),
60 | padding: const EdgeInsets.all(5.0),
61 | child: Text(title,
62 | style:
63 | TextStyle(color: Colors.white, fontSize: 15.0)),
64 | ),
65 | ),
66 | ],
67 | ),
68 | )));
69 | }
70 | }
71 | return TabBarView(
72 | controller: tabController,
73 | children: items,
74 | );
75 | }
76 |
77 | void _handOnItemClick(BannerModel bannerModel) {
78 | Navigator.of(context).push(MaterialPageRoute(builder: (context) {
79 | return ArticleDetailPage(
80 | title: bannerModel.title ?? "", url: bannerModel.url ?? "");
81 | }));
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | async:
5 | dependency: transitive
6 | description:
7 | name: async
8 | url: "https://pub.flutter-io.cn"
9 | source: hosted
10 | version: "2.6.1"
11 | boolean_selector:
12 | dependency: transitive
13 | description:
14 | name: boolean_selector
15 | url: "https://pub.flutter-io.cn"
16 | source: hosted
17 | version: "2.1.0"
18 | characters:
19 | dependency: transitive
20 | description:
21 | name: characters
22 | url: "https://pub.flutter-io.cn"
23 | source: hosted
24 | version: "1.1.0"
25 | charcode:
26 | dependency: transitive
27 | description:
28 | name: charcode
29 | url: "https://pub.flutter-io.cn"
30 | source: hosted
31 | version: "1.2.0"
32 | clock:
33 | dependency: transitive
34 | description:
35 | name: clock
36 | url: "https://pub.flutter-io.cn"
37 | source: hosted
38 | version: "1.1.0"
39 | collection:
40 | dependency: transitive
41 | description:
42 | name: collection
43 | url: "https://pub.flutter-io.cn"
44 | source: hosted
45 | version: "1.15.0"
46 | cookie_jar:
47 | dependency: transitive
48 | description:
49 | name: cookie_jar
50 | url: "https://pub.flutter-io.cn"
51 | source: hosted
52 | version: "3.0.1"
53 | cupertino_icons:
54 | dependency: "direct main"
55 | description:
56 | name: cupertino_icons
57 | url: "https://pub.flutter-io.cn"
58 | source: hosted
59 | version: "1.0.4"
60 | dio:
61 | dependency: "direct main"
62 | description:
63 | name: dio
64 | url: "https://pub.flutter-io.cn"
65 | source: hosted
66 | version: "4.0.4"
67 | dio_cookie_manager:
68 | dependency: "direct main"
69 | description:
70 | name: dio_cookie_manager
71 | url: "https://pub.flutter-io.cn"
72 | source: hosted
73 | version: "2.0.0"
74 | event_bus:
75 | dependency: "direct main"
76 | description:
77 | name: event_bus
78 | url: "https://pub.flutter-io.cn"
79 | source: hosted
80 | version: "2.0.0"
81 | fake_async:
82 | dependency: transitive
83 | description:
84 | name: fake_async
85 | url: "https://pub.flutter-io.cn"
86 | source: hosted
87 | version: "1.2.0"
88 | ffi:
89 | dependency: transitive
90 | description:
91 | name: ffi
92 | url: "https://pub.flutter-io.cn"
93 | source: hosted
94 | version: "1.1.2"
95 | file:
96 | dependency: transitive
97 | description:
98 | name: file
99 | url: "https://pub.flutter-io.cn"
100 | source: hosted
101 | version: "6.1.2"
102 | flutter:
103 | dependency: "direct main"
104 | description: flutter
105 | source: sdk
106 | version: "0.0.0"
107 | flutter_test:
108 | dependency: "direct dev"
109 | description: flutter
110 | source: sdk
111 | version: "0.0.0"
112 | flutter_web_plugins:
113 | dependency: transitive
114 | description: flutter
115 | source: sdk
116 | version: "0.0.0"
117 | http_parser:
118 | dependency: transitive
119 | description:
120 | name: http_parser
121 | url: "https://pub.flutter-io.cn"
122 | source: hosted
123 | version: "4.0.0"
124 | js:
125 | dependency: transitive
126 | description:
127 | name: js
128 | url: "https://pub.flutter-io.cn"
129 | source: hosted
130 | version: "0.6.3"
131 | matcher:
132 | dependency: transitive
133 | description:
134 | name: matcher
135 | url: "https://pub.flutter-io.cn"
136 | source: hosted
137 | version: "0.12.10"
138 | meta:
139 | dependency: transitive
140 | description:
141 | name: meta
142 | url: "https://pub.flutter-io.cn"
143 | source: hosted
144 | version: "1.3.0"
145 | path:
146 | dependency: transitive
147 | description:
148 | name: path
149 | url: "https://pub.flutter-io.cn"
150 | source: hosted
151 | version: "1.8.0"
152 | path_provider:
153 | dependency: "direct main"
154 | description:
155 | name: path_provider
156 | url: "https://pub.flutter-io.cn"
157 | source: hosted
158 | version: "2.0.4"
159 | path_provider_linux:
160 | dependency: transitive
161 | description:
162 | name: path_provider_linux
163 | url: "https://pub.flutter-io.cn"
164 | source: hosted
165 | version: "2.1.2"
166 | path_provider_macos:
167 | dependency: transitive
168 | description:
169 | name: path_provider_macos
170 | url: "https://pub.flutter-io.cn"
171 | source: hosted
172 | version: "2.0.3"
173 | path_provider_platform_interface:
174 | dependency: transitive
175 | description:
176 | name: path_provider_platform_interface
177 | url: "https://pub.flutter-io.cn"
178 | source: hosted
179 | version: "2.0.1"
180 | path_provider_windows:
181 | dependency: transitive
182 | description:
183 | name: path_provider_windows
184 | url: "https://pub.flutter-io.cn"
185 | source: hosted
186 | version: "2.0.4"
187 | platform:
188 | dependency: transitive
189 | description:
190 | name: platform
191 | url: "https://pub.flutter-io.cn"
192 | source: hosted
193 | version: "3.0.2"
194 | plugin_platform_interface:
195 | dependency: transitive
196 | description:
197 | name: plugin_platform_interface
198 | url: "https://pub.flutter-io.cn"
199 | source: hosted
200 | version: "2.0.2"
201 | process:
202 | dependency: transitive
203 | description:
204 | name: process
205 | url: "https://pub.flutter-io.cn"
206 | source: hosted
207 | version: "4.2.3"
208 | pull_to_refresh:
209 | dependency: "direct main"
210 | description:
211 | name: pull_to_refresh
212 | url: "https://pub.flutter-io.cn"
213 | source: hosted
214 | version: "2.0.0"
215 | shared_preferences:
216 | dependency: "direct main"
217 | description:
218 | name: shared_preferences
219 | url: "https://pub.flutter-io.cn"
220 | source: hosted
221 | version: "2.0.7"
222 | shared_preferences_linux:
223 | dependency: transitive
224 | description:
225 | name: shared_preferences_linux
226 | url: "https://pub.flutter-io.cn"
227 | source: hosted
228 | version: "2.0.3"
229 | shared_preferences_macos:
230 | dependency: transitive
231 | description:
232 | name: shared_preferences_macos
233 | url: "https://pub.flutter-io.cn"
234 | source: hosted
235 | version: "2.0.2"
236 | shared_preferences_platform_interface:
237 | dependency: transitive
238 | description:
239 | name: shared_preferences_platform_interface
240 | url: "https://pub.flutter-io.cn"
241 | source: hosted
242 | version: "2.0.0"
243 | shared_preferences_web:
244 | dependency: transitive
245 | description:
246 | name: shared_preferences_web
247 | url: "https://pub.flutter-io.cn"
248 | source: hosted
249 | version: "2.0.2"
250 | shared_preferences_windows:
251 | dependency: transitive
252 | description:
253 | name: shared_preferences_windows
254 | url: "https://pub.flutter-io.cn"
255 | source: hosted
256 | version: "2.0.3"
257 | sky_engine:
258 | dependency: transitive
259 | description: flutter
260 | source: sdk
261 | version: "0.0.99"
262 | source_span:
263 | dependency: transitive
264 | description:
265 | name: source_span
266 | url: "https://pub.flutter-io.cn"
267 | source: hosted
268 | version: "1.8.1"
269 | stack_trace:
270 | dependency: transitive
271 | description:
272 | name: stack_trace
273 | url: "https://pub.flutter-io.cn"
274 | source: hosted
275 | version: "1.10.0"
276 | stream_channel:
277 | dependency: transitive
278 | description:
279 | name: stream_channel
280 | url: "https://pub.flutter-io.cn"
281 | source: hosted
282 | version: "2.1.0"
283 | string_scanner:
284 | dependency: transitive
285 | description:
286 | name: string_scanner
287 | url: "https://pub.flutter-io.cn"
288 | source: hosted
289 | version: "1.1.0"
290 | term_glyph:
291 | dependency: transitive
292 | description:
293 | name: term_glyph
294 | url: "https://pub.flutter-io.cn"
295 | source: hosted
296 | version: "1.2.0"
297 | test_api:
298 | dependency: transitive
299 | description:
300 | name: test_api
301 | url: "https://pub.flutter-io.cn"
302 | source: hosted
303 | version: "0.3.0"
304 | typed_data:
305 | dependency: transitive
306 | description:
307 | name: typed_data
308 | url: "https://pub.flutter-io.cn"
309 | source: hosted
310 | version: "1.3.0"
311 | vector_math:
312 | dependency: transitive
313 | description:
314 | name: vector_math
315 | url: "https://pub.flutter-io.cn"
316 | source: hosted
317 | version: "2.1.0"
318 | webview_flutter:
319 | dependency: "direct main"
320 | description:
321 | name: webview_flutter
322 | url: "https://pub.flutter-io.cn"
323 | source: hosted
324 | version: "2.0.13"
325 | win32:
326 | dependency: transitive
327 | description:
328 | name: win32
329 | url: "https://pub.flutter-io.cn"
330 | source: hosted
331 | version: "2.2.10"
332 | xdg_directories:
333 | dependency: transitive
334 | description:
335 | name: xdg_directories
336 | url: "https://pub.flutter-io.cn"
337 | source: hosted
338 | version: "0.2.0"
339 | sdks:
340 | dart: ">=2.13.0 <3.0.0"
341 | flutter: ">=2.0.0"
342 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: wanAndroid
2 | description: WanAndroid 客户端 Flutter版.
3 | environment:
4 | sdk: ">=2.12.0 <3.0.0"
5 | dependencies:
6 |
7 | flutter:
8 | sdk: flutter
9 |
10 | cupertino_icons: ^1.0.2
11 | webview_flutter: ^2.0.10
12 | shared_preferences: ^2.0.6
13 | dio: 4.0.4
14 | dio_cookie_manager: 2.0.0
15 | pull_to_refresh: 2.0.0
16 | event_bus: 2.0.0
17 |
18 | path_provider: ^2.0.4
19 |
20 | dev_dependencies:
21 | flutter_test:
22 | sdk: flutter
23 |
24 |
25 | flutter:
26 |
27 | uses-material-design: true
28 |
29 | assets:
30 | - images/
31 |
32 |
--------------------------------------------------------------------------------
/screenshot/canhuah_flutter_wanandriod_about.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/screenshot/canhuah_flutter_wanandriod_about.png
--------------------------------------------------------------------------------
/screenshot/canhuah_flutter_wanandriod_collect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/screenshot/canhuah_flutter_wanandriod_collect.png
--------------------------------------------------------------------------------
/screenshot/canhuah_flutter_wanandriod_find.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/screenshot/canhuah_flutter_wanandriod_find.png
--------------------------------------------------------------------------------
/screenshot/canhuah_flutter_wanandriod_home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/screenshot/canhuah_flutter_wanandriod_home.png
--------------------------------------------------------------------------------
/screenshot/canhuah_flutter_wanandriod_hot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/screenshot/canhuah_flutter_wanandriod_hot.png
--------------------------------------------------------------------------------
/screenshot/canhuah_flutter_wanandriod_search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/screenshot/canhuah_flutter_wanandriod_search.png
--------------------------------------------------------------------------------
/screenshot/canhuah_flutter_wanandriod_tree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/screenshot/canhuah_flutter_wanandriod_tree.png
--------------------------------------------------------------------------------
/screenshot/canhuah_flutter_wanandriod_webdetail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canhuah/WanAndroid-Flutter/72ba6e377640a8a1158c6f954cf0c46cfc8c14c3/screenshot/canhuah_flutter_wanandriod_webdetail.png
--------------------------------------------------------------------------------
/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 | import 'package:flutter/material.dart';
8 | import 'package:flutter_test/flutter_test.dart';
9 |
10 | import 'package:wanAndroid/pages/home_page.dart';
11 |
12 |
13 | void main() {
14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
15 | // Build our app and trigger a frame.
16 | await tester.pumpWidget(new HomePage());
17 |
18 | // Verify that our counter starts at 0.
19 | expect(find.text('0'), findsOneWidget);
20 | expect(find.text('1'), findsNothing);
21 |
22 | // Tap the '+' icon and trigger a frame.
23 | await tester.tap(find.byIcon(Icons.add));
24 | await tester.pump();
25 |
26 | // Verify that our counter has incremented.
27 | expect(find.text('0'), findsNothing);
28 | expect(find.text('1'), findsOneWidget);
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/wanandroid.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------