├── .gitignore
├── .metadata
├── LICENSE
├── README.md
├── android
├── .gitignore
├── app
│ ├── build.gradle
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── com
│ │ │ └── cfelixmac
│ │ │ └── gankio
│ │ │ └── MainActivity.java
│ │ └── res
│ │ ├── drawable
│ │ └── launch_background.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ └── values
│ │ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
├── doc
├── gank_io_flutter_v1.0.apk
├── logo_c.png
├── screen1.jpg
├── screen2.jpg
├── screen3.jpg
├── screen4.jpg
└── screen5.jpg
├── gankio.iml
├── gankio_android.iml
├── images
├── android_icon.png
├── app_icon.png
├── doc_icon.png
├── extend_icon.png
├── front_icon.png
├── gankio.png
├── ios_icon.png
└── relax_icon.png
├── ios
├── .gitignore
├── Flutter
│ ├── AppFrameworkInfo.plist
│ ├── Debug.xcconfig
│ └── Release.xcconfig
├── Podfile
├── Podfile.lock
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── Runner
│ ├── AppDelegate.h
│ ├── AppDelegate.m
│ ├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── 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-57x57@1x.png
│ │ ├── Icon-App-57x57@2x.png
│ │ ├── Icon-App-60x60@1x.png
│ │ ├── Icon-App-60x60@2x.png
│ │ ├── Icon-App-60x60@3x.png
│ │ ├── Icon-App-72x72@1x.png
│ │ ├── Icon-App-72x72@2x.png
│ │ ├── Icon-App-76x76@1x.png
│ │ ├── Icon-App-76x76@2x.png
│ │ ├── Icon-App-76x76@3x.png
│ │ ├── Icon-App-83.5x83.5@2x.png
│ │ ├── Icon-Small-50x50@1x.png
│ │ ├── Icon-Small-50x50@2x.png
│ │ └── ItunesArtwork@2x.png
│ ├── LaunchImage.imageset
│ │ ├── Contents.json
│ │ ├── LaunchImage.png
│ │ ├── LaunchImage@2x.png
│ │ ├── LaunchImage@3x.png
│ │ └── README.md
│ ├── iTunesArtwork@1x.png
│ ├── iTunesArtwork@2x.png
│ └── iTunesArtwork@3x.png
│ ├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
│ ├── Info.plist
│ └── main.m
├── lib
├── data
│ ├── article.dart
│ ├── history_result.dart
│ ├── reading_data.dart
│ └── search_result.dart
├── display
│ ├── history.dart
│ ├── history_details.dart
│ ├── image_view.dart
│ ├── reading.dart
│ ├── reading_category.dart
│ ├── search.dart
│ ├── setting.dart
│ ├── today.dart
│ ├── today_details.dart
│ └── web_view.dart
├── main.dart
├── presenter
│ ├── history_presenter.dart
│ └── reading_presenter.dart
└── widget
│ ├── photo_view.dart
│ ├── photo_view_image_wrapper.dart
│ ├── photo_view_scale_type.dart
│ └── photo_view_utils.dart
├── pubspec.lock
├── pubspec.yaml
└── test
└── widget_test.dart
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .dart_tool/
3 |
4 | .packages
5 | .pub/
6 |
7 | build/
8 | .idea
9 |
10 | .flutter-plugins
11 |
--------------------------------------------------------------------------------
/.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: c7ea3ca377e909469c68f2ab878a5bc53d3cf66b
8 | channel: beta
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 cfelixmac
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 | #  Gank.IO
2 | **Another [Gank.io](https://gank.io/) Client by Flutter (for Android & iOS)**
3 |
4 | 又一个 Flutter 编写的 [Gank.io](https://gank.io/) App, 支持 Android、iOS平台
5 |
6 |
7 |
8 | ## Version
9 | 1.1
10 | ## Download Apk Link
11 | [LINK](https://raw.githubusercontent.com/cfelixw/GankIOFlutter/master/doc/gank_io_flutter_v1.0.apk)
12 | ## License
13 | MIT
14 | ## Screenshots
15 | 
16 |
17 | ## Known Issues
18 | * Large Image View can't load animated GIFs' animation.
19 | * Web View can't load web page correctly occasionally.
20 |
21 |
22 |
23 | ANY ISSUES & PRs are WELCOME!
24 |
--------------------------------------------------------------------------------
/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 | key.properties
12 |
--------------------------------------------------------------------------------
/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 |
21 | android {
22 | compileSdkVersion 27
23 |
24 | lintOptions {
25 | disable 'InvalidPackage'
26 | }
27 |
28 | defaultConfig {
29 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
30 | applicationId "com.cfelixmac.gankio"
31 | minSdkVersion 16
32 | targetSdkVersion 27
33 | versionCode 1
34 | versionName "1.0"
35 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
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 | }
51 | }
52 |
53 | flutter {
54 | source '../..'
55 | }
56 |
57 | dependencies {
58 | testImplementation 'junit:junit:4.12'
59 | androidTestImplementation 'com.android.support.test:runner:1.0.1'
60 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
61 | }
62 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
15 |
19 |
26 |
30 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/android/app/src/main/java/com/cfelixmac/gankio/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.cfelixmac.gankio;
2 |
3 | import android.os.Bundle;
4 | import io.flutter.app.FlutterActivity;
5 | import io.flutter.plugins.GeneratedPluginRegistrant;
6 |
7 | public class MainActivity extends FlutterActivity {
8 | @Override
9 | protected void onCreate(Bundle savedInstanceState) {
10 | super.onCreate(savedInstanceState);
11 | GeneratedPluginRegistrant.registerWith(this);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | jcenter()
5 | }
6 |
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:3.0.1'
9 | }
10 | }
11 |
12 | allprojects {
13 | repositories {
14 | google()
15 | jcenter()
16 | }
17 | }
18 |
19 | rootProject.buildDir = '../build'
20 | subprojects {
21 | project.buildDir = "${rootProject.buildDir}/${project.name}"
22 | }
23 | subprojects {
24 | project.evaluationDependsOn(':app')
25 | }
26 |
27 | task clean(type: Delete) {
28 | delete rootProject.buildDir
29 | }
30 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/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-4.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/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 |
--------------------------------------------------------------------------------
/doc/gank_io_flutter_v1.0.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/doc/gank_io_flutter_v1.0.apk
--------------------------------------------------------------------------------
/doc/logo_c.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/doc/logo_c.png
--------------------------------------------------------------------------------
/doc/screen1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/doc/screen1.jpg
--------------------------------------------------------------------------------
/doc/screen2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/doc/screen2.jpg
--------------------------------------------------------------------------------
/doc/screen3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/doc/screen3.jpg
--------------------------------------------------------------------------------
/doc/screen4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/doc/screen4.jpg
--------------------------------------------------------------------------------
/doc/screen5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/doc/screen5.jpg
--------------------------------------------------------------------------------
/gankio.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/gankio_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 |
--------------------------------------------------------------------------------
/images/android_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/images/android_icon.png
--------------------------------------------------------------------------------
/images/app_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/images/app_icon.png
--------------------------------------------------------------------------------
/images/doc_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/images/doc_icon.png
--------------------------------------------------------------------------------
/images/extend_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/images/extend_icon.png
--------------------------------------------------------------------------------
/images/front_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/images/front_icon.png
--------------------------------------------------------------------------------
/images/gankio.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/images/gankio.png
--------------------------------------------------------------------------------
/images/ios_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/images/ios_icon.png
--------------------------------------------------------------------------------
/images/relax_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/images/relax_icon.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 | .symlinks/
46 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 8.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | def parse_KV_file(file, separator='=')
8 | file_abs_path = File.expand_path(file)
9 | if !File.exists? file_abs_path
10 | return [];
11 | end
12 | pods_ary = []
13 | skip_line_start_symbols = ["#", "/"]
14 | File.foreach(file_abs_path) { |line|
15 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
16 | plugin = line.split(pattern=separator)
17 | if plugin.length == 2
18 | podname = plugin[0].strip()
19 | path = plugin[1].strip()
20 | podpath = File.expand_path("#{path}", file_abs_path)
21 | pods_ary.push({:name => podname, :path => podpath});
22 | else
23 | puts "Invalid plugin specification: #{line}"
24 | end
25 | }
26 | return pods_ary
27 | end
28 |
29 | target 'Runner' do
30 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
31 | # referring to absolute paths on developers' machines.
32 | system('rm -rf .symlinks')
33 | system('mkdir -p .symlinks/plugins')
34 |
35 | # Flutter Pods
36 | generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig')
37 | if generated_xcode_build_settings.empty?
38 | puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first."
39 | end
40 | generated_xcode_build_settings.map { |p|
41 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR'
42 | symlink = File.join('.symlinks', 'flutter')
43 | File.symlink(File.dirname(p[:path]), symlink)
44 | pod 'Flutter', :path => File.join(symlink, File.basename(p[:path]))
45 | end
46 | }
47 |
48 | # Plugin Pods
49 | plugin_pods = parse_KV_file('../.flutter-plugins')
50 | plugin_pods.map { |p|
51 | symlink = File.join('.symlinks', 'plugins', p[:name])
52 | File.symlink(p[:path], symlink)
53 | pod p[:name], :path => File.join(symlink, 'ios')
54 | }
55 | end
56 |
57 | post_install do |installer|
58 | installer.pods_project.targets.each do |target|
59 | target.build_configurations.each do |config|
60 | config.build_settings['ENABLE_BITCODE'] = 'NO'
61 | end
62 | end
63 | end
64 |
--------------------------------------------------------------------------------
/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Flutter (1.0.0)
3 | - flutter_webview_plugin (0.0.1):
4 | - Flutter
5 |
6 | DEPENDENCIES:
7 | - Flutter (from `.symlinks/flutter/ios`)
8 | - flutter_webview_plugin (from `.symlinks/plugins/flutter_webview_plugin/ios`)
9 |
10 | EXTERNAL SOURCES:
11 | Flutter:
12 | :path: ".symlinks/flutter/ios"
13 | flutter_webview_plugin:
14 | :path: ".symlinks/plugins/flutter_webview_plugin/ios"
15 |
16 | SPEC CHECKSUMS:
17 | Flutter: 9d0fac939486c9aba2809b7982dfdbb47a7b0296
18 | flutter_webview_plugin: 116575b48572029304775b768e9f15ebfc316274
19 |
20 | PODFILE CHECKSUM: 1e5af4103afd21ca5ead147d7b81d06f494f51a2
21 |
22 | COCOAPODS: 1.5.3
23 |
--------------------------------------------------------------------------------
/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 | 16FB647A1ADB97CA5B3A34B5 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AD64129330EB2A7CA7A504D /* libPods-Runner.a */; };
12 | 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */ = {isa = PBXBuildFile; fileRef = 2D5378251FAA1A9400D5DBA9 /* flutter_assets */; };
13 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
14 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
15 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
16 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
17 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
18 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
19 | 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
20 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
21 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
22 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
23 | /* End PBXBuildFile section */
24 |
25 | /* Begin PBXCopyFilesBuildPhase section */
26 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
27 | isa = PBXCopyFilesBuildPhase;
28 | buildActionMask = 2147483647;
29 | dstPath = "";
30 | dstSubfolderSpec = 10;
31 | files = (
32 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
33 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
34 | );
35 | name = "Embed Frameworks";
36 | runOnlyForDeploymentPostprocessing = 0;
37 | };
38 | /* End PBXCopyFilesBuildPhase section */
39 |
40 | /* Begin PBXFileReference section */
41 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
42 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
43 | 2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; };
44 | 3AD64129330EB2A7CA7A504D /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
45 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
46 | 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; };
47 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
48 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
49 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
50 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
51 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
52 | 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; };
53 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
54 | 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
55 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
56 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
57 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
58 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
59 | /* End PBXFileReference section */
60 |
61 | /* Begin PBXFrameworksBuildPhase section */
62 | 97C146EB1CF9000F007C117D /* Frameworks */ = {
63 | isa = PBXFrameworksBuildPhase;
64 | buildActionMask = 2147483647;
65 | files = (
66 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
67 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
68 | 16FB647A1ADB97CA5B3A34B5 /* libPods-Runner.a in Frameworks */,
69 | );
70 | runOnlyForDeploymentPostprocessing = 0;
71 | };
72 | /* End PBXFrameworksBuildPhase section */
73 |
74 | /* Begin PBXGroup section */
75 | 325775A8BB7F8F47FF46C699 /* Pods */ = {
76 | isa = PBXGroup;
77 | children = (
78 | );
79 | name = Pods;
80 | sourceTree = "";
81 | };
82 | 8E857B81BB18C1DC37F59F17 /* Frameworks */ = {
83 | isa = PBXGroup;
84 | children = (
85 | 3AD64129330EB2A7CA7A504D /* libPods-Runner.a */,
86 | );
87 | name = Frameworks;
88 | sourceTree = "";
89 | };
90 | 9740EEB11CF90186004384FC /* Flutter */ = {
91 | isa = PBXGroup;
92 | children = (
93 | 2D5378251FAA1A9400D5DBA9 /* flutter_assets */,
94 | 3B80C3931E831B6300D905FE /* App.framework */,
95 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
96 | 9740EEBA1CF902C7004384FC /* Flutter.framework */,
97 | 9740EEB21CF90195004384FC /* Debug.xcconfig */,
98 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
99 | 9740EEB31CF90195004384FC /* Generated.xcconfig */,
100 | );
101 | name = Flutter;
102 | sourceTree = "";
103 | };
104 | 97C146E51CF9000F007C117D = {
105 | isa = PBXGroup;
106 | children = (
107 | 9740EEB11CF90186004384FC /* Flutter */,
108 | 97C146F01CF9000F007C117D /* Runner */,
109 | 97C146EF1CF9000F007C117D /* Products */,
110 | 325775A8BB7F8F47FF46C699 /* Pods */,
111 | 8E857B81BB18C1DC37F59F17 /* Frameworks */,
112 | );
113 | sourceTree = "";
114 | };
115 | 97C146EF1CF9000F007C117D /* Products */ = {
116 | isa = PBXGroup;
117 | children = (
118 | 97C146EE1CF9000F007C117D /* Runner.app */,
119 | );
120 | name = Products;
121 | sourceTree = "";
122 | };
123 | 97C146F01CF9000F007C117D /* Runner */ = {
124 | isa = PBXGroup;
125 | children = (
126 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
127 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
128 | 97C146FA1CF9000F007C117D /* Main.storyboard */,
129 | 97C146FD1CF9000F007C117D /* Assets.xcassets */,
130 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
131 | 97C147021CF9000F007C117D /* Info.plist */,
132 | 97C146F11CF9000F007C117D /* Supporting Files */,
133 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
134 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
135 | );
136 | path = Runner;
137 | sourceTree = "";
138 | };
139 | 97C146F11CF9000F007C117D /* Supporting Files */ = {
140 | isa = PBXGroup;
141 | children = (
142 | 97C146F21CF9000F007C117D /* main.m */,
143 | );
144 | name = "Supporting Files";
145 | sourceTree = "";
146 | };
147 | /* End PBXGroup section */
148 |
149 | /* Begin PBXNativeTarget section */
150 | 97C146ED1CF9000F007C117D /* Runner */ = {
151 | isa = PBXNativeTarget;
152 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
153 | buildPhases = (
154 | 3F1C6BAB9069E325D3BDA3E8 /* [CP] Check Pods Manifest.lock */,
155 | 9740EEB61CF901F6004384FC /* Run Script */,
156 | 97C146EA1CF9000F007C117D /* Sources */,
157 | 97C146EB1CF9000F007C117D /* Frameworks */,
158 | 97C146EC1CF9000F007C117D /* Resources */,
159 | 9705A1C41CF9048500538489 /* Embed Frameworks */,
160 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
161 | 1D4E0814D767A969C61C98E0 /* [CP] Embed Pods Frameworks */,
162 | );
163 | buildRules = (
164 | );
165 | dependencies = (
166 | );
167 | name = Runner;
168 | productName = Runner;
169 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
170 | productType = "com.apple.product-type.application";
171 | };
172 | /* End PBXNativeTarget section */
173 |
174 | /* Begin PBXProject section */
175 | 97C146E61CF9000F007C117D /* Project object */ = {
176 | isa = PBXProject;
177 | attributes = {
178 | LastUpgradeCheck = 0940;
179 | ORGANIZATIONNAME = "The Chromium Authors";
180 | TargetAttributes = {
181 | 97C146ED1CF9000F007C117D = {
182 | CreatedOnToolsVersion = 7.3.1;
183 | DevelopmentTeam = 46YU5RF96W;
184 | };
185 | };
186 | };
187 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
188 | compatibilityVersion = "Xcode 3.2";
189 | developmentRegion = English;
190 | hasScannedForEncodings = 0;
191 | knownRegions = (
192 | en,
193 | Base,
194 | );
195 | mainGroup = 97C146E51CF9000F007C117D;
196 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
197 | projectDirPath = "";
198 | projectRoot = "";
199 | targets = (
200 | 97C146ED1CF9000F007C117D /* Runner */,
201 | );
202 | };
203 | /* End PBXProject section */
204 |
205 | /* Begin PBXResourcesBuildPhase section */
206 | 97C146EC1CF9000F007C117D /* Resources */ = {
207 | isa = PBXResourcesBuildPhase;
208 | buildActionMask = 2147483647;
209 | files = (
210 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
211 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
212 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
213 | 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */,
214 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
215 | );
216 | runOnlyForDeploymentPostprocessing = 0;
217 | };
218 | /* End PBXResourcesBuildPhase section */
219 |
220 | /* Begin PBXShellScriptBuildPhase section */
221 | 1D4E0814D767A969C61C98E0 /* [CP] Embed Pods Frameworks */ = {
222 | isa = PBXShellScriptBuildPhase;
223 | buildActionMask = 2147483647;
224 | files = (
225 | );
226 | inputPaths = (
227 | "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
228 | "${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework",
229 | );
230 | name = "[CP] Embed Pods Frameworks";
231 | outputPaths = (
232 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework",
233 | );
234 | runOnlyForDeploymentPostprocessing = 0;
235 | shellPath = /bin/sh;
236 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
237 | showEnvVarsInLog = 0;
238 | };
239 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
240 | isa = PBXShellScriptBuildPhase;
241 | buildActionMask = 2147483647;
242 | files = (
243 | );
244 | inputPaths = (
245 | );
246 | name = "Thin Binary";
247 | outputPaths = (
248 | );
249 | runOnlyForDeploymentPostprocessing = 0;
250 | shellPath = /bin/sh;
251 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
252 | };
253 | 3F1C6BAB9069E325D3BDA3E8 /* [CP] Check Pods Manifest.lock */ = {
254 | isa = PBXShellScriptBuildPhase;
255 | buildActionMask = 2147483647;
256 | files = (
257 | );
258 | inputPaths = (
259 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
260 | "${PODS_ROOT}/Manifest.lock",
261 | );
262 | name = "[CP] Check Pods Manifest.lock";
263 | outputPaths = (
264 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
265 | );
266 | runOnlyForDeploymentPostprocessing = 0;
267 | shellPath = /bin/sh;
268 | 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";
269 | showEnvVarsInLog = 0;
270 | };
271 | 9740EEB61CF901F6004384FC /* Run Script */ = {
272 | isa = PBXShellScriptBuildPhase;
273 | buildActionMask = 2147483647;
274 | files = (
275 | );
276 | inputPaths = (
277 | );
278 | name = "Run Script";
279 | outputPaths = (
280 | );
281 | runOnlyForDeploymentPostprocessing = 0;
282 | shellPath = /bin/sh;
283 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
284 | };
285 | /* End PBXShellScriptBuildPhase section */
286 |
287 | /* Begin PBXSourcesBuildPhase section */
288 | 97C146EA1CF9000F007C117D /* Sources */ = {
289 | isa = PBXSourcesBuildPhase;
290 | buildActionMask = 2147483647;
291 | files = (
292 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
293 | 97C146F31CF9000F007C117D /* main.m in Sources */,
294 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
295 | );
296 | runOnlyForDeploymentPostprocessing = 0;
297 | };
298 | /* End PBXSourcesBuildPhase section */
299 |
300 | /* Begin PBXVariantGroup section */
301 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
302 | isa = PBXVariantGroup;
303 | children = (
304 | 97C146FB1CF9000F007C117D /* Base */,
305 | );
306 | name = Main.storyboard;
307 | sourceTree = "";
308 | };
309 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
310 | isa = PBXVariantGroup;
311 | children = (
312 | 97C147001CF9000F007C117D /* Base */,
313 | );
314 | name = LaunchScreen.storyboard;
315 | sourceTree = "";
316 | };
317 | /* End PBXVariantGroup section */
318 |
319 | /* Begin XCBuildConfiguration section */
320 | 97C147031CF9000F007C117D /* Debug */ = {
321 | isa = XCBuildConfiguration;
322 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
323 | buildSettings = {
324 | ALWAYS_SEARCH_USER_PATHS = NO;
325 | CLANG_ANALYZER_NONNULL = YES;
326 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
327 | CLANG_CXX_LIBRARY = "libc++";
328 | CLANG_ENABLE_MODULES = YES;
329 | CLANG_ENABLE_OBJC_ARC = YES;
330 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
331 | CLANG_WARN_BOOL_CONVERSION = YES;
332 | CLANG_WARN_COMMA = YES;
333 | CLANG_WARN_CONSTANT_CONVERSION = YES;
334 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
335 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
336 | CLANG_WARN_EMPTY_BODY = YES;
337 | CLANG_WARN_ENUM_CONVERSION = YES;
338 | CLANG_WARN_INFINITE_RECURSION = YES;
339 | CLANG_WARN_INT_CONVERSION = YES;
340 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
341 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
342 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
343 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
344 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
345 | CLANG_WARN_STRICT_PROTOTYPES = YES;
346 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
347 | CLANG_WARN_UNREACHABLE_CODE = YES;
348 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
349 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
350 | COPY_PHASE_STRIP = NO;
351 | DEBUG_INFORMATION_FORMAT = dwarf;
352 | ENABLE_STRICT_OBJC_MSGSEND = YES;
353 | ENABLE_TESTABILITY = YES;
354 | GCC_C_LANGUAGE_STANDARD = gnu99;
355 | GCC_DYNAMIC_NO_PIC = NO;
356 | GCC_NO_COMMON_BLOCKS = YES;
357 | GCC_OPTIMIZATION_LEVEL = 0;
358 | GCC_PREPROCESSOR_DEFINITIONS = (
359 | "DEBUG=1",
360 | "$(inherited)",
361 | );
362 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
363 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
364 | GCC_WARN_UNDECLARED_SELECTOR = YES;
365 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
366 | GCC_WARN_UNUSED_FUNCTION = YES;
367 | GCC_WARN_UNUSED_VARIABLE = YES;
368 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
369 | MTL_ENABLE_DEBUG_INFO = YES;
370 | ONLY_ACTIVE_ARCH = YES;
371 | SDKROOT = iphoneos;
372 | TARGETED_DEVICE_FAMILY = "1,2";
373 | };
374 | name = Debug;
375 | };
376 | 97C147041CF9000F007C117D /* Release */ = {
377 | isa = XCBuildConfiguration;
378 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
379 | buildSettings = {
380 | ALWAYS_SEARCH_USER_PATHS = NO;
381 | CLANG_ANALYZER_NONNULL = YES;
382 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
383 | CLANG_CXX_LIBRARY = "libc++";
384 | CLANG_ENABLE_MODULES = YES;
385 | CLANG_ENABLE_OBJC_ARC = YES;
386 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
387 | CLANG_WARN_BOOL_CONVERSION = YES;
388 | CLANG_WARN_COMMA = YES;
389 | CLANG_WARN_CONSTANT_CONVERSION = YES;
390 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
391 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
392 | CLANG_WARN_EMPTY_BODY = YES;
393 | CLANG_WARN_ENUM_CONVERSION = YES;
394 | CLANG_WARN_INFINITE_RECURSION = YES;
395 | CLANG_WARN_INT_CONVERSION = YES;
396 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
397 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
398 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
399 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
400 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
401 | CLANG_WARN_STRICT_PROTOTYPES = YES;
402 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
403 | CLANG_WARN_UNREACHABLE_CODE = YES;
404 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
405 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
406 | COPY_PHASE_STRIP = NO;
407 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
408 | ENABLE_NS_ASSERTIONS = NO;
409 | ENABLE_STRICT_OBJC_MSGSEND = YES;
410 | GCC_C_LANGUAGE_STANDARD = gnu99;
411 | GCC_NO_COMMON_BLOCKS = YES;
412 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
413 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
414 | GCC_WARN_UNDECLARED_SELECTOR = YES;
415 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
416 | GCC_WARN_UNUSED_FUNCTION = YES;
417 | GCC_WARN_UNUSED_VARIABLE = YES;
418 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
419 | MTL_ENABLE_DEBUG_INFO = NO;
420 | SDKROOT = iphoneos;
421 | TARGETED_DEVICE_FAMILY = "1,2";
422 | VALIDATE_PRODUCT = YES;
423 | };
424 | name = Release;
425 | };
426 | 97C147061CF9000F007C117D /* Debug */ = {
427 | isa = XCBuildConfiguration;
428 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
429 | buildSettings = {
430 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
431 | CURRENT_PROJECT_VERSION = 1;
432 | DEVELOPMENT_TEAM = 46YU5RF96W;
433 | ENABLE_BITCODE = NO;
434 | FRAMEWORK_SEARCH_PATHS = (
435 | "$(inherited)",
436 | "$(PROJECT_DIR)/Flutter",
437 | );
438 | INFOPLIST_FILE = Runner/Info.plist;
439 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
440 | LIBRARY_SEARCH_PATHS = (
441 | "$(inherited)",
442 | "$(PROJECT_DIR)/Flutter",
443 | );
444 | PRODUCT_BUNDLE_IDENTIFIER = com.cfelixmac.gankio;
445 | PRODUCT_NAME = "$(TARGET_NAME)";
446 | VERSIONING_SYSTEM = "apple-generic";
447 | };
448 | name = Debug;
449 | };
450 | 97C147071CF9000F007C117D /* Release */ = {
451 | isa = XCBuildConfiguration;
452 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
453 | buildSettings = {
454 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
455 | CURRENT_PROJECT_VERSION = 1;
456 | DEVELOPMENT_TEAM = 46YU5RF96W;
457 | ENABLE_BITCODE = NO;
458 | FRAMEWORK_SEARCH_PATHS = (
459 | "$(inherited)",
460 | "$(PROJECT_DIR)/Flutter",
461 | );
462 | INFOPLIST_FILE = Runner/Info.plist;
463 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
464 | LIBRARY_SEARCH_PATHS = (
465 | "$(inherited)",
466 | "$(PROJECT_DIR)/Flutter",
467 | );
468 | PRODUCT_BUNDLE_IDENTIFIER = com.cfelixmac.gankio;
469 | PRODUCT_NAME = "$(TARGET_NAME)";
470 | VERSIONING_SYSTEM = "apple-generic";
471 | };
472 | name = Release;
473 | };
474 | /* End XCBuildConfiguration section */
475 |
476 | /* Begin XCConfigurationList section */
477 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
478 | isa = XCConfigurationList;
479 | buildConfigurations = (
480 | 97C147031CF9000F007C117D /* Debug */,
481 | 97C147041CF9000F007C117D /* Release */,
482 | );
483 | defaultConfigurationIsVisible = 0;
484 | defaultConfigurationName = Release;
485 | };
486 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
487 | isa = XCConfigurationList;
488 | buildConfigurations = (
489 | 97C147061CF9000F007C117D /* Debug */,
490 | 97C147071CF9000F007C117D /* Release */,
491 | );
492 | defaultConfigurationIsVisible = 0;
493 | defaultConfigurationName = Release;
494 | };
495 | /* End XCConfigurationList section */
496 | };
497 | rootObject = 97C146E61CF9000F007C117D /* Project object */;
498 | }
499 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | @interface AppDelegate : FlutterAppDelegate
5 |
6 | @end
7 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.m:
--------------------------------------------------------------------------------
1 | #include "AppDelegate.h"
2 | #include "GeneratedPluginRegistrant.h"
3 |
4 | @implementation AppDelegate
5 |
6 | - (BOOL)application:(UIApplication *)application
7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
8 | [GeneratedPluginRegistrant registerWithRegistry:self];
9 | // Override point for customization after application launch.
10 | return [super application:application didFinishLaunchingWithOptions:launchOptions];
11 | }
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images":[
3 | {
4 | "idiom":"iphone",
5 | "size":"20x20",
6 | "scale":"2x",
7 | "filename":"Icon-App-20x20@2x.png"
8 | },
9 | {
10 | "idiom":"iphone",
11 | "size":"20x20",
12 | "scale":"3x",
13 | "filename":"Icon-App-20x20@3x.png"
14 | },
15 | {
16 | "idiom":"iphone",
17 | "size":"29x29",
18 | "scale":"1x",
19 | "filename":"Icon-App-29x29@1x.png"
20 | },
21 | {
22 | "idiom":"iphone",
23 | "size":"29x29",
24 | "scale":"2x",
25 | "filename":"Icon-App-29x29@2x.png"
26 | },
27 | {
28 | "idiom":"iphone",
29 | "size":"29x29",
30 | "scale":"3x",
31 | "filename":"Icon-App-29x29@3x.png"
32 | },
33 | {
34 | "idiom":"iphone",
35 | "size":"40x40",
36 | "scale":"1x",
37 | "filename":"Icon-App-40x40@1x.png"
38 | },
39 | {
40 | "idiom":"iphone",
41 | "size":"40x40",
42 | "scale":"2x",
43 | "filename":"Icon-App-40x40@2x.png"
44 | },
45 | {
46 | "idiom":"iphone",
47 | "size":"40x40",
48 | "scale":"3x",
49 | "filename":"Icon-App-40x40@3x.png"
50 | },
51 | {
52 | "idiom":"iphone",
53 | "size":"57x57",
54 | "scale":"1x",
55 | "filename":"Icon-App-57x57@1x.png"
56 | },
57 | {
58 | "idiom":"iphone",
59 | "size":"57x57",
60 | "scale":"2x",
61 | "filename":"Icon-App-57x57@2x.png"
62 | },
63 | {
64 | "idiom":"iphone",
65 | "size":"60x60",
66 | "scale":"1x",
67 | "filename":"Icon-App-60x60@1x.png"
68 | },
69 | {
70 | "idiom":"iphone",
71 | "size":"60x60",
72 | "scale":"2x",
73 | "filename":"Icon-App-60x60@2x.png"
74 | },
75 | {
76 | "idiom":"iphone",
77 | "size":"60x60",
78 | "scale":"3x",
79 | "filename":"Icon-App-60x60@3x.png"
80 | },
81 | {
82 | "idiom":"iphone",
83 | "size":"76x76",
84 | "scale":"1x",
85 | "filename":"Icon-App-76x76@1x.png"
86 | },
87 | {
88 | "idiom":"ipad",
89 | "size":"20x20",
90 | "scale":"1x",
91 | "filename":"Icon-App-20x20@1x.png"
92 | },
93 | {
94 | "idiom":"ipad",
95 | "size":"20x20",
96 | "scale":"2x",
97 | "filename":"Icon-App-20x20@2x.png"
98 | },
99 | {
100 | "idiom":"ipad",
101 | "size":"29x29",
102 | "scale":"1x",
103 | "filename":"Icon-App-29x29@1x.png"
104 | },
105 | {
106 | "idiom":"ipad",
107 | "size":"29x29",
108 | "scale":"2x",
109 | "filename":"Icon-App-29x29@2x.png"
110 | },
111 | {
112 | "idiom":"ipad",
113 | "size":"40x40",
114 | "scale":"1x",
115 | "filename":"Icon-App-40x40@1x.png"
116 | },
117 | {
118 | "idiom":"ipad",
119 | "size":"40x40",
120 | "scale":"2x",
121 | "filename":"Icon-App-40x40@2x.png"
122 | },
123 | {
124 | "size" : "50x50",
125 | "idiom" : "ipad",
126 | "filename" : "Icon-Small-50x50@1x.png",
127 | "scale" : "1x"
128 | },
129 | {
130 | "size" : "50x50",
131 | "idiom" : "ipad",
132 | "filename" : "Icon-Small-50x50@2x.png",
133 | "scale" : "2x"
134 | },
135 | {
136 | "idiom":"ipad",
137 | "size":"72x72",
138 | "scale":"1x",
139 | "filename":"Icon-App-72x72@1x.png"
140 | },
141 | {
142 | "idiom":"ipad",
143 | "size":"72x72",
144 | "scale":"2x",
145 | "filename":"Icon-App-72x72@2x.png"
146 | },
147 | {
148 | "idiom":"ipad",
149 | "size":"76x76",
150 | "scale":"1x",
151 | "filename":"Icon-App-76x76@1x.png"
152 | },
153 | {
154 | "idiom":"ipad",
155 | "size":"76x76",
156 | "scale":"2x",
157 | "filename":"Icon-App-76x76@2x.png"
158 | },
159 | {
160 | "idiom":"ipad",
161 | "size":"76x76",
162 | "scale":"3x",
163 | "filename":"Icon-App-76x76@3x.png"
164 | },
165 | {
166 | "idiom":"ipad",
167 | "size":"83.5x83.5",
168 | "scale":"2x",
169 | "filename":"Icon-App-83.5x83.5@2x.png"
170 | },
171 | {
172 | "size" : "1024x1024",
173 | "idiom" : "ios-marketing",
174 | "filename" : "ItunesArtwork@2x.png",
175 | "scale" : "1x"
176 | }
177 | ],
178 | "info":{
179 | "version":1,
180 | "author":"makeappicon"
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/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/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/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/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/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/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/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/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/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/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/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/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/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/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/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/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/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/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/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/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-50x50@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-50x50@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-50x50@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-50x50@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/ios/Runner/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/iTunesArtwork@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/ios/Runner/Assets.xcassets/iTunesArtwork@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/iTunesArtwork@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/ios/Runner/Assets.xcassets/iTunesArtwork@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/iTunesArtwork@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cfelixmac-code/GankIOFlutter/0ba24f2ab177376f9e7c0c48b0d23ca2bce9ca6c/ios/Runner/Assets.xcassets/iTunesArtwork@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | Gank.IO
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | gankio
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1
25 | LSRequiresIPhoneOS
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UIMainStoryboardFile
30 | Main
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 | UIViewControllerBasedStatusBarAppearance
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/ios/Runner/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/data/article.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | class Article {
4 | Article({this.category, this.error, this.result});
5 |
6 | List category;
7 | bool error;
8 | Map> result;
9 |
10 | factory Article.fromJson(String bodyString) {
11 | Map body = json.decode(bodyString);
12 | Map> results = Map();
13 | Map rawResults = body['results'];
14 |
15 | results['App'] = parseSectors("App", rawResults);
16 | results['iOS'] = parseSectors("iOS", rawResults);
17 | results['Android'] = parseSectors("Android", rawResults);
18 | results['休息视频'] = parseSectors("休息视频", rawResults);
19 | results['前端'] = parseSectors("前端", rawResults);
20 | results['拓展资源'] = parseSectors("拓展资源", rawResults);
21 | results['瞎推荐'] = parseSectors("瞎推荐", rawResults);
22 | results['福利'] = parseSectors("福利", rawResults);
23 |
24 | return Article(
25 | category: new List.from(body['category']),
26 | error: body['error'],
27 | result: results,
28 | );
29 | }
30 | }
31 |
32 | List parseSectors(String key, Map rawResults) {
33 | List raw = rawResults[key];
34 | List result = List();
35 | if (raw != null && raw.length != 0) {
36 | for (var article in raw) {
37 | result.add(ArticleSector.fromJson(article));
38 | }
39 | }
40 | return result;
41 | }
42 |
43 | class ArticleSector {
44 | ArticleSector({this.createdAt, this.desc, this.images, this.publishedAt, this.url, this.who});
45 |
46 | String createdAt;
47 | String desc;
48 | List images;
49 | String publishedAt;
50 | String url;
51 | String who;
52 |
53 | factory ArticleSector.fromJson(dynamic jsonString) {
54 | Map raw = jsonString;
55 | return new ArticleSector(
56 | createdAt: raw['createdAt'],
57 | desc: raw['desc'],
58 | images: (raw.containsKey('images') && raw['images'] != null) ? new List.from(raw['images']) : null,
59 | publishedAt: raw['publishedAt'],
60 | url: raw['url'],
61 | who: raw['who'],
62 | );
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/lib/data/history_result.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'package:html/dom.dart';
3 | import 'package:html/parser.dart' show parse;
4 |
5 | class HistoryResult {
6 | HistoryResult({this.error, this.results});
7 |
8 | bool error;
9 |
10 | List results;
11 |
12 | factory HistoryResult.fromJson(String raw) {
13 | var decoded = json.decode(raw);
14 | var results = List();
15 | for (var rawItem in decoded['results']) {
16 | results.add(HistoryResultItem.fromJson(rawItem));
17 | }
18 | return HistoryResult(error: decoded['error'], results: results);
19 | }
20 | }
21 |
22 | class HistoryResultItem {
23 | HistoryResultItem({this.id, this.sections, this.publishedAt, this.title});
24 |
25 | String id;
26 | String publishedAt;
27 | String title;
28 |
29 | List sections;
30 |
31 | factory HistoryResultItem.fromJson(dynamic raw) {
32 | String id = raw['_id'];
33 | var resolvedSections = List();
34 |
35 | Document document = parse(raw['content']);
36 |
37 | HistoryArticleSection section;
38 | bool isSectionValid = false;
39 | for (Node node in document.body.nodes) {
40 | try {
41 | if (node.toString() == '') {
42 | section = new HistoryArticleSection();
43 | isSectionValid = true;
44 | section.name = node.text;
45 | }
46 |
47 | if (section != null && node.toString() == '') {
48 | section.articles = List();
49 | for (var liItem in node.nodes) {
50 | if (liItem.toString() == '') {
51 | String value = "";
52 | HistoryArticle article = HistoryArticle();
53 |
54 | for (var liItemChild in liItem.nodes) {
55 | if (liItemChild.text != null && liItemChild.text.trim().length != 0) {
56 | if (article.url == null &&
57 | liItemChild.attributes['href'] != null &&
58 | liItemChild.attributes['href'].length != 0) {
59 | article.url = liItemChild.attributes['href'].trim();
60 | }
61 | String currentText = liItemChild.text.trim();
62 | value += currentText;
63 | }
64 | }
65 | article.title = value;
66 |
67 | section.articles.add(article);
68 | }
69 | }
70 | }
71 | if (section != null && isSectionValid) {
72 | resolvedSections.add(section);
73 | isSectionValid = false;
74 | }
75 | } catch (e) {}
76 | }
77 | return HistoryResultItem(id: id, sections: resolvedSections, publishedAt: raw['publishedAt'], title: raw['title']);
78 | }
79 | }
80 |
81 | class HistoryArticleSection {
82 | HistoryArticleSection({this.name, this.articles});
83 |
84 | String name;
85 | List articles;
86 | }
87 |
88 | class HistoryArticle {
89 | HistoryArticle({this.title, this.url});
90 |
91 | String title;
92 | String url;
93 | }
94 |
--------------------------------------------------------------------------------
/lib/data/reading_data.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | class ReadingCategoryResult {
4 | ReadingCategoryResult({this.error, this.results});
5 |
6 | bool error;
7 |
8 | List results;
9 |
10 | factory ReadingCategoryResult.fromJson(String raw) {
11 | var decoded = json.decode(raw);
12 | var results = List();
13 | for (var rawCategory in decoded['results']) {
14 | results.add(ReadingCategory.fromJson(rawCategory));
15 | }
16 | return ReadingCategoryResult(error: decoded['error'], results: results);
17 | }
18 | }
19 |
20 | class ReadingCategory {
21 | ReadingCategory({this.id, this.name, this.enName});
22 |
23 | String id;
24 | String name;
25 | String enName;
26 |
27 | bool selected = false;
28 |
29 | factory ReadingCategory.fromJson(dynamic raw) {
30 | Map rawCategory = raw;
31 | return ReadingCategory(id: rawCategory['_id'], name: rawCategory['name'], enName: rawCategory['en_name']);
32 | }
33 | }
34 |
35 | class ReadingSiteResult {
36 | ReadingSiteResult({this.error, this.results});
37 |
38 | bool error;
39 | List results;
40 |
41 | factory ReadingSiteResult.fromJson(String raw) {
42 | var decoded = json.decode(raw);
43 | var results = List();
44 | for (var rawSite in decoded['results']) {
45 | results.add(ReadingSite.fromJson(rawSite));
46 | }
47 | return ReadingSiteResult(error: decoded['error'], results: results);
48 | }
49 | }
50 |
51 | class ReadingSite {
52 | ReadingSite({this.id, this.title, this.icon});
53 |
54 | String id;
55 | String icon;
56 | String title;
57 |
58 | factory ReadingSite.fromJson(dynamic raw) {
59 | Map rawSite = raw;
60 | return ReadingSite(id: rawSite['id'], icon: rawSite['icon'], title: rawSite['title']);
61 | }
62 | }
63 |
64 | class ReadingArticleResult {
65 | ReadingArticleResult({this.error, this.results});
66 |
67 | bool error;
68 | List results;
69 |
70 | factory ReadingArticleResult.fromJson(String raw) {
71 | var decoded = json.decode(raw);
72 | var results = List();
73 | for (var rawArticle in decoded['results']) {
74 | results.add(ReadingArticle.fromJson(rawArticle));
75 | }
76 | return ReadingArticleResult(error: decoded['error'], results: results);
77 | }
78 | }
79 |
80 | class ReadingArticle {
81 | ReadingArticle({this.url, this.title, this.published, this.author});
82 |
83 | String url;
84 |
85 | String title;
86 |
87 | String published;
88 |
89 | String author;
90 |
91 | factory ReadingArticle.fromJson(dynamic raw) {
92 | String publishTime = raw['published_at'];
93 | publishTime = (publishTime != null && publishTime.length > 10) ? publishTime.substring(0, 10) : publishTime;
94 |
95 | return ReadingArticle(url: raw['url'], title: raw['title'], published: publishTime);
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/lib/data/search_result.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | class SearchResult {
4 | SearchResult({this.count, this.error, this.results});
5 |
6 | int count;
7 | bool error;
8 | List results;
9 |
10 | factory SearchResult.fromJson(String raw) {
11 | var decoded = json.decode(raw);
12 | List resolvedResults = List();
13 | for (var rawItem in decoded['results']) {
14 | resolvedResults.add(SearchResultItem.fromJson(rawItem));
15 | }
16 | return SearchResult(count: decoded['count'], error: decoded['error'], results: resolvedResults);
17 | }
18 | }
19 |
20 | class SearchResultItem {
21 | SearchResultItem({this.desc, this.id, this.publishedAt, this.type, this.url, this.who});
22 |
23 | String desc;
24 | String id;
25 | String publishedAt;
26 | String type;
27 | String url;
28 | String who;
29 |
30 | factory SearchResultItem.fromJson(dynamic raw) {
31 | Map rawItem = raw;
32 | return SearchResultItem(
33 | desc: rawItem['desc'],
34 | id: rawItem['ganhuo_id'],
35 | publishedAt: rawItem['publishedAt'],
36 | type: rawItem['type'],
37 | url: rawItem['url'],
38 | who: rawItem['who']);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lib/display/history.dart:
--------------------------------------------------------------------------------
1 | import 'package:event_bus/event_bus.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:gankio/data/history_result.dart';
4 | import 'package:gankio/display/history_details.dart';
5 | import 'package:gankio/presenter/history_presenter.dart';
6 |
7 | var _bus = EventBus();
8 |
9 | class HistoryPage extends StatefulWidget {
10 | @override
11 | _HistoryPageState createState() => new _HistoryPageState();
12 | }
13 |
14 | class _HistoryPageState extends State implements HistoryView {
15 | HistoryPresenter _presenter;
16 |
17 | var _data = List();
18 | var _pageIndex = 1;
19 | var _requesting = false;
20 | var _end = false;
21 |
22 | _HistoryPageState() {
23 | _presenter = HistoryPresenter(this);
24 | }
25 |
26 | _updateResults(HistoryResult result) {
27 | if (this.mounted) {
28 | setState(() {
29 | _data.addAll(result.results);
30 | });
31 | }
32 | }
33 |
34 | @override
35 | void initState() {
36 | super.initState();
37 | _presenter.fetchHistory(page: _pageIndex);
38 | _bus.on().listen((event) {
39 | _requesting = false;
40 | _updateResults(event.result);
41 | });
42 | }
43 |
44 | @override
45 | void dispose() {
46 | super.dispose();
47 | }
48 |
49 | var _scrollController = ScrollController();
50 |
51 | @override
52 | Widget build(BuildContext context) {
53 | _scrollController.addListener(() {
54 | if (!_requesting && _scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
55 | _pageIndex++;
56 | _requesting = true;
57 | _presenter.fetchHistory(page: _pageIndex);
58 | }
59 | });
60 |
61 | if (_data.length != 0) {
62 | return Center(
63 | child: ListView.builder(
64 | controller: _scrollController,
65 | padding: EdgeInsets.all(3.0),
66 | itemCount: _end ? _data.length : _data.length + 1,
67 | itemBuilder: (context, index) {
68 | if (index < _data.length) {
69 | return HistoryListItem(_data[index]);
70 | } else {
71 | return Container(
72 | height: 60.0,
73 | child: Center(
74 | child: Container(
75 | width: 24.0,
76 | height: 24.0,
77 | child: CircularProgressIndicator(
78 | strokeWidth: 2.0,
79 | ),
80 | ),
81 | ),
82 | );
83 | }
84 | },
85 | ),
86 | );
87 | } else {
88 | return Center(
89 | child: Container(
90 | width: MediaQuery.of(context).size.width * 0.08,
91 | height: MediaQuery.of(context).size.width * 0.08,
92 | child: CircularProgressIndicator(),
93 | ),
94 | );
95 | }
96 | }
97 |
98 | @override
99 | fetchReceived(HistoryResult result) {
100 | if (result.results != null && result.results.length != 0) {
101 | _bus.fire(HistoryResultEvent(result: result));
102 | } else {
103 | _end = true;
104 | }
105 | }
106 | }
107 |
108 | class HistoryListItem extends StatelessWidget {
109 | HistoryListItem(this.item);
110 |
111 | final HistoryResultItem item;
112 |
113 | @override
114 | Widget build(BuildContext context) {
115 | var widgets = List();
116 | widgets.add(Text(
117 | '${item.title}',
118 | maxLines: 2,
119 | overflow: TextOverflow.ellipsis,
120 | style: TextStyle(fontSize: 17.0, color: Colors.black87, fontWeight: FontWeight.w600),
121 | ));
122 | widgets.add(Text(
123 | ' ${item.publishedAt.length > 10 ? item.publishedAt.substring(0, 10) : item.publishedAt}',
124 | style: TextStyle(fontSize: 10.0, color: Colors.blueAccent, fontWeight: FontWeight.w500),
125 | ));
126 | String content = '';
127 | for (HistoryArticleSection section in item.sections) {
128 | if (section.articles != null) {
129 | for (HistoryArticle article in section.articles) {
130 | content += article.title.substring(0, article.title.lastIndexOf('(')).trim();
131 | content += ' ';
132 | }
133 | }
134 | }
135 | widgets.add(Container(
136 | child: Text(
137 | content,
138 | maxLines: 8,
139 | overflow: TextOverflow.ellipsis,
140 | style: TextStyle(
141 | fontSize: 13.3,
142 | color: Colors.black54,
143 | ),
144 | ),
145 | ));
146 | widgets.add(Container(
147 | margin: EdgeInsets.only(top: 8.0),
148 | child: MaterialButton(
149 | onPressed: () {
150 | showDialog(
151 | context: context,
152 | builder: (context) {
153 | return HistoryDetailDialog(item);
154 | });
155 | },
156 | color: Colors.blueAccent,
157 | child: Text(
158 | '详情',
159 | style: TextStyle(fontSize: 13.0, color: Colors.white, letterSpacing: 1.0),
160 | ),
161 | ),
162 | alignment: Alignment.topRight,
163 | ));
164 |
165 | return Card(
166 | child: Container(
167 | padding: EdgeInsets.all(10.0),
168 | child: Column(
169 | crossAxisAlignment: CrossAxisAlignment.start,
170 | children: widgets,
171 | ),
172 | ),
173 | );
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/lib/display/history_details.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:gankio/data/history_result.dart';
3 | import 'package:gankio/display/web_view.dart';
4 | import 'package:gankio/main.dart';
5 |
6 | class HistoryDetailDialog extends StatefulWidget {
7 | HistoryDetailDialog(this.article);
8 |
9 | final HistoryResultItem article;
10 |
11 | @override
12 | _HistoryDetailDialogState createState() => _HistoryDetailDialogState(article);
13 | }
14 |
15 | class _HistoryDetailDialogState extends State {
16 | _HistoryDetailDialogState(this._item);
17 |
18 | HistoryResultItem _item;
19 |
20 | @override
21 | Widget build(BuildContext context) {
22 | var contentWidgets = List();
23 | for (HistoryArticleSection section in _item.sections) {
24 | if (section.articles != null && section.articles.length != 0) {
25 | for (HistoryArticle article in section.articles) {
26 | contentWidgets.add(_buildItem(section.name, article));
27 | }
28 | }
29 | }
30 |
31 | return SimpleDialog(
32 | titlePadding: EdgeInsets.only(top: 15.0, left: 12.0, right: 12.0),
33 | contentPadding: EdgeInsets.only(bottom: 12.0),
34 | title: Column(
35 | crossAxisAlignment: CrossAxisAlignment.start,
36 | children: [
37 | Text(
38 | '${(_item.publishedAt != null && _item.publishedAt.length > 10) ? _item.publishedAt.substring(0, 10) : _item
39 | .publishedAt}',
40 | maxLines: 1,
41 | overflow: TextOverflow.ellipsis,
42 | style: TextStyle(
43 | fontSize: 13.0,
44 | color: Colors.orangeAccent,
45 | fontWeight: FontWeight.w600,
46 | ),
47 | ),
48 | Text(
49 | '${_item.title}',
50 | maxLines: 2,
51 | overflow: TextOverflow.ellipsis,
52 | style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.w600),
53 | ),
54 | ],
55 | ),
56 | children: contentWidgets,
57 | );
58 | }
59 |
60 | Widget _buildItem(String sectionName, HistoryArticle article) {
61 | return InkWell(
62 | onTap: () {
63 | Navigator.push(
64 | context,
65 | GankNavigator(
66 | builder: (context) => WebViewer(
67 | url: article.url,
68 | title: article.title,
69 | )));
70 | },
71 | child: Container(
72 | padding: EdgeInsets.only(left: 12.0, right: 12.0),
73 | height: 52.0,
74 | child: Column(
75 | crossAxisAlignment: CrossAxisAlignment.start,
76 | children: [
77 | _buildItemTextWidget(sectionName, article),
78 | ],
79 | ),
80 | ),
81 | );
82 | }
83 |
84 | Expanded _buildItemTextWidget(String sectionName, HistoryArticle article) {
85 | return Expanded(
86 | child: Row(
87 | children: [
88 | Expanded(
89 | child: RichText(
90 | maxLines: 2,
91 | overflow: TextOverflow.ellipsis,
92 | text: TextSpan(
93 | children: [
94 | TextSpan(
95 | text: '[$sectionName] ',
96 | style: TextStyle(
97 | fontSize: 11.0,
98 | color: Colors.blueAccent,
99 | fontWeight: FontWeight.w500,
100 | ),
101 | ),
102 | TextSpan(
103 | text: '${article.title}',
104 | style: TextStyle(
105 | fontSize: 13.0,
106 | color: Color(0xff464646),
107 | ),
108 | ),
109 | ],
110 | ),
111 | ),
112 | ),
113 | ],
114 | ),
115 | );
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/lib/display/image_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:gankio/widget/photo_view.dart';
3 |
4 | class ImageViewPage extends StatelessWidget {
5 | ImageViewPage(this.imageUrl);
6 |
7 | final String imageUrl;
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return new Scaffold(
12 | appBar: new AppBar(
13 | title: Row(
14 | children: [
15 | Expanded(
16 | child: new Text('查看大图'),
17 | ),
18 | ],
19 | ),
20 | ),
21 | body: new Container(
22 | child: new PhotoView(
23 | imageProvider: new NetworkImage(imageUrl),
24 | ),
25 | ),
26 | );
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/lib/display/reading.dart:
--------------------------------------------------------------------------------
1 | import 'package:event_bus/event_bus.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:gankio/data/reading_data.dart';
4 | import 'package:gankio/display/reading_category.dart';
5 | import 'package:gankio/display/web_view.dart';
6 | import 'package:gankio/main.dart';
7 | import 'package:gankio/presenter/reading_presenter.dart';
8 |
9 | var _bus = EventBus();
10 |
11 | class ReadingPage extends StatefulWidget {
12 | @override
13 | _ReadingPageState createState() => _ReadingPageState();
14 | }
15 |
16 | class _ReadingPageState extends State implements ReadingView {
17 | var _currentSiteName = '小众软件';
18 | var _currentSiteId = 'appinn';
19 |
20 | var _currentArticles = List();
21 | var _isListEnd = false;
22 |
23 | ReadingPresenter _presenter;
24 |
25 | var _scrollController = ScrollController();
26 |
27 | @override
28 | void initState() {
29 | super.initState();
30 | _presenter = ReadingPresenter(this);
31 | _presenter.fetchReadingArticles(_currentSiteId, true);
32 | _bus.on().listen((event) {
33 | if (this.mounted) {
34 | setState(() {
35 | if (event.clear) {
36 | _currentArticles.clear();
37 | }
38 | _currentArticles.addAll(event.results);
39 | });
40 | }
41 | });
42 | _bus.on().listen((event) {
43 | if (this.mounted) {
44 | setState(() {
45 | _isListEnd = true;
46 | });
47 | }
48 | });
49 |
50 | _scrollController.addListener(() {
51 | if (!_isListEnd && _scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
52 | _presenter.fetchReadingArticles(_currentSiteId, false);
53 | }
54 | });
55 | }
56 |
57 | @override
58 | Widget build(BuildContext context) {
59 | var _articleWidgets = List();
60 | for (var article in _currentArticles) {
61 | _articleWidgets.add(_buildArticleItem(article));
62 | }
63 | if (!_isListEnd) {
64 | _articleWidgets.add(_buildArticleLoading());
65 | }
66 |
67 | return Container(
68 | child: Column(
69 | children: [
70 | Container(
71 | color: Color(0xcc538195),
72 | height: 60.0,
73 | padding: EdgeInsets.only(left: 15.0, right: 15.0),
74 | child: Row(
75 | children: [
76 | Expanded(
77 | child: Text(
78 | _currentSiteName,
79 | style: TextStyle(color: Colors.white),
80 | ),
81 | ),
82 | Container(
83 | child: IconButton(
84 | onPressed: () async {
85 | Map results = await Navigator.push(context, MaterialPageRoute(builder: (context) => ReadingCategoryPage()));
86 | if (results != null) {
87 | setState(() {
88 | if (this.mounted) {
89 | _isListEnd = false;
90 | _currentArticles.clear();
91 | _currentSiteName = results['siteName'];
92 | _currentSiteId = results['siteId'];
93 | _scrollController.jumpTo(0.0);
94 | _presenter.handleSiteChange();
95 | _presenter.fetchReadingArticles(_currentSiteId, true);
96 | }
97 | });
98 | }
99 | },
100 | tooltip: '修改分类',
101 | icon: Icon(Icons.edit),
102 | color: Colors.white,
103 | ),
104 | )
105 | ],
106 | ),
107 | ),
108 | Expanded(
109 | child: ListView(
110 | controller: _scrollController,
111 | children: _articleWidgets,
112 | ),
113 | ),
114 | ],
115 | ),
116 | );
117 | }
118 |
119 | Widget _buildArticleLoading() {
120 | return Container(
121 | height: 60.0,
122 | child: Center(
123 | child: Container(
124 | width: 24.0,
125 | height: 24.0,
126 | child: CircularProgressIndicator(
127 | strokeWidth: 2.0,
128 | ),
129 | ),
130 | ),
131 | );
132 | }
133 |
134 | Widget _buildArticleItem(ReadingArticle article) {
135 | return Container(
136 | height: 60.0,
137 | padding: EdgeInsets.only(left: 12.0, right: 12.0),
138 | child: Column(
139 | crossAxisAlignment: CrossAxisAlignment.start,
140 | children: [
141 | Expanded(
142 | child: Material(
143 | child: InkWell(
144 | onTap: () {
145 | Navigator.push(
146 | context,
147 | GankNavigator(
148 | builder: (context) => WebViewer(
149 | url: article.url,
150 | title: article.title,
151 | )));
152 | },
153 | child: Row(
154 | children: [
155 | Expanded(
156 | child: RichText(
157 | maxLines: 2,
158 | overflow: TextOverflow.ellipsis,
159 | text: TextSpan(
160 | children: [
161 | TextSpan(text: '${article.published}\n', style: TextStyle(fontSize: 10.0, color: Colors.blueAccent)),
162 | TextSpan(text: article.title, style: TextStyle(fontSize: 13.0, color: Colors.black87)),
163 | ],
164 | ),
165 | ),
166 | ),
167 | ],
168 | ),
169 | ),
170 | color: Colors.transparent,
171 | ),
172 | ),
173 | Container(
174 | height: 1.0,
175 | color: Colors.black12,
176 | )
177 | ],
178 | ),
179 | );
180 | }
181 |
182 | @override
183 | fetchReadingArticlesReceived(ReadingArticleResult result, bool clear) {
184 | _bus.fire(ReadingArticlesEvent(results: result.results, clear: clear));
185 | }
186 |
187 | @override
188 | fetchReadingArticlesEnd() {
189 | _bus.fire(ReadingArticlesEndEvent());
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/lib/display/reading_category.dart:
--------------------------------------------------------------------------------
1 | import 'package:event_bus/event_bus.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:gankio/data/reading_data.dart';
4 | import 'package:gankio/presenter/reading_presenter.dart';
5 |
6 | var _bus = EventBus();
7 |
8 | class ReadingCategoryPage extends StatefulWidget {
9 | @override
10 | _ReadingCategoryState createState() => _ReadingCategoryState();
11 | }
12 |
13 | class _ReadingCategoryState extends State implements ReadingCategoryView {
14 | var _categories = List();
15 | var _sites = List();
16 |
17 | ReadingCategoryPresenter _presenter;
18 |
19 | _ReadingCategoryState() {
20 | _presenter = ReadingCategoryPresenter(this);
21 | }
22 |
23 | @override
24 | void initState() {
25 | super.initState();
26 | _presenter.fetchCategory();
27 | _bus.on().listen((event) {
28 | if (this.mounted) {
29 | setState(() {
30 | _categories.clear();
31 | _categories.addAll(event.categories);
32 | changeCurrentSelectedCategory(event.categories[0]);
33 | _presenter.fetchSites(event.categories[0].enName);
34 | });
35 | }
36 | });
37 | _bus.on().listen((event) {
38 | if (this.mounted) {
39 | setState(() {
40 | _sites.clear();
41 | _sites.addAll(event.sites);
42 | });
43 | }
44 | });
45 | _bus.on().listen((event) {
46 | if (this.mounted) {
47 | setState(() {
48 | if (changeCurrentSelectedCategory(event.category)) {
49 | _presenter.fetchSites(event.category.enName);
50 | }
51 | });
52 | }
53 | });
54 | }
55 |
56 | bool changeCurrentSelectedCategory(ReadingCategory category) {
57 | if (_categories != null) {
58 | bool result = false;
59 | for (var c in _categories) {
60 | if (c.enName == category.enName) {
61 | result = true;
62 | c.selected = true;
63 | } else {
64 | c.selected = false;
65 | }
66 | }
67 | return result;
68 | }
69 | return false;
70 | }
71 |
72 | @override
73 | Widget build(BuildContext context) {
74 | var _categoryWidgets = List();
75 | for (var category in _categories) {
76 | _categoryWidgets.add(_buildCategoryItem(category));
77 | }
78 |
79 | var _siteWidgets = List();
80 | for (var site in _sites) {
81 | _siteWidgets.add(_buildSiteItem(site));
82 | }
83 |
84 | return Scaffold(
85 | appBar: new AppBar(
86 | title: Row(
87 | children: [
88 | Expanded(
89 | child: new Text('选择闲读分类'),
90 | ),
91 | ],
92 | ),
93 | ),
94 | body: Row(
95 | children: [
96 | Container(
97 | color: Color(0x99D9DAE4),
98 | padding: EdgeInsets.only(top: 10.0),
99 | width: MediaQuery.of(context).size.width * 0.3,
100 | child: ListView(
101 | children: _categoryWidgets,
102 | ),
103 | ),
104 | Container(
105 | color: Colors.white,
106 | padding: EdgeInsets.only(top: 10.0),
107 | width: MediaQuery.of(context).size.width * 0.7,
108 | child: ListView(children: _siteWidgets),
109 | ),
110 | ],
111 | ),
112 | );
113 | }
114 |
115 | Widget _buildSiteItem(ReadingSite site) {
116 | return Material(
117 | child: InkWell(
118 | onTap: () {
119 | Navigator.pop(context, {'siteId': site.id, 'siteName': site.title});
120 | },
121 | child: Container(
122 | padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 10.0),
123 | margin: EdgeInsets.only(bottom: 18.0),
124 | child: Column(
125 | children: [
126 | Row(
127 | children: [
128 | Container(
129 | height: 40.0,
130 | width: 40.0,
131 | margin: EdgeInsets.only(right: 10.0),
132 | child: Image.network(site.icon),
133 | ),
134 | Expanded(
135 | child: Text(
136 | site.title,
137 | maxLines: 2,
138 | style: TextStyle(
139 | fontSize: 13.0,
140 | color: Colors.black87,
141 | ),
142 | overflow: TextOverflow.ellipsis,
143 | ),
144 | ),
145 | ],
146 | ),
147 | Container(
148 | height: 1.2,
149 | margin: EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0),
150 | color: Colors.black12,
151 | )
152 | ],
153 | ),
154 | ),
155 | ),
156 | color: Colors.transparent,
157 | );
158 | }
159 |
160 | Widget _buildCategoryItem(ReadingCategory category) {
161 | return GestureDetector(
162 | onTap: () {
163 | _bus.fire(ReadingCategorySwitchEvent(category));
164 | },
165 | child: Container(
166 | height: 46.0,
167 | padding: EdgeInsets.only(top: 8.0, bottom: 8.0, left: 8.0, right: 8.0),
168 | child: Container(
169 | padding: EdgeInsets.only(left: 10.0, right: 10.0),
170 | decoration: BoxDecoration(
171 | color: Colors.blueAccent,
172 | borderRadius: BorderRadius.all(
173 | Radius.circular(15.0),
174 | ),
175 | ),
176 | child: Center(
177 | child: Text(
178 | '${category.name}',
179 | maxLines: 1,
180 | overflow: TextOverflow.ellipsis,
181 | style: TextStyle(
182 | fontSize: 13.5,
183 | fontWeight: FontWeight.w300,
184 | color: category.selected ? Colors.deepOrangeAccent : Colors.white,
185 | ),
186 | ),
187 | ),
188 | ),
189 | ),
190 | );
191 | }
192 |
193 | @override
194 | fetchCategoryReceived(ReadingCategoryResult result) {
195 | if (result.results != null && result.results.length != 0) {
196 | _bus.fire(ReadingCategoryEvent(categories: result.results));
197 | }
198 | }
199 |
200 | @override
201 | fetchSitesReceived(ReadingSiteResult result) {
202 | if (result.results != null && result.results.length != 0) {
203 | _bus.fire(ReadingSiteEvent(sites: result.results));
204 | }
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/lib/display/search.dart:
--------------------------------------------------------------------------------
1 | import 'package:event_bus/event_bus.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter/services.dart';
4 | import 'package:gankio/data/search_result.dart';
5 | import 'package:gankio/display/web_view.dart';
6 | import 'package:gankio/main.dart';
7 | import 'package:http/http.dart' as http;
8 |
9 | var _bus = EventBus();
10 |
11 | class SearchPage extends StatefulWidget {
12 | @override
13 | _SearchPageState createState() => new _SearchPageState();
14 | }
15 |
16 | class _SearchPageState extends State {
17 | List _searchResults = List();
18 |
19 | var _currentKeyword = '';
20 | var _page = 1;
21 |
22 | var _pageTitle = '搜索干货';
23 |
24 | _updateResults(List items, bool clear) {
25 | if (this.mounted) {
26 | setState(() {
27 | if (clear) _searchResults.clear();
28 | _searchResults.addAll(items);
29 | });
30 | }
31 | }
32 |
33 | @override
34 | Widget build(BuildContext context) {
35 | var _keywordInputController = TextEditingController(text: _currentKeyword);
36 | var _requesting = false;
37 | var _endList = false;
38 |
39 | var _scrollController = ScrollController();
40 |
41 | _bus.on<_SearchResultEvent>().listen((event) {
42 | _requesting = false;
43 | if (event.result.results != null && event.result.results.length != 0) {
44 | _updateResults(event.result.results, event.clear);
45 | } else {
46 | _endList = true;
47 | }
48 | });
49 |
50 | _startSearch(bool clear) {
51 | if (clear) {
52 | _endList = false;
53 | _scrollController.jumpTo(0.0);
54 | }
55 | if (!_endList) {
56 | _currentKeyword = _keywordInputController.text;
57 | if (_currentKeyword != null && _currentKeyword.length != 0) {
58 | _pageTitle = '搜索干货:$_currentKeyword';
59 | _requesting = true;
60 | _fetchSearchResult(key: _currentKeyword, page: _page, clear: clear);
61 | SystemChannels.textInput.invokeMethod('TextInput.hide');
62 | }
63 | }
64 | }
65 |
66 | _scrollController.addListener(() {
67 | if (!_requesting && _scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
68 | _page++;
69 | _startSearch(false);
70 | }
71 | });
72 |
73 | ListView _buildListView() {
74 | return ListView.builder(
75 | controller: _scrollController,
76 | itemCount: _searchResults.length,
77 | itemBuilder: (context, index) {
78 | return _buildItemView(_searchResults[index], index);
79 | });
80 | }
81 |
82 | return new Scaffold(
83 | appBar: new AppBar(
84 | title: new Text(_pageTitle),
85 | ),
86 | body: new Container(
87 | padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 8.0, bottom: 8.0),
88 | child: new Column(
89 | children: [
90 | new Container(
91 | padding: EdgeInsets.all(5.0),
92 | child: Row(
93 | children: [
94 | Expanded(
95 | child: TextFormField(
96 | autofocus: true,
97 | style: TextStyle(fontSize: 14.0, color: Colors.black87),
98 | keyboardType: TextInputType.text,
99 | controller: _keywordInputController,
100 | onFieldSubmitted: (val) {
101 | _startSearch(true);
102 | },
103 | decoration: InputDecoration(hintText: '在此输入关键词~~'),
104 | ),
105 | ),
106 | IconButton(
107 | onPressed: () {
108 | _startSearch(true);
109 | },
110 | icon: Icon(
111 | Icons.search,
112 | color: Colors.blueGrey,
113 | ),
114 | ),
115 | ],
116 | ),
117 | ),
118 | new Expanded(
119 | child: _buildListView(),
120 | )
121 | ],
122 | )),
123 | );
124 | }
125 |
126 | Widget _buildItemView(SearchResultItem resultItem, int index) {
127 | return Container(
128 | margin: EdgeInsets.only(top: 3.5, bottom: 3.5),
129 | padding: EdgeInsets.only(top: 5.0, bottom: 5.0, left: 5.0, right: 2.0),
130 | child: GestureDetector(
131 | onTap: () {
132 | Navigator.push(
133 | context,
134 | MaterialPageRoute(
135 | builder: (context) => WebViewer(
136 | url: resultItem.url,
137 | title: resultItem.desc,
138 | )));
139 | },
140 | child: Material(
141 | child: InkWell(
142 | onTap: () {
143 | Navigator.push(
144 | context,
145 | GankNavigator(
146 | builder: (context) => WebViewer(
147 | url: resultItem.url,
148 | title: resultItem.desc,
149 | )));
150 | },
151 | child: Column(
152 | crossAxisAlignment: CrossAxisAlignment.start,
153 | children: [
154 | Text(
155 | '${index + 1}. ${resultItem.desc}'.trim(),
156 | style: TextStyle(fontSize: 15.2, color: Colors.black87),
157 | maxLines: 2,
158 | overflow: TextOverflow.ellipsis,
159 | ),
160 | Container(
161 | margin: EdgeInsets.only(top: 3.0),
162 | child: Text(
163 | '[${resultItem.type}] By ${resultItem.who} at ${resultItem.publishedAt}',
164 | style: TextStyle(fontSize: 9.0, color: Colors.grey[500]),
165 | ),
166 | ),
167 | Container(
168 | margin: EdgeInsets.only(top: 5.5),
169 | color: Colors.black12,
170 | height: 1.5,
171 | )
172 | ],
173 | ),
174 | ),
175 | color: Colors.transparent,
176 | ),
177 | ),
178 | );
179 | }
180 | }
181 |
182 | _fetchSearchResult({String key = 'Gank', String category = 'all', int page = 1, bool clear = false}) async {
183 | final response = await http.get("https://gank.io/api/search/query/$key/category/$category/count/30/page/$page");
184 | if (response.statusCode == 200) {
185 | _bus.fire(_SearchResultEvent(result: SearchResult.fromJson(response.body), clear: clear));
186 | } else {
187 | throw Exception('Search-Error ${response.statusCode}');
188 | }
189 | }
190 |
191 | class _SearchResultEvent {
192 | _SearchResultEvent({this.result, this.clear});
193 |
194 | SearchResult result;
195 |
196 | bool clear = false;
197 | }
198 |
--------------------------------------------------------------------------------
/lib/display/setting.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | class SettingPage extends StatefulWidget {
5 | @override
6 | _SettingPageState createState() => _SettingPageState();
7 | }
8 |
9 | class _SettingPageState extends State {
10 | @override
11 | Widget build(BuildContext context) {
12 | return Container(
13 | padding: EdgeInsets.only(left: 15.0, right: 15.0, top: 12.0),
14 | child: Column(
15 | children: [
16 | Container(
17 | margin: EdgeInsets.only(top: 50.0, bottom: 20.0),
18 | height: 75.0,
19 | width: 75.0,
20 | child: Image.asset('images/gankio.png'),
21 | ),
22 | RichText(
23 | text: TextSpan(
24 | children: [
25 | TextSpan(
26 | text: 'Gank.IO Flutter',
27 | style: TextStyle(color: Colors.black, fontSize: 17.0, fontWeight: FontWeight.w600)),
28 | TextSpan(text: '\nby cfelixmac_v1.0_201808', style: TextStyle(color: Colors.black54, fontSize: 12.0)),
29 | ],
30 | ),
31 | ),
32 | Container(
33 | margin: EdgeInsets.only(top: 80.0),
34 | child: Column(
35 | children: [
36 | Text(
37 | '3rd Libraries:',
38 | style: TextStyle(fontWeight: FontWeight.w600, color: Colors.black45, fontSize: 13.0),
39 | ),
40 | Text('flutter_webview_plugin',
41 | style: TextStyle(fontWeight: FontWeight.w400, color: Colors.black45, fontSize: 12.0)),
42 | Text('event_bus',
43 | style: TextStyle(fontWeight: FontWeight.w400, color: Colors.black45, fontSize: 12.0)),
44 | Text('fluttertoast',
45 | style: TextStyle(fontWeight: FontWeight.w400, color: Colors.black45, fontSize: 12.0)),
46 | ],
47 | ),
48 | )
49 | ],
50 | ),
51 | );
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/lib/display/today.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:gankio/data/article.dart';
5 | import 'package:gankio/display/today_details.dart';
6 | import 'package:html/dom.dart' as dom;
7 | import 'package:html/parser.dart' show parse;
8 | import 'package:http/http.dart' as http;
9 |
10 | class TodayPage extends StatefulWidget {
11 | @override
12 | _TodayPageState createState() => _TodayPageState();
13 | }
14 |
15 | class _TodayPageState extends State {
16 | @override
17 | Widget build(BuildContext context) {
18 | return Container(
19 | color: const Color(0x99D9DAE4),
20 | child: FutureBuilder(
21 | future: _fetchHomePage(),
22 | builder: (context, snapshot) {
23 | if (snapshot.hasData) {
24 | return _generateContent(snapshot.data, context);
25 | } else if (snapshot.hasError) {
26 | return new Center(
27 | child: Text("${snapshot.error}"),
28 | );
29 | }
30 | return new Container(
31 | width: MediaQuery.of(context).size.width * 0.15,
32 | height: MediaQuery.of(context).size.width * 0.15,
33 | child: Center(
34 | child: CircularProgressIndicator(), // By default, show a loading spinner
35 | ),
36 | );
37 | },
38 | ),
39 | );
40 | }
41 | }
42 |
43 | Widget _generateContent(Article article, BuildContext context) {
44 | List items = new List();
45 | // ==============> Title
46 | items.add(FutureBuilder(
47 | future: _fetchHtml(),
48 | builder: (context, snapshot) {
49 | if (snapshot.hasData) {
50 | for (var node in snapshot.data.head.nodes) {
51 | if (node != null && node.toString() == '') {
52 | return _generateTitle(node.text);
53 | }
54 | }
55 | }
56 | return _generateTitle("Gank IO~");
57 | },
58 | ));
59 | // ==============> RequestTime
60 | items.add(
61 | new Container(
62 | padding: EdgeInsets.only(top: 8.0, bottom: 8.0),
63 | child: Text(
64 | "请求时间:${ DateTime.now().toString()}",
65 | textAlign: TextAlign.left,
66 | style: TextStyle(fontSize: 10.0, color: const Color(0xFF747474)),
67 | maxLines: 1,
68 | overflow: TextOverflow.ellipsis,
69 | )),
70 | );
71 |
72 | // =================> App Content
73 | var appArticles = article.result['App'];
74 | if (appArticles != null && appArticles.length != 0) {
75 | items.add(_buildArticlesBlock(title: 'App', icon: 'images/app_icon.png', articles: appArticles, context: context));
76 | items.add(_buildSectionSpace());
77 | }
78 | // =================> Android Content
79 | var androidArticles = article.result['Android'];
80 | if (androidArticles != null && androidArticles.length != 0) {
81 | items.add(_buildArticlesBlock(
82 | title: 'Android', icon: 'images/android_icon.png', articles: androidArticles, context: context));
83 | items.add(_buildSectionSpace());
84 | }
85 | // =================> iOS Content
86 | var iOSArticles = article.result['iOS'];
87 | if (iOSArticles != null && iOSArticles.length != 0) {
88 | items.add(_buildArticlesBlock(title: 'iOS', icon: 'images/ios_icon.png', articles: iOSArticles, context: context));
89 | items.add(_buildSectionSpace());
90 | }
91 | // =================> QianDuan Content
92 | var frontArticles = article.result['前端'];
93 | if (frontArticles != null && frontArticles.length != 0) {
94 | items.add(
95 | _buildArticlesBlock(title: '前端', icon: 'images/front_icon.png', articles: frontArticles, context: context));
96 | items.add(_buildSectionSpace());
97 | }
98 | // =================> TuoZhan Content
99 | var extendArticles = article.result['拓展资源'];
100 | if (extendArticles != null && extendArticles.length != 0) {
101 | items.add(
102 | _buildArticlesBlock(title: '拓展资源', icon: 'images/extend_icon.png', articles: extendArticles, context: context));
103 | items.add(_buildSectionSpace());
104 | }
105 | // =================> XiuXi Content
106 | var relaxArticles = article.result['休息视频'];
107 | if (relaxArticles != null && relaxArticles.length != 0) {
108 | items.add(
109 | _buildArticlesBlock(title: '休息视频', icon: 'images/relax_icon.png', articles: relaxArticles, context: context));
110 | items.add(_buildSectionSpace());
111 | }
112 | // ==============> Girl Image
113 | String fuLiUrl =
114 | (article.result['福利'] != null && article.result['福利'].length > 0) ? article.result['福利'][0].url : null;
115 | if (fuLiUrl != null) {
116 | items.add(new Card(
117 | child: Container(
118 | height: 42.0,
119 | child: GestureDetector(
120 | onTap: () {
121 | showDialog(
122 | context: context,
123 | builder: (context) {
124 | return ProfitImageDialog(fuLiUrl);
125 | });
126 | },
127 | child: Row(
128 | children: [
129 | Expanded(
130 | child: Container(
131 | color: Colors.white,
132 | child: Column(
133 | children: [
134 | Expanded(
135 | child: Center(
136 | child: Text(
137 | '~TODAY妹子~',
138 | maxLines: 1,
139 | style: TextStyle(fontSize: 12.0, color: Colors.black38, letterSpacing: 3.0),
140 | ),
141 | ),
142 | ),
143 | ],
144 | )),
145 | ),
146 | ],
147 | ),
148 | ),
149 | ),
150 | ));
151 | items.add(Container(
152 | height: 12.0,
153 | ));
154 | }
155 |
156 | return ListView(
157 | padding: new EdgeInsets.only(left: 15.0, right: 15.0),
158 | children: items,
159 | );
160 | }
161 |
162 | Widget _buildSectionSpace() {
163 | return Container(
164 | height: 10.0,
165 | );
166 | }
167 |
168 | Widget _generateTitle(String text) {
169 | return new Container(
170 | padding: EdgeInsets.only(top: 15.0, bottom: 2.0),
171 | child: new Text(
172 | text,
173 | style: TextStyle(fontSize: 13.5, fontWeight: FontWeight.w600, color: const Color(0xFF747474)),
174 | ),
175 | );
176 | }
177 |
178 | Widget _buildArticlesBlock({String title, String icon, List articles, BuildContext context}) {
179 | var articleItems = List();
180 | articleItems.add(Container(
181 | height: 3.0,
182 | ));
183 | articleItems.add(_buildArticlesBlockTitle(title: title, icon: icon));
184 | articleItems.add(Container(
185 | height: 5.0,
186 | ));
187 | for (var sector in articles) {
188 | articleItems.add(_buildArticleItem(sector, context));
189 | }
190 | Color color = Colors.black26;
191 | switch (title) {
192 | case 'App':
193 | color = Colors.blueAccent;
194 | break;
195 | case 'iOS':
196 | color = Colors.deepPurpleAccent;
197 | break;
198 | case 'Android':
199 | color = Colors.lightGreen;
200 | break;
201 | case '前端':
202 | color = Colors.brown;
203 | break;
204 | case '拓展资源':
205 | color = Colors.blueGrey;
206 | break;
207 | case '休息视频':
208 | color = Colors.teal;
209 | break;
210 | }
211 | return Card(
212 | child: Container(
213 | color: color,
214 | child: Row(
215 | crossAxisAlignment: CrossAxisAlignment.center,
216 | children: [
217 | Container(
218 | width: 5.0,
219 | ),
220 | Expanded(
221 | child: Container(
222 | color: Colors.white,
223 | padding: EdgeInsets.only(top: 10.0),
224 | child: Column(
225 | crossAxisAlignment: CrossAxisAlignment.start,
226 | children: articleItems,
227 | ),
228 | ),
229 | )
230 | ],
231 | ),
232 | ),
233 | );
234 | }
235 |
236 | Widget _buildArticlesBlockTitle({String title, String icon}) {
237 | return Row(
238 | children: [
239 | Container(
240 | margin: EdgeInsets.only(left: 6.0),
241 | padding: EdgeInsets.all(0.0),
242 | width: 24.0,
243 | height: 24.0,
244 | child: Image.asset(icon),
245 | ),
246 | Text(
247 | ' $title',
248 | style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.w600, color: const Color(0xFF515151)),
249 | ),
250 | ],
251 | );
252 | }
253 |
254 | Widget _buildArticleItem(ArticleSector article, BuildContext context) {
255 | return Material(
256 | child: InkWell(
257 | onTap: () {
258 | showDialog(
259 | context: context,
260 | builder: (context) {
261 | return TodayDetailDialog(article);
262 | });
263 | },
264 | child: Container(
265 | padding: EdgeInsets.only(left: 18.0, top: 10.0, right: 3.0, bottom: 10.0),
266 | child: Row(
267 | crossAxisAlignment: CrossAxisAlignment.start,
268 | children: [
269 | Center(
270 | child: Container(
271 | margin: EdgeInsets.only(top: 3.5, right: 6.0),
272 | child: Image.asset('images/doc_icon.png'),
273 | width: 15.0,
274 | height: 15.0,
275 | ),
276 | ),
277 | Expanded(
278 | child: Column(
279 | crossAxisAlignment: CrossAxisAlignment.start,
280 | children: [
281 | Container(
282 | margin: EdgeInsets.only(right: 5.0),
283 | child: Text(
284 | '${article.desc.trim()}',
285 | maxLines: 2,
286 | overflow: TextOverflow.ellipsis,
287 | style: TextStyle(fontSize: 13.0, color: Color(0xFF555555)),
288 | )),
289 | Container(
290 | child: Text(
291 | '${article.who} 发布于 ${article.publishedAt.substring(0, 10)}',
292 | maxLines: 2,
293 | overflow: TextOverflow.ellipsis,
294 | textAlign: TextAlign.right,
295 | style: TextStyle(fontSize: 9.0, color: Colors.black45),
296 | ),
297 | ),
298 | ],
299 | ),
300 | ),
301 | ],
302 | ),
303 | ),
304 | ),
305 | color: Colors.transparent,
306 | );
307 | }
308 |
309 | Future _fetchHomePage() async {
310 | final response = await http.get('https://gank.io/api/today');
311 | if (response.statusCode == 200) {
312 | return Article.fromJson(response.body);
313 | } else {
314 | throw Exception('Failed to HomePage');
315 | }
316 | }
317 |
318 | Future _fetchHtml() async {
319 | final response = await http.get('https://gank.io');
320 | if (response.statusCode == 200) {
321 | return parse(response.body);
322 | } else {
323 | throw Exception('Failed to HomeHtml');
324 | }
325 | }
326 |
327 | // /////////// IMAGE REQUEST NOTE ///////////////////
328 | //
329 | // items.add(_generateSectionTitle('福利'));
330 | // items.add(_generateHorizontalLine());
331 | // String fuLiUrl = (article.result['福利'] != null && article.result['福利'].length > 0) ? article.result['福利'][0].url : null;
332 | // if (fuLiUrl != null) {
333 | // Image image = new Image.network(article.result['福利'][0].url);
334 | // Completer completer = new Completer();
335 | // image.image.resolve(new ImageConfiguration()).addListener((ImageInfo info, bool _) => completer.complete(info.image));
336 | // items.add(new FutureBuilder(
337 | // future: completer.future,
338 | // builder: (BuildContext context, AsyncSnapshot snapshot) {
339 | // if (snapshot.hasData) {
340 | // double imageWidthDefine = MediaQuery.of(context).size.width * 0.5;
341 | // return new Container(
342 | // margin: EdgeInsets.only(top: 3.0, bottom: 10.0),
343 | // width: imageWidthDefine,
344 | // height: imageWidthDefine / snapshot.data.width * snapshot.data.height,
345 | // child: image,
346 | // );
347 | // } else {
348 | // return new Text('Loading...');
349 | // }
350 | // },
351 | // ));
352 | // } else {
353 | // items.add(new Container(
354 | // margin: EdgeInsets.only(top: 15.0, bottom: 15.0),
355 | // child: Text(
356 | // '今天没有福利~QAQ~',
357 | // style: TextStyle(fontSize: 15.0),
358 | // ),
359 | // ));
360 | // }
361 |
--------------------------------------------------------------------------------
/lib/display/today_details.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:gankio/data/article.dart';
3 | import 'package:gankio/display/image_view.dart';
4 | import 'package:gankio/display/web_view.dart';
5 | import 'package:gankio/main.dart';
6 |
7 | class TodayDetailDialog extends StatefulWidget {
8 | TodayDetailDialog(this.article);
9 |
10 | final ArticleSector article;
11 |
12 | @override
13 | _TodayDetailDialogState createState() => _TodayDetailDialogState(article);
14 | }
15 |
16 | class _TodayDetailDialogState extends State {
17 | _TodayDetailDialogState(this._article);
18 |
19 | ArticleSector _article;
20 |
21 | int _imagePagePosition = 1;
22 |
23 | @override
24 | Widget build(BuildContext context) {
25 | var imageWidgets = List();
26 | if (_article.images != null && _article.images.length != 0) {
27 | for (var imgUrl in _article.images) {
28 | imageWidgets.add(
29 | Container(
30 | child: Stack(
31 | children: [
32 | Center(
33 | child: Container(
34 | width: 20.0,
35 | height: 20.0,
36 | child: CircularProgressIndicator(
37 | strokeWidth: 2.0,
38 | ),
39 | ),
40 | ),
41 | Center(
42 | child: GestureDetector(
43 | onTap: () {
44 | Navigator.push(context, GankNavigator(builder: (context) => ImageViewPage(imgUrl)));
45 | },
46 | child: Image.network(imgUrl),
47 | ),
48 | ),
49 | ],
50 | ),
51 | ),
52 | );
53 | }
54 | }
55 |
56 | var contentWidgets = List();
57 | if (imageWidgets.length != 0) {
58 | contentWidgets.add(Container(
59 | margin: EdgeInsets.only(top: 10.0, bottom: 8.0),
60 | width: MediaQuery.of(context).size.width,
61 | height: MediaQuery.of(context).size.height * 0.35,
62 | child: PageView(
63 | onPageChanged: (pos) {
64 | setState(() {
65 | _imagePagePosition = pos + 1;
66 | });
67 | },
68 | children: imageWidgets,
69 | )));
70 | contentWidgets.add(
71 | new Center(
72 | child: Text(
73 | '$_imagePagePosition/${imageWidgets.length}',
74 | style: TextStyle(
75 | color: Colors.black38,
76 | fontSize: 12.0,
77 | ),
78 | ),
79 | ),
80 | );
81 | }
82 | contentWidgets.add(_buildActionButtons(_article));
83 |
84 | return SimpleDialog(
85 | titlePadding: EdgeInsets.only(top: 10.0, left: 10.0, right: 10.0),
86 | contentPadding: EdgeInsets.all(0.0),
87 | title: Column(
88 | crossAxisAlignment: CrossAxisAlignment.start,
89 | children: [
90 | Text(
91 | '${_article.desc}',
92 | maxLines: 3,
93 | overflow: TextOverflow.ellipsis,
94 | style: TextStyle(fontSize: 15.0, color: Colors.black87, fontWeight: FontWeight.w500),
95 | ),
96 | Text(
97 | '${_article.url}',
98 | maxLines: 1,
99 | overflow: TextOverflow.ellipsis,
100 | style: TextStyle(
101 | fontSize: 10.5,
102 | color: Colors.blueAccent,
103 | ),
104 | ),
105 | ],
106 | ),
107 | children: contentWidgets,
108 | );
109 | }
110 |
111 | Widget _buildActionButtons(ArticleSector article) {
112 | return GestureDetector(
113 | onTap: () {
114 | Navigator.push(
115 | context,
116 | GankNavigator(
117 | builder: (context) => WebViewer(
118 | url: article.url,
119 | title: article.desc,
120 | )));
121 | },
122 | child: Container(
123 | color: Color(0xFF6791EF),
124 | margin: EdgeInsets.only(top: 12.0),
125 | child: Column(
126 | crossAxisAlignment: CrossAxisAlignment.center,
127 | children: [
128 | Row(
129 | children: [
130 | Expanded(
131 | child: Container(
132 | height: 50.0,
133 | padding: EdgeInsets.all(0.0),
134 | child: Center(
135 | child: Text(
136 | '点击查看详情',
137 | style: TextStyle(color: Colors.white, fontSize: 16.0, letterSpacing: 1.0),
138 | ),
139 | ),
140 | ),
141 | ),
142 | ],
143 | ),
144 | ],
145 | ),
146 | ),
147 | );
148 | }
149 | }
150 |
151 | class ProfitImageDialog extends StatelessWidget {
152 | ProfitImageDialog(this.url);
153 |
154 | final String url;
155 |
156 | @override
157 | Widget build(BuildContext context) {
158 | return SimpleDialog(
159 | contentPadding: EdgeInsets.only(top: 15.0),
160 | children: [
161 | Column(
162 | children: [
163 | Container(
164 | width: MediaQuery.of(context).size.width,
165 | height: MediaQuery.of(context).size.width,
166 | child: Image.network(url),
167 | ),
168 | GestureDetector(
169 | onTap: () {
170 | Navigator.pop(context, true);
171 | },
172 | child: Container(
173 | margin: EdgeInsets.only(top: 12.0),
174 | height: 42.0,
175 | color: Color(0xfff0ac73),
176 | child: Row(
177 | children: [
178 | Expanded(
179 | child: Center(
180 | child: Text(
181 | '关闭',
182 | style: TextStyle(color: Colors.white, fontSize: 13.0, letterSpacing: 5.0),
183 | ),
184 | ),
185 | ),
186 | ],
187 | ),
188 | ),
189 | ),
190 | ],
191 | )
192 | ],
193 | );
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/lib/display/web_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
3 |
4 | class WebViewer extends StatelessWidget {
5 | WebViewer({this.url, this.title = "Gank.IO"});
6 |
7 | final String url;
8 | final String title;
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | return new WebviewScaffold(
13 | appBar: new AppBar(
14 | title: new Text(title),
15 | ),
16 | withJavascript: true,
17 | withLocalStorage: true,
18 | withZoom: false,
19 | enableAppScheme: true,
20 | url: url);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:gankio/display/history.dart';
3 | import 'package:gankio/display/reading.dart';
4 | import 'package:gankio/display/search.dart';
5 | import 'package:gankio/display/setting.dart';
6 | import 'package:gankio/display/today.dart';
7 |
8 | void main() => runApp(new MyApp());
9 |
10 | class MyApp extends StatelessWidget {
11 | @override
12 | Widget build(BuildContext context) {
13 | return new MaterialApp(
14 | title: 'Gank IO Flutter',
15 | theme: new ThemeData(
16 | primarySwatch: Colors.blueGrey,
17 | ),
18 | home: new HomePage(title: 'Gank IO'),
19 | );
20 | }
21 | }
22 |
23 | class HomePage extends StatefulWidget {
24 | HomePage({Key key, this.title}) : super(key: key);
25 |
26 | final String title;
27 |
28 | @override
29 | _HomePageState createState() => new _HomePageState();
30 | }
31 |
32 | class _HomePageState extends State {
33 | int _pageIndex = 0;
34 | var _pageController = PageController(keepPage: true);
35 |
36 | void _updatePageIndex(int index) {
37 | setState(() {
38 | if (this.mounted) {
39 | this._pageIndex = index;
40 | }
41 | });
42 | }
43 |
44 | @override
45 | Widget build(BuildContext context) {
46 | try {
47 | // ignore: deprecated_member_use
48 | MaterialPageRoute.debugEnableFadingRoutes = true;
49 | } catch (e) {
50 | // nothing..
51 | }
52 | return new Scaffold(
53 | appBar: new AppBar(
54 | title: Row(
55 | children: [
56 | Expanded(
57 | child: new Text(widget.title),
58 | ),
59 | IconButton(
60 | icon: Icon(Icons.search),
61 | tooltip: 'Search',
62 | onPressed: () {
63 | Navigator.push(context, MaterialPageRoute(builder: (context) => SearchPage()));
64 | },
65 | )
66 | ],
67 | ),
68 | ),
69 | body: new PageView(
70 | onPageChanged: (int position) {
71 | _updatePageIndex(position);
72 | },
73 | children: [
74 | Container(
75 | key: PageStorageKey("today"),
76 | child: TodayPage(),
77 | ),
78 | Container(
79 | key: PageStorageKey("history"),
80 | child: HistoryPage(),
81 | ),
82 | Container(
83 | key: PageStorageKey("reading"),
84 | child: ReadingPage(),
85 | ),
86 | Container(
87 | key: PageStorageKey("setting"),
88 | child: SettingPage(),
89 | ),
90 | ],
91 | controller: _pageController,
92 | ),
93 | bottomNavigationBar: new BottomNavigationBar(
94 | type: BottomNavigationBarType.fixed,
95 | items: [
96 | new BottomNavigationBarItem(icon: Icon(Icons.home), title: Text("今日")),
97 | new BottomNavigationBarItem(icon: Icon(Icons.history), title: Text("历史")),
98 | new BottomNavigationBarItem(icon: Icon(Icons.book), title: Text("闲读")),
99 | new BottomNavigationBarItem(icon: Icon(Icons.settings), title: Text("设置")),
100 | ],
101 | currentIndex: _pageIndex,
102 | onTap: (int pos) {
103 | _pageController.jumpToPage(pos);
104 | // _pageController.jumpTo(pos, duration: new Duration(milliseconds: 400), curve: Curves.easeInOut);
105 | _updatePageIndex(pos);
106 | },
107 | ),
108 | );
109 | }
110 | }
111 |
112 | class GankNavigator extends MaterialPageRoute {
113 | GankNavigator({@required WidgetBuilder builder}) : super(builder: builder);
114 |
115 | @override
116 | Widget buildPage(BuildContext context, Animation animation, Animation secondaryAnimation) {
117 | return new FadeTransition(
118 | opacity: animation,
119 | child: builder(context),
120 | );
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/lib/presenter/history_presenter.dart:
--------------------------------------------------------------------------------
1 | import 'package:gankio/data/history_result.dart';
2 | import 'package:http/http.dart' as http;
3 |
4 | abstract class HistoryView {
5 | fetchReceived(HistoryResult result);
6 | }
7 |
8 | class HistoryPresenter {
9 | HistoryPresenter(this._view);
10 |
11 | final HistoryView _view;
12 |
13 | fetchHistory({int page = 1}) async {
14 | final response = await http.get("https://gank.io/api/history/content/10/$page");
15 | if (response.statusCode == 200) {
16 | _view.fetchReceived(HistoryResult.fromJson(response.body));
17 | } else {
18 | throw Exception('Error ${response.statusCode}');
19 | }
20 | }
21 | }
22 |
23 | class HistoryResultEvent {
24 | HistoryResultEvent({this.result});
25 |
26 | HistoryResult result;
27 | }
28 |
--------------------------------------------------------------------------------
/lib/presenter/reading_presenter.dart:
--------------------------------------------------------------------------------
1 | import 'package:gankio/data/reading_data.dart';
2 | import 'package:http/http.dart' as http;
3 |
4 | abstract class ReadingCategoryView {
5 | fetchCategoryReceived(ReadingCategoryResult result);
6 |
7 | fetchSitesReceived(ReadingSiteResult result);
8 | }
9 |
10 | abstract class ReadingView {
11 | fetchReadingArticlesReceived(ReadingArticleResult result, bool clear);
12 |
13 | fetchReadingArticlesEnd();
14 | }
15 |
16 | class ReadingCategoryPresenter {
17 | ReadingCategoryPresenter(this._categoryView);
18 |
19 | final ReadingCategoryView _categoryView;
20 |
21 | fetchCategory() async {
22 | final response = await http.get("https://gank.io/api/xiandu/categories");
23 | if (response.statusCode == 200 && _categoryView != null) {
24 | _categoryView.fetchCategoryReceived(ReadingCategoryResult.fromJson(response.body));
25 | } else {
26 | throw Exception('fetchCategory-Error ${response.statusCode}');
27 | }
28 | }
29 |
30 | fetchSites(String category) async {
31 | final response = await http.get("https://gank.io/api/xiandu/category/$category");
32 | if (response.statusCode == 200 && _categoryView != null) {
33 | _categoryView.fetchSitesReceived(ReadingSiteResult.fromJson(response.body));
34 | } else {
35 | throw Exception('fetchSites-$category-Error ${response.statusCode}');
36 | }
37 | }
38 | }
39 |
40 | class ReadingPresenter {
41 | ReadingPresenter(this._readingView);
42 |
43 | final ReadingView _readingView;
44 |
45 | var _page = 1;
46 | var _requesting = false;
47 |
48 | fetchReadingArticles(String site, bool clear) async {
49 | if (!_requesting) {
50 | if (!clear) _page++;
51 | _requesting = true;
52 | final response = await http.get("https://gank.io/api/xiandu/data/id/$site/count/25/page/$_page");
53 | if (response.statusCode == 200 && _readingView != null) {
54 | _requesting = false;
55 | ReadingArticleResult result = ReadingArticleResult.fromJson(response.body);
56 | if (result != null && result.results != null && result.results.length != 0) {
57 | _readingView.fetchReadingArticlesReceived(result, clear);
58 | } else {
59 | _readingView.fetchReadingArticlesEnd();
60 | }
61 | } else {
62 | throw Exception('fetchReadingArticles-$site-Error ${response.statusCode}');
63 | }
64 | }
65 | }
66 |
67 | handleSiteChange() {
68 | _page = 1;
69 | _requesting = false;
70 | }
71 | }
72 |
73 | class ReadingCategoryEvent {
74 | ReadingCategoryEvent({this.categories});
75 |
76 | List categories;
77 | }
78 |
79 | class ReadingSiteEvent {
80 | ReadingSiteEvent({this.sites});
81 |
82 | List sites;
83 | }
84 |
85 | class ReadingCategorySwitchEvent {
86 | ReadingCategorySwitchEvent(this.category);
87 |
88 | ReadingCategory category;
89 | }
90 |
91 | class ReadingArticlesEvent {
92 | ReadingArticlesEvent({this.results, this.clear = false});
93 |
94 | bool clear;
95 | List results;
96 | }
97 |
98 | class ReadingArticlesEndEvent {}
99 |
--------------------------------------------------------------------------------
/lib/widget/photo_view.dart:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Renan C. Araújo
3 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
4 | * documentation files (the "Software"), * to deal in the Software without restriction, including without limitation
5 | * the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software,
6 | * and to permit persons to whom the Software is furnished to do so, * subject to the following conditions:
7 | *
8 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
10 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
11 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
12 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
13 | * USE OR OTHER DEALINGS IN THE SOFTWARE.
14 | */
15 | library photo_view;
16 |
17 | import 'dart:async';
18 | import 'package:flutter/material.dart';
19 | import 'package:gankio/widget/photo_view_image_wrapper.dart';
20 | import 'package:gankio/widget/photo_view_scale_type.dart';
21 | import 'package:gankio/widget/photo_view_utils.dart';
22 |
23 | class PhotoView extends StatefulWidget {
24 | final ImageProvider imageProvider;
25 | final Widget loadingChild;
26 | final Color backgroundColor;
27 | final double minScale;
28 | final double maxScale;
29 |
30 | PhotoView({
31 | Key key,
32 | @required this.imageProvider,
33 | this.loadingChild,
34 | this.backgroundColor = const Color.fromRGBO(0, 0, 0, 1.0),
35 | this.minScale = 0.0,
36 | this.maxScale,
37 | }) : super(key: key);
38 |
39 | @override
40 | State createState() {
41 | return new _PhotoViewState();
42 | }
43 | }
44 |
45 | class _PhotoViewState extends State {
46 | PhotoViewScaleType _scaleType;
47 |
48 | Future _getImage() {
49 | Completer completer = new Completer();
50 | ImageStream stream = widget.imageProvider.resolve(new ImageConfiguration());
51 | stream.addListener((ImageInfo info, bool completed) {
52 | if (!completer.isCompleted) {
53 | completer.complete(info);
54 | }
55 | });
56 | return completer.future;
57 | }
58 |
59 | void onDoubleTap() {
60 | setState(() {
61 | _scaleType = nextScaleType(_scaleType);
62 | });
63 | }
64 |
65 | void onStartPanning() {
66 | setState(() {
67 | _scaleType = PhotoViewScaleType.zooming;
68 | });
69 | }
70 |
71 | @override
72 | void initState() {
73 | super.initState();
74 | _scaleType = PhotoViewScaleType.contained;
75 | }
76 |
77 | @override
78 | Widget build(BuildContext context) {
79 | return new FutureBuilder(
80 | future: _getImage(),
81 | builder: (BuildContext context, AsyncSnapshot info) {
82 | if (info.hasData) {
83 | return new PhotoViewImageWrapper(
84 | onDoubleTap: onDoubleTap,
85 | onStartPanning: onStartPanning,
86 | imageInfo: info.data,
87 | scaleType: _scaleType,
88 | backgroundColor: widget.backgroundColor,
89 | minScale: widget.minScale,
90 | maxScale: widget.maxScale,
91 | );
92 | } else {
93 | return buildLoading();
94 | }
95 | });
96 | }
97 |
98 | Widget buildLoading() {
99 | return widget.loadingChild != null
100 | ? widget.loadingChild
101 | : new Center(
102 | child: new Text(
103 | "Loading",
104 | style: new TextStyle(color: Colors.white),
105 | ));
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/lib/widget/photo_view_image_wrapper.dart:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Renan C. Araújo
3 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
4 | * documentation files (the "Software"), * to deal in the Software without restriction, including without limitation
5 | * the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software,
6 | * and to permit persons to whom the Software is furnished to do so, * subject to the following conditions:
7 | *
8 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
10 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
11 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
12 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
13 | * USE OR OTHER DEALINGS IN THE SOFTWARE.
14 | */
15 | import 'package:flutter/material.dart';
16 | import 'package:gankio/widget/photo_view_scale_type.dart';
17 | import 'package:gankio/widget/photo_view_utils.dart';
18 |
19 | class PhotoViewImageWrapper extends StatefulWidget {
20 | const PhotoViewImageWrapper(
21 | {Key key,
22 | @required this.onDoubleTap,
23 | @required this.onStartPanning,
24 | @required this.imageInfo,
25 | @required this.scaleType,
26 | this.backgroundColor,
27 | this.minScale,
28 | this.maxScale})
29 | : super(key: key);
30 |
31 | final Function onDoubleTap;
32 | final Function onStartPanning;
33 | final ImageInfo imageInfo;
34 | final PhotoViewScaleType scaleType;
35 | final Color backgroundColor;
36 | final double minScale;
37 | final double maxScale;
38 |
39 | @override
40 | State createState() {
41 | return new _PhotoViewImageWrapperState();
42 | }
43 | }
44 |
45 | class _PhotoViewImageWrapperState extends State with TickerProviderStateMixin {
46 | Offset _position;
47 | Offset _normalizedPosition;
48 | double _scale;
49 | double _scaleBefore;
50 |
51 | AnimationController _scaleAnimationController;
52 | Animation _scaleAnimation;
53 |
54 | AnimationController _positionAnimationController;
55 | Animation _positionAnimation;
56 |
57 | void handleScaleAnimation() {
58 | setState(() {
59 | _scale = _scaleAnimation.value;
60 | });
61 | }
62 |
63 | void handlePositionAnimate() {
64 | setState(() {
65 | _position = _positionAnimation.value;
66 | });
67 | }
68 |
69 | void onScaleStart(ScaleStartDetails details) {
70 | _scaleBefore = scaleTypeAwareScale();
71 | _normalizedPosition = (details.focalPoint - _position);
72 | _scaleAnimationController.stop();
73 | _positionAnimationController.stop();
74 | }
75 |
76 | void onScaleUpdate(ScaleUpdateDetails details) {
77 | final double newScale = (_scaleBefore * details.scale);
78 | final Offset delta = (details.focalPoint - _normalizedPosition);
79 | if (details.scale != 1.0) {
80 | widget.onStartPanning();
81 | }
82 | setState(() {
83 | _scale = newScale;
84 | _position = clampPosition(delta * details.scale);
85 | });
86 | }
87 |
88 | void onScaleEnd(ScaleEndDetails details) {
89 | //animate back to maxScale if gesture exceeded the maxscale specified
90 | if ((widget.maxScale != null) && (this._scale > widget.maxScale)) {
91 | double scaleComebackRatio = widget.maxScale / this._scale;
92 | print(scaleComebackRatio);
93 |
94 | animateScale(_scale, widget.maxScale);
95 | animatePosition(_position, clampPosition(_position * scaleComebackRatio));
96 | return;
97 | }
98 |
99 | //animate back to minScale if gesture fell smaller than the minScale specified
100 | if (widget.minScale != null && this._scale < widget.minScale) {
101 | double scaleComebackRatio = widget.minScale / this._scale;
102 | animateScale(_scale, widget.minScale);
103 | animatePosition(_position, clampPosition(_position * scaleComebackRatio));
104 | }
105 | }
106 |
107 | Offset clampPosition(Offset offset) {
108 | final x = offset.dx;
109 | final y = offset.dy;
110 | final computedWidth = widget.imageInfo.image.width * scaleTypeAwareScale();
111 | final computedHeight = widget.imageInfo.image.height * scaleTypeAwareScale();
112 | final screenWidth = MediaQuery.of(context).size.width;
113 | final screenHeight = MediaQuery.of(context).size.height;
114 | final screenHalfX = screenWidth / 2;
115 | final screenHalfY = screenHeight / 2;
116 |
117 | final double computedX = screenWidth < computedWidth
118 | ? x.clamp(0 - (computedWidth / 2) + screenHalfX, computedWidth / 2 - screenHalfX)
119 | : 0.0;
120 |
121 | final double computedY = screenHeight < computedHeight
122 | ? y.clamp(0 - (computedHeight / 2) + screenHalfY, computedHeight / 2 - screenHalfY)
123 | : 0.0;
124 |
125 | return new Offset(computedX, computedY);
126 | }
127 |
128 | double scaleTypeAwareScale() {
129 | return _scale != null || widget.scaleType == PhotoViewScaleType.zooming
130 | ? _scale
131 | : getScaleForScaleType(
132 | imageInfo: widget.imageInfo, scaleType: widget.scaleType, size: MediaQuery.of(context).size);
133 | }
134 |
135 | void animateScale(double from, double to) {
136 | _scaleAnimation = new Tween(
137 | begin: from,
138 | end: to,
139 | ).animate(_scaleAnimationController);
140 | _scaleAnimationController
141 | ..value = 0.0
142 | ..fling(velocity: 0.4);
143 | }
144 |
145 | void animatePosition(Offset from, Offset to) {
146 | _positionAnimation = new Tween(begin: from, end: to).animate(_positionAnimationController);
147 | _positionAnimationController
148 | ..value = 0.0
149 | ..fling(velocity: 0.4);
150 | }
151 |
152 | @override
153 | void initState() {
154 | super.initState();
155 | _position = Offset.zero;
156 | _scale = null;
157 | _scaleAnimationController = new AnimationController(vsync: this)..addListener(handleScaleAnimation);
158 |
159 | _positionAnimationController = new AnimationController(vsync: this)..addListener(handlePositionAnimate);
160 | }
161 |
162 | @override
163 | void dispose() {
164 | _positionAnimationController.dispose();
165 | _scaleAnimationController.dispose();
166 | super.dispose();
167 | }
168 |
169 | void didUpdateWidget(PhotoViewImageWrapper oldWidget) {
170 | super.didUpdateWidget(oldWidget);
171 | if (oldWidget.scaleType != widget.scaleType && widget.scaleType != PhotoViewScaleType.zooming) {
172 | animateScale(
173 | _scale == null
174 | ? getScaleForScaleType(
175 | imageInfo: widget.imageInfo,
176 | scaleType: PhotoViewScaleType.contained,
177 | size: MediaQuery.of(context).size)
178 | : _scale,
179 | getScaleForScaleType(
180 | imageInfo: widget.imageInfo, scaleType: widget.scaleType, size: MediaQuery.of(context).size));
181 | animatePosition(_position, Offset.zero);
182 | }
183 | }
184 |
185 | @override
186 | Widget build(BuildContext context) {
187 | var matrix = new Matrix4.identity()
188 | ..translate(_position.dx, _position.dy)
189 | ..scale(scaleTypeAwareScale());
190 |
191 | return new GestureDetector(
192 | child: new Container(
193 | child: new Center(
194 | child: new Transform(
195 | child: new CustomSingleChildLayout(
196 | delegate: new ImagePositionDelegate(widget.imageInfo.image.width / 1, widget.imageInfo.image.height / 1),
197 | child: new RawImage(
198 | image: widget.imageInfo.image,
199 | scale: widget.imageInfo.scale,
200 | ),
201 | ),
202 | transform: matrix,
203 | alignment: Alignment.center,
204 | )),
205 | decoration: new BoxDecoration(color: widget.backgroundColor),
206 | ),
207 | onDoubleTap: widget.onDoubleTap,
208 | onScaleStart: onScaleStart,
209 | onScaleUpdate: onScaleUpdate,
210 | onScaleEnd: onScaleEnd,
211 | );
212 | }
213 | }
214 |
215 | class ImagePositionDelegate extends SingleChildLayoutDelegate {
216 | final double imageWidth;
217 | final double imageHeight;
218 |
219 | const ImagePositionDelegate(this.imageWidth, this.imageHeight);
220 |
221 | @override
222 | Offset getPositionForChild(Size size, Size childSize) {
223 | double offsetX = ((size.width - imageWidth) / 2);
224 | double offsetY = ((size.height - imageHeight) / 2);
225 | return new Offset(offsetX, offsetY);
226 | }
227 |
228 | @override
229 | BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
230 | return new BoxConstraints(
231 | maxWidth: imageWidth,
232 | maxHeight: imageHeight,
233 | minHeight: imageHeight,
234 | minWidth: imageWidth,
235 | );
236 | }
237 |
238 | @override
239 | bool shouldRelayout(SingleChildLayoutDelegate oldDelegate) {
240 | return true;
241 | }
242 | }
243 |
--------------------------------------------------------------------------------
/lib/widget/photo_view_scale_type.dart:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Renan C. Araújo
3 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
4 | * documentation files (the "Software"), * to deal in the Software without restriction, including without limitation
5 | * the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software,
6 | * and to permit persons to whom the Software is furnished to do so, * subject to the following conditions:
7 | *
8 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
10 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
11 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
12 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
13 | * USE OR OTHER DEALINGS IN THE SOFTWARE.
14 | */
15 | enum PhotoViewScaleType { contained, covering, originalSize, zooming }
16 |
--------------------------------------------------------------------------------
/lib/widget/photo_view_utils.dart:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Renan C. Araújo
3 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
4 | * documentation files (the "Software"), * to deal in the Software without restriction, including without limitation
5 | * the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software,
6 | * and to permit persons to whom the Software is furnished to do so, * subject to the following conditions:
7 | *
8 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
10 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
11 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
12 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
13 | * USE OR OTHER DEALINGS IN THE SOFTWARE.
14 | */
15 | import 'dart:math' as Math;
16 |
17 | import 'package:flutter/material.dart';
18 | import 'package:gankio/widget/photo_view_scale_type.dart';
19 |
20 | PhotoViewScaleType nextScaleType(PhotoViewScaleType actual) {
21 | switch (actual) {
22 | case PhotoViewScaleType.contained:
23 | return PhotoViewScaleType.covering;
24 | case PhotoViewScaleType.covering:
25 | return PhotoViewScaleType.originalSize;
26 | case PhotoViewScaleType.originalSize:
27 | return PhotoViewScaleType.contained;
28 | case PhotoViewScaleType.zooming:
29 | return PhotoViewScaleType.contained;
30 | default:
31 | return PhotoViewScaleType.contained;
32 | }
33 | }
34 |
35 | double getScaleForScaleType(
36 | {@required Size size, @required PhotoViewScaleType scaleType, @required ImageInfo imageInfo}) {
37 | switch (scaleType) {
38 | case PhotoViewScaleType.contained:
39 | return scaleForContained(size: size, imageInfo: imageInfo);
40 | case PhotoViewScaleType.covering:
41 | return scaleForCovering(size: size, imageInfo: imageInfo);
42 | case PhotoViewScaleType.originalSize:
43 | return 1.0;
44 | default:
45 | return null;
46 | }
47 | }
48 |
49 | double scaleForContained({@required Size size, @required ImageInfo imageInfo}) {
50 | final int imageWidth = imageInfo.image.width;
51 | final int imageHeight = imageInfo.image.height;
52 |
53 | final double screenWidth = size.width;
54 | final double screenHeight = size.height;
55 |
56 | return Math.min(screenWidth / imageWidth, screenHeight / imageHeight);
57 | }
58 |
59 | double scaleForCovering({@required Size size, @required ImageInfo imageInfo}) {
60 | final int imageWidth = imageInfo.image.width;
61 | final int imageHeight = imageInfo.image.height;
62 |
63 | final double screenWidth = size.width;
64 | final double screenHeight = size.height;
65 |
66 | return Math.max(screenWidth / imageWidth, screenHeight / imageHeight);
67 | }
68 |
--------------------------------------------------------------------------------
/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://www.dartlang.org/tools/pub/glossary#lockfile
3 | packages:
4 | analyzer:
5 | dependency: transitive
6 | description:
7 | name: analyzer
8 | url: "https://pub.dartlang.org"
9 | source: hosted
10 | version: "0.31.2-alpha.2"
11 | args:
12 | dependency: transitive
13 | description:
14 | name: args
15 | url: "https://pub.dartlang.org"
16 | source: hosted
17 | version: "1.4.3"
18 | async:
19 | dependency: transitive
20 | description:
21 | name: async
22 | url: "https://pub.dartlang.org"
23 | source: hosted
24 | version: "2.0.7"
25 | boolean_selector:
26 | dependency: transitive
27 | description:
28 | name: boolean_selector
29 | url: "https://pub.dartlang.org"
30 | source: hosted
31 | version: "1.0.3"
32 | charcode:
33 | dependency: transitive
34 | description:
35 | name: charcode
36 | url: "https://pub.dartlang.org"
37 | source: hosted
38 | version: "1.1.1"
39 | collection:
40 | dependency: transitive
41 | description:
42 | name: collection
43 | url: "https://pub.dartlang.org"
44 | source: hosted
45 | version: "1.14.6"
46 | convert:
47 | dependency: transitive
48 | description:
49 | name: convert
50 | url: "https://pub.dartlang.org"
51 | source: hosted
52 | version: "2.0.1"
53 | crypto:
54 | dependency: transitive
55 | description:
56 | name: crypto
57 | url: "https://pub.dartlang.org"
58 | source: hosted
59 | version: "2.0.3"
60 | csslib:
61 | dependency: transitive
62 | description:
63 | name: csslib
64 | url: "https://pub.dartlang.org"
65 | source: hosted
66 | version: "0.14.4"
67 | cupertino_icons:
68 | dependency: "direct main"
69 | description:
70 | name: cupertino_icons
71 | url: "https://pub.dartlang.org"
72 | source: hosted
73 | version: "0.1.2"
74 | event_bus:
75 | dependency: "direct main"
76 | description:
77 | name: event_bus
78 | url: "https://pub.dartlang.org"
79 | source: hosted
80 | version: "1.0.1"
81 | flutter:
82 | dependency: "direct main"
83 | description: flutter
84 | source: sdk
85 | version: "0.0.0"
86 | flutter_test:
87 | dependency: "direct dev"
88 | description: flutter
89 | source: sdk
90 | version: "0.0.0"
91 | flutter_webview_plugin:
92 | dependency: "direct main"
93 | description:
94 | name: flutter_webview_plugin
95 | url: "https://pub.dartlang.org"
96 | source: hosted
97 | version: "0.1.6"
98 | fluttertoast:
99 | dependency: "direct main"
100 | description:
101 | name: fluttertoast
102 | url: "https://pub.dartlang.org"
103 | source: hosted
104 | version: "2.0.7"
105 | front_end:
106 | dependency: transitive
107 | description:
108 | name: front_end
109 | url: "https://pub.dartlang.org"
110 | source: hosted
111 | version: "0.1.0-alpha.12"
112 | glob:
113 | dependency: transitive
114 | description:
115 | name: glob
116 | url: "https://pub.dartlang.org"
117 | source: hosted
118 | version: "1.1.5"
119 | html:
120 | dependency: "direct main"
121 | description:
122 | name: html
123 | url: "https://pub.dartlang.org"
124 | source: hosted
125 | version: "0.13.3"
126 | http:
127 | dependency: "direct main"
128 | description:
129 | name: http
130 | url: "https://pub.dartlang.org"
131 | source: hosted
132 | version: "0.11.3+16"
133 | http_multi_server:
134 | dependency: transitive
135 | description:
136 | name: http_multi_server
137 | url: "https://pub.dartlang.org"
138 | source: hosted
139 | version: "2.0.4"
140 | http_parser:
141 | dependency: transitive
142 | description:
143 | name: http_parser
144 | url: "https://pub.dartlang.org"
145 | source: hosted
146 | version: "3.1.2"
147 | io:
148 | dependency: transitive
149 | description:
150 | name: io
151 | url: "https://pub.dartlang.org"
152 | source: hosted
153 | version: "0.3.2+1"
154 | js:
155 | dependency: transitive
156 | description:
157 | name: js
158 | url: "https://pub.dartlang.org"
159 | source: hosted
160 | version: "0.6.1"
161 | kernel:
162 | dependency: transitive
163 | description:
164 | name: kernel
165 | url: "https://pub.dartlang.org"
166 | source: hosted
167 | version: "0.3.0-alpha.12"
168 | logging:
169 | dependency: transitive
170 | description:
171 | name: logging
172 | url: "https://pub.dartlang.org"
173 | source: hosted
174 | version: "0.11.3+1"
175 | matcher:
176 | dependency: transitive
177 | description:
178 | name: matcher
179 | url: "https://pub.dartlang.org"
180 | source: hosted
181 | version: "0.12.2+1"
182 | meta:
183 | dependency: transitive
184 | description:
185 | name: meta
186 | url: "https://pub.dartlang.org"
187 | source: hosted
188 | version: "1.1.5"
189 | mime:
190 | dependency: transitive
191 | description:
192 | name: mime
193 | url: "https://pub.dartlang.org"
194 | source: hosted
195 | version: "0.9.6"
196 | multi_server_socket:
197 | dependency: transitive
198 | description:
199 | name: multi_server_socket
200 | url: "https://pub.dartlang.org"
201 | source: hosted
202 | version: "1.0.1"
203 | node_preamble:
204 | dependency: transitive
205 | description:
206 | name: node_preamble
207 | url: "https://pub.dartlang.org"
208 | source: hosted
209 | version: "1.4.1"
210 | package_config:
211 | dependency: transitive
212 | description:
213 | name: package_config
214 | url: "https://pub.dartlang.org"
215 | source: hosted
216 | version: "1.0.3"
217 | package_resolver:
218 | dependency: transitive
219 | description:
220 | name: package_resolver
221 | url: "https://pub.dartlang.org"
222 | source: hosted
223 | version: "1.0.2"
224 | path:
225 | dependency: transitive
226 | description:
227 | name: path
228 | url: "https://pub.dartlang.org"
229 | source: hosted
230 | version: "1.5.1"
231 | plugin:
232 | dependency: transitive
233 | description:
234 | name: plugin
235 | url: "https://pub.dartlang.org"
236 | source: hosted
237 | version: "0.2.0+2"
238 | pool:
239 | dependency: transitive
240 | description:
241 | name: pool
242 | url: "https://pub.dartlang.org"
243 | source: hosted
244 | version: "1.3.4"
245 | pub_semver:
246 | dependency: transitive
247 | description:
248 | name: pub_semver
249 | url: "https://pub.dartlang.org"
250 | source: hosted
251 | version: "1.4.1"
252 | quiver:
253 | dependency: transitive
254 | description:
255 | name: quiver
256 | url: "https://pub.dartlang.org"
257 | source: hosted
258 | version: "0.29.0+1"
259 | shelf:
260 | dependency: transitive
261 | description:
262 | name: shelf
263 | url: "https://pub.dartlang.org"
264 | source: hosted
265 | version: "0.7.3"
266 | shelf_packages_handler:
267 | dependency: transitive
268 | description:
269 | name: shelf_packages_handler
270 | url: "https://pub.dartlang.org"
271 | source: hosted
272 | version: "1.0.3"
273 | shelf_static:
274 | dependency: transitive
275 | description:
276 | name: shelf_static
277 | url: "https://pub.dartlang.org"
278 | source: hosted
279 | version: "0.2.7"
280 | shelf_web_socket:
281 | dependency: transitive
282 | description:
283 | name: shelf_web_socket
284 | url: "https://pub.dartlang.org"
285 | source: hosted
286 | version: "0.2.2"
287 | sky_engine:
288 | dependency: transitive
289 | description: flutter
290 | source: sdk
291 | version: "0.0.99"
292 | source_map_stack_trace:
293 | dependency: transitive
294 | description:
295 | name: source_map_stack_trace
296 | url: "https://pub.dartlang.org"
297 | source: hosted
298 | version: "1.1.4"
299 | source_maps:
300 | dependency: transitive
301 | description:
302 | name: source_maps
303 | url: "https://pub.dartlang.org"
304 | source: hosted
305 | version: "0.10.5"
306 | source_span:
307 | dependency: transitive
308 | description:
309 | name: source_span
310 | url: "https://pub.dartlang.org"
311 | source: hosted
312 | version: "1.4.0"
313 | stack_trace:
314 | dependency: transitive
315 | description:
316 | name: stack_trace
317 | url: "https://pub.dartlang.org"
318 | source: hosted
319 | version: "1.9.2"
320 | stream_channel:
321 | dependency: transitive
322 | description:
323 | name: stream_channel
324 | url: "https://pub.dartlang.org"
325 | source: hosted
326 | version: "1.6.6"
327 | string_scanner:
328 | dependency: transitive
329 | description:
330 | name: string_scanner
331 | url: "https://pub.dartlang.org"
332 | source: hosted
333 | version: "1.0.2"
334 | term_glyph:
335 | dependency: transitive
336 | description:
337 | name: term_glyph
338 | url: "https://pub.dartlang.org"
339 | source: hosted
340 | version: "1.0.0"
341 | test:
342 | dependency: transitive
343 | description:
344 | name: test
345 | url: "https://pub.dartlang.org"
346 | source: hosted
347 | version: "0.12.37"
348 | typed_data:
349 | dependency: transitive
350 | description:
351 | name: typed_data
352 | url: "https://pub.dartlang.org"
353 | source: hosted
354 | version: "1.1.5"
355 | utf:
356 | dependency: transitive
357 | description:
358 | name: utf
359 | url: "https://pub.dartlang.org"
360 | source: hosted
361 | version: "0.9.0+4"
362 | vector_math:
363 | dependency: transitive
364 | description:
365 | name: vector_math
366 | url: "https://pub.dartlang.org"
367 | source: hosted
368 | version: "2.0.6"
369 | watcher:
370 | dependency: transitive
371 | description:
372 | name: watcher
373 | url: "https://pub.dartlang.org"
374 | source: hosted
375 | version: "0.9.7+7"
376 | web_socket_channel:
377 | dependency: transitive
378 | description:
379 | name: web_socket_channel
380 | url: "https://pub.dartlang.org"
381 | source: hosted
382 | version: "1.0.7"
383 | yaml:
384 | dependency: transitive
385 | description:
386 | name: yaml
387 | url: "https://pub.dartlang.org"
388 | source: hosted
389 | version: "2.1.13"
390 | sdks:
391 | dart: ">=2.0.0-dev.58.0 <=2.0.0-dev.58.0.flutter-f981f09760"
392 | flutter: ">=0.1.4 <2.0.0"
393 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: gankio
2 | description: A Gank.io Client by Flutter
3 |
4 | dependencies:
5 | flutter:
6 | sdk: flutter
7 |
8 | # The following adds the Cupertino Icons font to your application.
9 | # Use with the CupertinoIcons class for iOS style icons.
10 | cupertino_icons: ^0.1.2
11 |
12 | http: ^0.11.3+16
13 | html: ^0.13.3
14 | flutter_webview_plugin: ^0.1.6
15 | event_bus: ^1.0.1
16 | fluttertoast: ^2.0.7
17 |
18 | dev_dependencies:
19 | flutter_test:
20 | sdk: flutter
21 |
22 | # For information on the generic Dart part of this file, see the
23 | # following page: https://www.dartlang.org/tools/pub/pubspec
24 |
25 | # The following section is specific to Flutter.
26 | flutter:
27 | # The following line ensures that the Material Icons font is
28 | # included with your application, so that you can use the icons in
29 | # the material Icons class.
30 | uses-material-design: true
31 |
32 | assets:
33 | - images/doc_icon.png
34 | - images/android_icon.png
35 | - images/app_icon.png
36 | - images/extend_icon.png
37 | - images/front_icon.png
38 | - images/ios_icon.png
39 | - images/relax_icon.png
40 | - images/gankio.png
41 |
42 | # An image asset can refer to one or more resolution-specific "variants", see
43 | # https://flutter.io/assets-and-images/#resolution-aware.
44 |
45 | # For details regarding adding assets from package dependencies, see
46 | # https://flutter.io/assets-and-images/#from-packages
47 |
48 | # To add custom fonts to your application, add a fonts section here,
49 | # in this "flutter" section. Each entry in this list should have a
50 | # "family" key with the font family name, and a "fonts" key with a
51 | # list giving the asset and other descriptors for the font. For
52 | # example:
53 | # fonts:
54 | # - family: Schyler
55 | # fonts:
56 | # - asset: fonts/Schyler-Regular.ttf
57 | # - asset: fonts/Schyler-Italic.ttf
58 | # style: italic
59 | # - family: Trajan Pro
60 | # fonts:
61 | # - asset: fonts/TrajanPro.ttf
62 | # - asset: fonts/TrajanPro_Bold.ttf
63 | # weight: 700
64 | #
65 | # For details regarding fonts from package dependencies,
66 | # see https://flutter.io/custom-fonts/#from-packages
67 |
--------------------------------------------------------------------------------
/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:gankio/main.dart';
11 |
12 | void main() {
13 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
14 | // Build our app and trigger a frame.
15 | await tester.pumpWidget(new MyApp());
16 |
17 | // Verify that our counter starts at 0.
18 | expect(find.text('0'), findsOneWidget);
19 | expect(find.text('1'), findsNothing);
20 |
21 | // Tap the '+' icon and trigger a frame.
22 | await tester.tap(find.byIcon(Icons.add));
23 | await tester.pump();
24 |
25 | // Verify that our counter has incremented.
26 | expect(find.text('0'), findsNothing);
27 | expect(find.text('1'), findsOneWidget);
28 | });
29 | }
30 |
--------------------------------------------------------------------------------