├── .gitignore ├── .idea ├── codeStyles │ └── Project.xml ├── libraries │ ├── Dart_Packages.xml │ ├── Dart_SDK.xml │ ├── Flutter_Plugins.xml │ └── Flutter_for_Android.xml ├── misc.xml ├── modules.xml ├── runConfigurations │ └── main_dart.xml ├── vcs.xml └── workspace.xml ├── .metadata ├── LICENSE ├── README.md ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── yubo │ │ │ └── flutterosc │ │ │ └── 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 ├── key.properties └── settings.gradle ├── apk └── app-release.apk ├── flutter_osc.iml ├── flutter_osc_android.iml ├── images ├── cover_img.jpg ├── ic_add_pics.png ├── ic_arrow_right.png ├── ic_avatar_default.png ├── ic_comment.png ├── ic_discover_gist.png ├── ic_discover_git.png ├── ic_discover_nearby.png ├── ic_discover_pos.png ├── ic_discover_scan.png ├── ic_discover_shake.png ├── ic_discover_softwares.png ├── ic_eye.png ├── ic_hongshu.jpg ├── ic_house.png ├── ic_img_default.jpg ├── ic_logo.jpeg ├── ic_my_blog.png ├── ic_my_message.png ├── ic_my_question.png ├── ic_my_recommend.png ├── ic_my_team.png ├── ic_nav_discover_actived.png ├── ic_nav_discover_normal.png ├── ic_nav_my_normal.png ├── ic_nav_my_pressed.png ├── ic_nav_news_actived.png ├── ic_nav_news_normal.png ├── ic_nav_tweet_actived.png ├── ic_nav_tweet_normal.png ├── ic_osc_logo.png ├── ic_test.png └── leftmenu │ ├── dizzy_face.png │ ├── ic_about.png │ ├── ic_fabu.png │ ├── ic_settings.png │ ├── ic_xiaoheiwu.png │ └── sob.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 │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── main.m ├── lib ├── api │ └── Api.dart ├── constants │ └── Constants.dart ├── events │ ├── ChangeThemeEvent.dart │ ├── LoginEvent.dart │ ├── LogoutEvent.dart │ └── SlideEvent.dart ├── main.dart ├── model │ └── UserInfo.dart ├── pages │ ├── AboutPage.dart │ ├── BlackHousePage.dart │ ├── ChangeThemePage.dart │ ├── CommonWebPage.dart │ ├── DiscoveryPage.dart │ ├── LoginPage.dart │ ├── MyInfoPage.dart │ ├── NewLoginPage.dart │ ├── NewsDetailPage.dart │ ├── NewsListPage.dart │ ├── OfflineActivityPage.dart │ ├── PublishTweetPage.dart │ ├── SettingsPage.dart │ ├── TweetDetailPage.dart │ └── TweetsListPage.dart ├── util │ ├── BlackListUtils.dart │ ├── DataUtils.dart │ ├── NetUtils.dart │ ├── ThemeUtils.dart │ └── Utf8Utils.dart └── widgets │ ├── CircleImage.dart │ ├── CommonButton.dart │ ├── CommonEndLine.dart │ ├── MyDrawer.dart │ ├── SlideView.dart │ └── SlideViewIndicator.dart ├── pubspec.lock ├── pubspec.yaml ├── screenshots ├── android01.jpg ├── android02.jpg ├── android03.jpg ├── android04.jpg ├── android05.jpg ├── android06.jpg ├── android07.jpg ├── android08.jpg ├── android09.jpg ├── flutter_osc_qrcode.png ├── ios-theme-01.png ├── ios-theme-02.png ├── ios-theme-03.png ├── ios01.png ├── ios02.png ├── ios03.png ├── ios04.png ├── ios05.png ├── ios06.png ├── ios07.png ├── ios08.png ├── ios09.png ├── ios10.png └── qrcode.png └── test └── widget_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | 9 | .flutter-plugins 10 | 11 | .idea/workspace.xml 12 | 13 | .idea -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/libraries/Dart_Packages.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /.idea/libraries/Dart_SDK.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/libraries/Flutter_Plugins.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/Flutter_for_Android.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations/main_dart.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.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: 5ab9e70727d858def3a586db7fb98ee580352957 8 | channel: beta 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 yubo_725 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FlutterOSC 2 | - 基于Google Flutter的开源中国客户端,支持Android和iOS。 3 | - [关于Google Flutter](https://flutter.io/)。 4 | 5 | **注意!项目不再维护,仅供Flutter初学者学习交流,且该项目使用的Flutter版本和Dart版本都较老,某些语法可能已经过时。** 6 | 7 | # Android扫码下载APK 8 | - 请使用手机浏览器扫码下载,不要使用微信或者qq扫码 9 | - 10 | 11 | # 功能 12 | - [x] 登录(使用osc账号) 13 | - [x] 查看资讯(未登录即可查看) 14 | - [x] 查看、回复、发表、评论动弹(需要登录) 15 | - [x] 动弹小黑屋(需要登录) 16 | - [x] “发现”部分的功能基本上都是用H5实现 17 | - [x] 资讯列表、动弹列表、评论列表支持下拉刷新或分页加载 18 | - [x] 支持主题切换(入口在侧滑菜单-设置-切换主题) 19 | - [ ] 动弹中的图片预览暂未实现 20 | - [ ] 摇一摇、“我的”页面功能暂时没完成 21 | 22 | # 说明 23 | 1. ~~由于开源中国的openapi只提供了基于webview或浏览器的oauth认证方式,故该项目登录界面使用webview加载OSChina三方认证页面,请放心使用开源中国的账号和密码登录~~目前该项目支持原生登录页面和WebView方式登录 24 | 2. 受开源中国openapi的限制,获取动弹数据需要登录,由于openapi的资讯接口提供的数据比较简单,故资讯部分采用python爬取的网页数据 25 | 3. 本项目纯属个人兴趣爱好而写,不是开源中国官方版本,源代码仅供学习交流,实际使用请下载安装开源中国[官方APP](https://www.oschina.net/app) 26 | 27 | # 截图 28 | #### iOS 29 |
30 | 31 | 32 | 33 |
34 |
35 | 36 | 37 | 38 |
39 |
40 | 41 | 42 | 43 |
44 |
45 | 46 | 47 | 48 |
49 | 50 | #### Android 51 |
52 | 53 | 54 | 55 |
56 |
57 | 58 | 59 | 60 |
61 |
62 | 63 | 64 | 65 |
66 | 67 | # LICENSE 68 | The MIT License (MIT) 69 | 70 | Copyright (c) 2018 yubo_725 71 | 72 | Permission is hereby granted, free of charge, to any person obtaining a copy of 73 | this software and associated documentation files (the "Software"), to deal in 74 | the Software without restriction, including without limitation the rights to 75 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 76 | the Software, and to permit persons to whom the Software is furnished to do so, 77 | subject to the following conditions: 78 | 79 | The above copyright notice and this permission notice shall be included in all 80 | copies or substantial portions of the Software. 81 | 82 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 83 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 84 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 85 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 86 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 87 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 88 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.class 3 | .gradle 4 | /local.properties 5 | /.idea/workspace.xml 6 | /.idea/libraries 7 | .DS_Store 8 | /build 9 | /captures 10 | GeneratedPluginRegistrant.java 11 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | def keystorePropertiesFile = rootProject.file("key.properties") 28 | def keystoreProperties = new Properties() 29 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 30 | 31 | android { 32 | compileSdkVersion 27 33 | 34 | lintOptions { 35 | disable 'InvalidPackage' 36 | } 37 | 38 | defaultConfig { 39 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 40 | applicationId "com.yubo.flutterosc" 41 | minSdkVersion 16 42 | targetSdkVersion 27 43 | versionCode flutterVersionCode.toInteger() 44 | versionName flutterVersionName 45 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 46 | } 47 | 48 | buildTypes { 49 | release { 50 | // TODO: Add your own signing config for the release build. 51 | // Signing with the debug keys for now, so `flutter run --release` works. 52 | signingConfig signingConfigs.debug 53 | } 54 | } 55 | 56 | signingConfigs { 57 | release { 58 | keyAlias keystoreProperties['keyAlias'] 59 | keyPassword keystoreProperties['keyPassword'] 60 | storeFile file(keystoreProperties['storeFile']) 61 | storePassword keystoreProperties['storePassword'] 62 | } 63 | } 64 | } 65 | 66 | flutter { 67 | source '../..' 68 | } 69 | 70 | dependencies { 71 | testImplementation 'junit:junit:4.12' 72 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 73 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 74 | } 75 | -------------------------------------------------------------------------------- /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/yubo/flutterosc/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.yubo.flutterosc; 2 | 3 | import android.os.Build; 4 | import android.os.Bundle; 5 | 6 | import io.flutter.app.FlutterActivity; 7 | import io.flutter.plugins.GeneratedPluginRegistrant; 8 | 9 | public class MainActivity extends FlutterActivity { 10 | @Override 11 | protected void onCreate(Bundle savedInstanceState) { 12 | super.onCreate(savedInstanceState); 13 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 14 | getWindow().setStatusBarColor(0); 15 | } 16 | GeneratedPluginRegistrant.registerWith(this); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /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/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/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.1.2' 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.enableAapt2=false -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/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.4-all.zip 7 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /android/key.properties: -------------------------------------------------------------------------------- 1 | storePassword=flutter 2 | keyPassword=flutter 3 | keyAlias=key 4 | storeFile=/Users/yubo/key.jks -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /apk/app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/apk/app-release.apk -------------------------------------------------------------------------------- /flutter_osc.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /flutter_osc_android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /images/cover_img.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/cover_img.jpg -------------------------------------------------------------------------------- /images/ic_add_pics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_add_pics.png -------------------------------------------------------------------------------- /images/ic_arrow_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_arrow_right.png -------------------------------------------------------------------------------- /images/ic_avatar_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_avatar_default.png -------------------------------------------------------------------------------- /images/ic_comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_comment.png -------------------------------------------------------------------------------- /images/ic_discover_gist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_discover_gist.png -------------------------------------------------------------------------------- /images/ic_discover_git.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_discover_git.png -------------------------------------------------------------------------------- /images/ic_discover_nearby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_discover_nearby.png -------------------------------------------------------------------------------- /images/ic_discover_pos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_discover_pos.png -------------------------------------------------------------------------------- /images/ic_discover_scan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_discover_scan.png -------------------------------------------------------------------------------- /images/ic_discover_shake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_discover_shake.png -------------------------------------------------------------------------------- /images/ic_discover_softwares.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_discover_softwares.png -------------------------------------------------------------------------------- /images/ic_eye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_eye.png -------------------------------------------------------------------------------- /images/ic_hongshu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_hongshu.jpg -------------------------------------------------------------------------------- /images/ic_house.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_house.png -------------------------------------------------------------------------------- /images/ic_img_default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_img_default.jpg -------------------------------------------------------------------------------- /images/ic_logo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_logo.jpeg -------------------------------------------------------------------------------- /images/ic_my_blog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_my_blog.png -------------------------------------------------------------------------------- /images/ic_my_message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_my_message.png -------------------------------------------------------------------------------- /images/ic_my_question.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_my_question.png -------------------------------------------------------------------------------- /images/ic_my_recommend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_my_recommend.png -------------------------------------------------------------------------------- /images/ic_my_team.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_my_team.png -------------------------------------------------------------------------------- /images/ic_nav_discover_actived.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_nav_discover_actived.png -------------------------------------------------------------------------------- /images/ic_nav_discover_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_nav_discover_normal.png -------------------------------------------------------------------------------- /images/ic_nav_my_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_nav_my_normal.png -------------------------------------------------------------------------------- /images/ic_nav_my_pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_nav_my_pressed.png -------------------------------------------------------------------------------- /images/ic_nav_news_actived.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_nav_news_actived.png -------------------------------------------------------------------------------- /images/ic_nav_news_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_nav_news_normal.png -------------------------------------------------------------------------------- /images/ic_nav_tweet_actived.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_nav_tweet_actived.png -------------------------------------------------------------------------------- /images/ic_nav_tweet_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_nav_tweet_normal.png -------------------------------------------------------------------------------- /images/ic_osc_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_osc_logo.png -------------------------------------------------------------------------------- /images/ic_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/ic_test.png -------------------------------------------------------------------------------- /images/leftmenu/dizzy_face.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/leftmenu/dizzy_face.png -------------------------------------------------------------------------------- /images/leftmenu/ic_about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/leftmenu/ic_about.png -------------------------------------------------------------------------------- /images/leftmenu/ic_fabu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/leftmenu/ic_fabu.png -------------------------------------------------------------------------------- /images/leftmenu/ic_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/leftmenu/ic_settings.png -------------------------------------------------------------------------------- /images/leftmenu/ic_xiaoheiwu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/leftmenu/ic_xiaoheiwu.png -------------------------------------------------------------------------------- /images/leftmenu/sob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/images/leftmenu/sob.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 | - barcode_scan (0.0.1): 3 | - Flutter 4 | - MTBBarcodeScanner 5 | - Flutter (1.0.0) 6 | - flutter_webview_plugin (0.0.1): 7 | - Flutter 8 | - image_picker (0.0.1): 9 | - Flutter 10 | - MTBBarcodeScanner (5.0.6) 11 | - shared_preferences (0.0.1): 12 | - Flutter 13 | 14 | DEPENDENCIES: 15 | - barcode_scan (from `.symlinks/plugins/barcode_scan/ios`) 16 | - Flutter (from `.symlinks/flutter/ios`) 17 | - flutter_webview_plugin (from `.symlinks/plugins/flutter_webview_plugin/ios`) 18 | - image_picker (from `.symlinks/plugins/image_picker/ios`) 19 | - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) 20 | 21 | SPEC REPOS: 22 | https://github.com/cocoapods/specs.git: 23 | - MTBBarcodeScanner 24 | 25 | EXTERNAL SOURCES: 26 | barcode_scan: 27 | :path: ".symlinks/plugins/barcode_scan/ios" 28 | Flutter: 29 | :path: ".symlinks/flutter/ios" 30 | flutter_webview_plugin: 31 | :path: ".symlinks/plugins/flutter_webview_plugin/ios" 32 | image_picker: 33 | :path: ".symlinks/plugins/image_picker/ios" 34 | shared_preferences: 35 | :path: ".symlinks/plugins/shared_preferences/ios" 36 | 37 | SPEC CHECKSUMS: 38 | barcode_scan: 33f586d02270046fc6559135038b34b5754eaa4f 39 | Flutter: 58dd7d1b27887414a370fcccb9e645c08ffd7a6a 40 | flutter_webview_plugin: ed9e8a6a96baf0c867e90e1bce2673913eeac694 41 | image_picker: a211f28b95a560433c00f5cd3773f4710a20404d 42 | MTBBarcodeScanner: bb0bb62e18b57f8a571a482248adc20722b70a91 43 | shared_preferences: 1feebfa37bb57264736e16865e7ffae7fc99b523 44 | 45 | PODFILE CHECKSUM: 1e5af4103afd21ca5ead147d7b81d06f494f51a2 46 | 47 | COCOAPODS: 1.7.0.beta.3 48 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Original 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/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/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/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/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/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/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/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/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/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/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/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/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/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/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/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/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/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/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/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/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | flutter_osc 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/api/Api.dart: -------------------------------------------------------------------------------- 1 | class Api { 2 | static final String host = "https://www.oschina.net"; 3 | 4 | // 资讯列表 5 | static final String newsList = "http://osc.yubo.me/news/list"; 6 | 7 | // 资讯详情 8 | static final String newsDetail = host + "/action/openapi/news_detail"; 9 | 10 | // 动弹列表 11 | static final String tweetsList = host + "/action/openapi/tweet_list"; 12 | 13 | // 评论列表 14 | static final String commentList = host + "/action/openapi/comment_list"; 15 | 16 | // 评论回复 17 | static final String commentReply = host + "/action/openapi/comment_reply"; 18 | 19 | // 获取用户信息 20 | static final String userInfo = host + "/action/openapi/user"; 21 | 22 | // 发布动弹 23 | static final String pubTweet = host + "/action/openapi/tweet_pub"; 24 | 25 | // 添加到小黑屋 26 | static final String addToBlack = "http://osc.yubo.me/black/add"; 27 | 28 | // 查询小黑屋 29 | static final String queryBlack = "http://osc.yubo.me/black/query"; 30 | 31 | // 从小黑屋中删除 32 | static final String deleteBlack = "http://osc.yubo.me/black/delete"; 33 | 34 | // 开源活动 35 | static final String eventList = "http://osc.yubo.me/events/"; 36 | } -------------------------------------------------------------------------------- /lib/constants/Constants.dart: -------------------------------------------------------------------------------- 1 | import 'package:event_bus/event_bus.dart'; 2 | 3 | class Constants { 4 | 5 | static final String redirectUrl = "http://osc.yubo.me/logincallback"; 6 | 7 | static final String loginUrl = "https://www.oschina.net/action/oauth2/authorize?client_id=4rWcDXCNTV5gMWxtagxI&response_type=code&redirect_uri=" + redirectUrl; 8 | 9 | static final String oscClientID = "4rWcDXCNTV5gMWxtagxI"; 10 | 11 | static final String endLineTag = "COMPLETE"; 12 | 13 | static final EventBus eventBus = new EventBus(); 14 | } -------------------------------------------------------------------------------- /lib/events/ChangeThemeEvent.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ChangeThemeEvent { 4 | 5 | Color color; 6 | 7 | ChangeThemeEvent(this.color); 8 | } -------------------------------------------------------------------------------- /lib/events/LoginEvent.dart: -------------------------------------------------------------------------------- 1 | 2 | class LoginEvent { 3 | 4 | } -------------------------------------------------------------------------------- /lib/events/LogoutEvent.dart: -------------------------------------------------------------------------------- 1 | 2 | class LogoutEvent { 3 | 4 | } -------------------------------------------------------------------------------- /lib/events/SlideEvent.dart: -------------------------------------------------------------------------------- 1 | 2 | class SlideEvent { 3 | int index; 4 | SlideEvent(this.index); 5 | } -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_osc/constants/Constants.dart'; 4 | import 'package:flutter_osc/events/ChangeThemeEvent.dart'; 5 | import 'package:flutter_osc/util/DataUtils.dart'; 6 | import 'package:flutter_osc/util/ThemeUtils.dart'; 7 | import 'pages/NewsListPage.dart'; 8 | import 'pages/TweetsListPage.dart'; 9 | import 'pages/DiscoveryPage.dart'; 10 | import 'pages/MyInfoPage.dart'; 11 | import './widgets/MyDrawer.dart'; 12 | 13 | void main() { 14 | runApp(MyOSCClient()); 15 | } 16 | 17 | class MyOSCClient extends StatefulWidget { 18 | @override 19 | State createState() => MyOSCClientState(); 20 | } 21 | 22 | class MyOSCClientState extends State { 23 | final appBarTitles = ['资讯', '动弹', '发现', '我的']; 24 | final tabTextStyleSelected = TextStyle(color: const Color(0xff63ca6c)); 25 | final tabTextStyleNormal = TextStyle(color: const Color(0xff969696)); 26 | 27 | Color themeColor = ThemeUtils.currentColorTheme; 28 | int _tabIndex = 0; 29 | 30 | var tabImages; 31 | var _body; 32 | var pages; 33 | 34 | Image getTabImage(path) { 35 | return Image.asset(path, width: 20.0, height: 20.0); 36 | } 37 | 38 | @override 39 | void initState() { 40 | super.initState(); 41 | DataUtils.getColorThemeIndex().then((index) { 42 | print('color theme index = $index'); 43 | if (index != null) { 44 | ThemeUtils.currentColorTheme = ThemeUtils.supportColors[index]; 45 | Constants.eventBus.fire(ChangeThemeEvent(ThemeUtils.supportColors[index])); 46 | } 47 | }); 48 | Constants.eventBus.on().listen((event) { 49 | setState(() { 50 | themeColor = event.color; 51 | }); 52 | }); 53 | pages = [NewsListPage(), TweetsListPage(), DiscoveryPage(), MyInfoPage()]; 54 | if (tabImages == null) { 55 | tabImages = [ 56 | [ 57 | getTabImage('images/ic_nav_news_normal.png'), 58 | getTabImage('images/ic_nav_news_actived.png') 59 | ], 60 | [ 61 | getTabImage('images/ic_nav_tweet_normal.png'), 62 | getTabImage('images/ic_nav_tweet_actived.png') 63 | ], 64 | [ 65 | getTabImage('images/ic_nav_discover_normal.png'), 66 | getTabImage('images/ic_nav_discover_actived.png') 67 | ], 68 | [ 69 | getTabImage('images/ic_nav_my_normal.png'), 70 | getTabImage('images/ic_nav_my_pressed.png') 71 | ] 72 | ]; 73 | } 74 | } 75 | 76 | TextStyle getTabTextStyle(int curIndex) { 77 | if (curIndex == _tabIndex) { 78 | return tabTextStyleSelected; 79 | } 80 | return tabTextStyleNormal; 81 | } 82 | 83 | Image getTabIcon(int curIndex) { 84 | if (curIndex == _tabIndex) { 85 | return tabImages[curIndex][1]; 86 | } 87 | return tabImages[curIndex][0]; 88 | } 89 | 90 | Text getTabTitle(int curIndex) { 91 | return Text(appBarTitles[curIndex], style: getTabTextStyle(curIndex)); 92 | } 93 | 94 | @override 95 | Widget build(BuildContext context) { 96 | _body = IndexedStack( 97 | children: pages, 98 | index: _tabIndex, 99 | ); 100 | return MaterialApp( 101 | theme: ThemeData( 102 | primaryColor: themeColor 103 | ), 104 | home: Scaffold( 105 | appBar: AppBar( 106 | title: Text(appBarTitles[_tabIndex], 107 | style: TextStyle(color: Colors.white)), 108 | iconTheme: IconThemeData(color: Colors.white) 109 | ), 110 | body: _body, 111 | bottomNavigationBar: CupertinoTabBar( 112 | items: [ 113 | BottomNavigationBarItem( 114 | icon: getTabIcon(0), 115 | title: getTabTitle(0)), 116 | BottomNavigationBarItem( 117 | icon: getTabIcon(1), 118 | title: getTabTitle(1)), 119 | BottomNavigationBarItem( 120 | icon: getTabIcon(2), 121 | title: getTabTitle(2)), 122 | BottomNavigationBarItem( 123 | icon: getTabIcon(3), 124 | title: getTabTitle(3)), 125 | ], 126 | currentIndex: _tabIndex, 127 | onTap: (index) { 128 | setState((){ 129 | _tabIndex = index; 130 | }); 131 | }, 132 | ), 133 | drawer: MyDrawer() 134 | ), 135 | ); 136 | } 137 | } 138 | 139 | -------------------------------------------------------------------------------- /lib/model/UserInfo.dart: -------------------------------------------------------------------------------- 1 | 2 | // 用户信息 3 | class UserInfo { 4 | 5 | String gender; 6 | String name; 7 | String location; 8 | num id; 9 | String avatar; 10 | String email; 11 | String url; 12 | 13 | UserInfo({this.id, this.name, this.gender, this.avatar, this.email, this.location, this.url}); 14 | 15 | } -------------------------------------------------------------------------------- /lib/pages/AboutPage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'CommonWebPage.dart'; 3 | 4 | // "关于"页面 5 | 6 | class AboutPage extends StatefulWidget { 7 | @override 8 | State createState() { 9 | return AboutPageState(); 10 | } 11 | } 12 | 13 | class AboutPageState extends State { 14 | bool showImage = false; 15 | TextStyle textStyle = TextStyle( 16 | color: Colors.blue, 17 | decoration: TextDecoration.combine([TextDecoration.underline])); 18 | Widget authorLink, maYunLink, githubLink; 19 | List urls = List(); 20 | List titles = List(); 21 | 22 | AboutPageState() { 23 | titles.add("yubo's blog"); 24 | titles.add("码云"); 25 | titles.add("GitHub"); 26 | urls.add("https://yubo725.top"); 27 | urls.add("https://gitee.com/yubo725"); 28 | urls.add("https://github.com/yubo725"); 29 | authorLink = GestureDetector( 30 | child: Container( 31 | margin: const EdgeInsets.fromLTRB(0.0, 80.0, 0.0, 0.0), 32 | child: Row( 33 | mainAxisAlignment: MainAxisAlignment.center, 34 | children: [ 35 | Text("作者:"), 36 | Text( 37 | "yubo", 38 | style: textStyle, 39 | ), 40 | ], 41 | ), 42 | ), 43 | onTap: getLink(0), 44 | ); 45 | maYunLink = GestureDetector( 46 | child: Container( 47 | margin: const EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0), 48 | child: Row( 49 | mainAxisAlignment: MainAxisAlignment.center, 50 | children: [ 51 | Text("码云:"), 52 | Text( 53 | "https://gitee.com/yubo725", 54 | style: textStyle, 55 | ) 56 | ], 57 | ), 58 | ), 59 | onTap: getLink(1), 60 | ); 61 | githubLink = GestureDetector( 62 | child: Container( 63 | margin: const EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0), 64 | child: Row( 65 | mainAxisAlignment: MainAxisAlignment.center, 66 | children: [ 67 | Text("GitHub:"), 68 | Text( 69 | "https://github.com/yubo725", 70 | style: textStyle, 71 | ), 72 | ], 73 | ), 74 | ), 75 | onTap: getLink(2), 76 | ); 77 | } 78 | 79 | getLink(index) { 80 | String url = urls[index]; 81 | String title = titles[index]; 82 | return () { 83 | Navigator.of(context).push(MaterialPageRoute(builder: (ctx) { 84 | return CommonWebPage(title: title, url: url); 85 | })); 86 | }; 87 | } 88 | 89 | Widget getImageOrBtn() { 90 | if (!showImage) { 91 | return Container( 92 | child: Center( 93 | child: InkWell( 94 | child: Container( 95 | padding: const EdgeInsets.fromLTRB(15.0, 8.0, 15.0, 8.0), 96 | child: Text("不要点我"), 97 | decoration: BoxDecoration( 98 | border: Border.all(color: Colors.black), 99 | borderRadius: BorderRadius.all(Radius.circular(5.0))), 100 | ), 101 | onTap: () { 102 | setState(() { 103 | showImage = true; 104 | }); 105 | }, 106 | ), 107 | ), 108 | ); 109 | } else { 110 | return Image.asset( 111 | './images/ic_hongshu.jpg', 112 | width: 100.0, 113 | height: 100.0, 114 | ); 115 | } 116 | } 117 | 118 | @override 119 | Widget build(BuildContext context) { 120 | return Scaffold( 121 | appBar: AppBar( 122 | title: Text("关于", style: TextStyle(color: Colors.white)), 123 | iconTheme: IconThemeData(color: Colors.white), 124 | ), 125 | body: Center( 126 | child: Column( 127 | children: [ 128 | Container( 129 | width: 1.0, 130 | height: 100.0, 131 | color: Colors.transparent, 132 | ), 133 | Image.asset( 134 | './images/ic_osc_logo.png', 135 | width: 200.0, 136 | height: 56.0, 137 | ), 138 | Text("基于Google Flutter的开源中国客户端"), 139 | authorLink, 140 | maYunLink, 141 | githubLink, 142 | Expanded(flex: 1, child: getImageOrBtn()), 143 | Container( 144 | margin: const EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 15.0), 145 | child: Text( 146 | "本项目仅供学习使用,与开源中国官方无关", 147 | style: TextStyle(fontSize: 12.0), 148 | )) 149 | ], 150 | ), 151 | )); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /lib/pages/BlackHousePage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_osc/constants/Constants.dart'; 3 | import 'package:flutter_osc/events/LoginEvent.dart'; 4 | import 'package:flutter_osc/pages/NewLoginPage.dart'; 5 | import 'package:flutter_osc/util/BlackListUtils.dart'; 6 | import 'package:shared_preferences/shared_preferences.dart'; 7 | import '../util/NetUtils.dart'; 8 | import '../api/Api.dart'; 9 | import 'dart:convert'; 10 | import '../pages/LoginPage.dart'; 11 | import '../util/DataUtils.dart'; 12 | import '../util/Utf8Utils.dart'; 13 | 14 | class BlackHousePage extends StatefulWidget { 15 | @override 16 | State createState() { 17 | return BlackHousePageState(); 18 | } 19 | } 20 | 21 | class BlackHousePageState extends State { 22 | bool isLogin = true; 23 | List blackDataList; 24 | TextStyle btnStyle = TextStyle(color: Colors.white, fontSize: 12.0); 25 | 26 | BlackHousePageState() { 27 | queryBlackList(); 28 | } 29 | 30 | queryBlackList() { 31 | DataUtils.getUserInfo().then((userInfo) { 32 | if (userInfo != null) { 33 | String url = Api.queryBlack; 34 | url += "/${userInfo.id}"; 35 | NetUtils.get(url).then((data) { 36 | if (data != null) { 37 | var obj = json.decode(data); 38 | if (obj['code'] == 0) { 39 | setState(() { 40 | blackDataList = obj['msg']; 41 | }); 42 | } 43 | } 44 | }); 45 | } else { 46 | setState(() { 47 | isLogin = false; 48 | }); 49 | } 50 | }); 51 | } 52 | 53 | // 获取用户信息 54 | getUserInfo() async { 55 | SharedPreferences sp = await SharedPreferences.getInstance(); 56 | String accessToken = sp.get(DataUtils.SP_AC_TOKEN); 57 | Map params = Map(); 58 | params['access_token'] = accessToken; 59 | NetUtils.get(Api.userInfo, params: params).then((data) { 60 | if (data != null) { 61 | var map = json.decode(data); 62 | DataUtils.saveUserInfo(map).then((userInfo) { 63 | queryBlackList(); 64 | }); 65 | } 66 | }); 67 | } 68 | 69 | // 从黑名单中删除 70 | deleteFromBlack(authorId) { 71 | DataUtils.getUserInfo().then((userInfo) { 72 | if (userInfo != null) { 73 | String userId = "${userInfo.id}"; 74 | Map params = Map(); 75 | params['userid'] = userId; 76 | params['authorid'] = "$authorId"; 77 | NetUtils.get(Api.deleteBlack, params: params).then((data) { 78 | Navigator.of(context).pop(); 79 | if (data != null) { 80 | var obj = json.decode(data); 81 | if (obj['code'] == 0) { 82 | // 删除成功 83 | BlackListUtils.removeBlackId(authorId); 84 | queryBlackList(); 85 | } else { 86 | showResultDialog("操作失败:${obj['msg']}"); 87 | } 88 | } 89 | }).catchError((e) { 90 | Navigator.of(context).pop(); 91 | showResultDialog("网络请求失败:$e"); 92 | }); 93 | } 94 | }); 95 | } 96 | 97 | showResultDialog(String msg) { 98 | showDialog( 99 | context: context, 100 | builder: (ctx) { 101 | return AlertDialog( 102 | title: Text('提示'), 103 | content: Text(msg), 104 | actions: [ 105 | FlatButton( 106 | child: Text( 107 | '确定', 108 | style: TextStyle(color: Colors.red), 109 | ), 110 | onPressed: () { 111 | Navigator.of(context).pop(); 112 | }, 113 | ) 114 | ], 115 | ); 116 | } 117 | ); 118 | } 119 | 120 | showSetFreeDialog(item) { 121 | String name = Utf8Utils.decode(item['authorname']); 122 | showDialog( 123 | context: context, 124 | builder: (BuildContext ctx) { 125 | return AlertDialog( 126 | title: Text('提示'), 127 | content: Text('确定要把\"$name\"放出小黑屋吗?'), 128 | actions: [ 129 | FlatButton( 130 | child: Text( 131 | '确定', 132 | style: TextStyle(color: Colors.red), 133 | ), 134 | onPressed: () { 135 | deleteFromBlack(item['authorid']); 136 | }, 137 | ) 138 | ], 139 | ); 140 | }); 141 | } 142 | 143 | Widget getBody() { 144 | if (!isLogin) { 145 | return Center( 146 | child: InkWell( 147 | child: Container( 148 | padding: const EdgeInsets.fromLTRB(15.0, 8.0, 15.0, 8.0), 149 | child: Text("去登录"), 150 | decoration: BoxDecoration( 151 | border: Border.all(color: Colors.black), 152 | borderRadius: BorderRadius.all(Radius.circular(5.0)) 153 | ), 154 | ), 155 | onTap: () async { 156 | final result = await Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) { 157 | return NewLoginPage(); 158 | })); 159 | if (result != null && result == "refresh") { 160 | // 通知动弹页面刷新 161 | Constants.eventBus.fire(LoginEvent()); 162 | getUserInfo(); 163 | } 164 | }, 165 | ), 166 | ); 167 | } 168 | if (blackDataList == null) { 169 | return Center( 170 | child: CircularProgressIndicator(), 171 | ); 172 | } else if (blackDataList.length == 0) { 173 | return Center( 174 | child: Column( 175 | mainAxisAlignment: MainAxisAlignment.center, 176 | children: [ 177 | Text("小黑屋中没人..."), 178 | Text("长按动弹列表即可往小黑屋中加人") 179 | ], 180 | ), 181 | ); 182 | } 183 | return GridView.count( 184 | crossAxisCount: 3, 185 | children: List.generate(blackDataList.length, (index) { 186 | String name = Utf8Utils.decode(blackDataList[index]['authorname']); 187 | return Container( 188 | margin: const EdgeInsets.all(2.0), 189 | color: Colors.black, 190 | child: Column( 191 | mainAxisAlignment: MainAxisAlignment.center, 192 | children: [ 193 | Container( 194 | width: 45.0, 195 | height: 45.0, 196 | decoration: BoxDecoration( 197 | shape: BoxShape.circle, 198 | color: Colors.transparent, 199 | image: DecorationImage( 200 | image: NetworkImage( 201 | "${blackDataList[index]['authoravatar']}"), 202 | fit: BoxFit.cover), 203 | border: Border.all( 204 | color: Colors.white, 205 | width: 2.0, 206 | ), 207 | ), 208 | ), 209 | Container( 210 | margin: const EdgeInsets.fromLTRB(0.0, 5.0, 0.0, 5.0), 211 | child: 212 | Text(name, style: TextStyle(color: Colors.white)), 213 | ), 214 | InkWell( 215 | child: Container( 216 | padding: const EdgeInsets.fromLTRB(8.0, 5.0, 5.0, 8.0), 217 | child: Text( 218 | "放我出去", 219 | style: btnStyle, 220 | ), 221 | decoration: BoxDecoration( 222 | border: Border.all(color: Colors.white), 223 | borderRadius: 224 | BorderRadius.all(Radius.circular(5.0))), 225 | ), 226 | onTap: () { 227 | showSetFreeDialog(blackDataList[index]); 228 | }, 229 | ), 230 | ], 231 | ), 232 | ); 233 | }), 234 | ); 235 | } 236 | 237 | @override 238 | Widget build(BuildContext context) { 239 | return Scaffold( 240 | appBar: AppBar( 241 | title: Text("动弹小黑屋", style: TextStyle(color: Colors.white)), 242 | iconTheme: IconThemeData(color: Colors.white), 243 | ), 244 | body: Padding( 245 | padding: const EdgeInsets.fromLTRB(2.0, 4.0, 2.0, 0.0), 246 | child: getBody(), 247 | ), 248 | ); 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /lib/pages/ChangeThemePage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_osc/constants/Constants.dart'; 3 | import 'package:flutter_osc/events/ChangeThemeEvent.dart'; 4 | import 'package:flutter_osc/util/DataUtils.dart'; 5 | import 'package:flutter_osc/util/ThemeUtils.dart'; 6 | 7 | class ChangeThemePage extends StatefulWidget { 8 | @override 9 | State createState() => ChangeThemePageState(); 10 | } 11 | 12 | class ChangeThemePageState extends State { 13 | 14 | List colors = ThemeUtils.supportColors; 15 | 16 | changeColorTheme(Color c) { 17 | Constants.eventBus.fire(ChangeThemeEvent(c)); 18 | } 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return Scaffold( 23 | appBar: AppBar( 24 | title: Text('切换主题', style: TextStyle(color: Colors.white)), 25 | iconTheme: IconThemeData(color: Colors.white), 26 | ), 27 | body: Padding( 28 | padding: const EdgeInsets.all(4.0), 29 | child: GridView.count( 30 | crossAxisCount: 4, 31 | children: List.generate(colors.length, (index) { 32 | return InkWell( 33 | onTap: () { 34 | ThemeUtils.currentColorTheme = colors[index]; 35 | DataUtils.setColorTheme(index); 36 | changeColorTheme(colors[index]); 37 | }, 38 | child: Container( 39 | color: colors[index], 40 | margin: const EdgeInsets.all(3.0), 41 | ), 42 | ); 43 | }), 44 | ) 45 | ) 46 | ); 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /lib/pages/CommonWebPage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; 4 | 5 | //公共的WebView页面,需要标题和URL参数 6 | class CommonWebPage extends StatefulWidget { 7 | final String title; 8 | final String url; 9 | 10 | CommonWebPage({Key key, this.title, this.url}) : super(key: key); 11 | 12 | @override 13 | State createState() { 14 | return CommonWebPageState(); 15 | } 16 | } 17 | 18 | class CommonWebPageState extends State { 19 | bool loading = true; 20 | 21 | final flutterWebViewPlugin = FlutterWebviewPlugin(); 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | // 监听WebView的加载事件 27 | flutterWebViewPlugin.onStateChanged.listen((state) { 28 | // if (state.type == WebViewState.finishLoad) { 29 | // // 加载完成 30 | // setState(() { 31 | // loading = false; 32 | // }); 33 | // } else if (state.type == WebViewState.startLoad) { 34 | // setState(() { 35 | // loading = true; 36 | // }); 37 | // } 38 | }); 39 | flutterWebViewPlugin.onUrlChanged.listen((url) { 40 | setState(() { 41 | loading = false; 42 | }); 43 | }); 44 | } 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | List titleContent = []; 49 | titleContent.add(Text( 50 | widget.title, 51 | style: TextStyle(color: Colors.white), 52 | )); 53 | if (loading) { 54 | titleContent.add(CupertinoActivityIndicator()); 55 | } 56 | titleContent.add(Container(width: 50.0)); 57 | return WebviewScaffold( 58 | url: widget.url, 59 | appBar: AppBar( 60 | title: Row( 61 | mainAxisAlignment: MainAxisAlignment.center, 62 | children: titleContent, 63 | ), 64 | iconTheme: IconThemeData(color: Colors.white), 65 | ), 66 | withZoom: true, 67 | withLocalStorage: true, 68 | withJavascript: true, 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/pages/DiscoveryPage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'CommonWebPage.dart'; 3 | import 'OfflineActivityPage.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:barcode_scan/barcode_scan.dart'; 6 | 7 | class DiscoveryPage extends StatelessWidget { 8 | 9 | static const String TAG_START = "startDivider"; 10 | static const String TAG_END = "endDivider"; 11 | static const String TAG_CENTER = "centerDivider"; 12 | static const String TAG_BLANK = "blankDivider"; 13 | 14 | static const double IMAGE_ICON_WIDTH = 30.0; 15 | static const double ARROW_ICON_WIDTH = 16.0; 16 | 17 | final imagePaths = [ 18 | "images/ic_discover_softwares.png", 19 | "images/ic_discover_git.png", 20 | "images/ic_discover_gist.png", 21 | "images/ic_discover_scan.png", 22 | "images/ic_discover_shake.png", 23 | "images/ic_discover_nearby.png", 24 | "images/ic_discover_pos.png", 25 | ]; 26 | final titles = [ 27 | "开源软件", "码云推荐", "代码片段", "扫一扫", "摇一摇", "码云封面人物", "线下活动" 28 | ]; 29 | final rightArrowIcon = Image.asset('images/ic_arrow_right.png', width: ARROW_ICON_WIDTH, height: ARROW_ICON_WIDTH,); 30 | final titleTextStyle = TextStyle(fontSize: 16.0); 31 | final List listData = []; 32 | 33 | DiscoveryPage() { 34 | initData(); 35 | } 36 | 37 | initData() { 38 | listData.add(TAG_START); 39 | for (int i = 0; i < 3; i++) { 40 | listData.add(ListItem(title: titles[i], icon: imagePaths[i])); 41 | if (i == 2) { 42 | listData.add(TAG_END); 43 | } else { 44 | listData.add(TAG_CENTER); 45 | } 46 | } 47 | listData.add(TAG_BLANK); 48 | listData.add(TAG_START); 49 | for (int i = 3; i < 5; i++) { 50 | listData.add(ListItem(title: titles[i], icon: imagePaths[i])); 51 | if (i == 4) { 52 | listData.add(TAG_END); 53 | } else { 54 | listData.add(TAG_CENTER); 55 | } 56 | } 57 | listData.add(TAG_BLANK); 58 | listData.add(TAG_START); 59 | for (int i = 5; i < 7; i++) { 60 | listData.add(ListItem(title: titles[i], icon: imagePaths[i])); 61 | if (i == 6) { 62 | listData.add(TAG_END); 63 | } else { 64 | listData.add(TAG_CENTER); 65 | } 66 | } 67 | } 68 | 69 | Widget getIconImage(path) { 70 | return Padding( 71 | padding: const EdgeInsets.fromLTRB(0.0, 0.0, 10.0, 0.0), 72 | child: Image.asset(path, width: IMAGE_ICON_WIDTH, height: IMAGE_ICON_WIDTH), 73 | ); 74 | } 75 | 76 | renderRow(BuildContext ctx, int i) { 77 | var item = listData[i]; 78 | if (item is String) { 79 | switch (item) { 80 | case TAG_START: 81 | case TAG_END: 82 | return Divider(height: 1.0,); 83 | case TAG_CENTER: 84 | return Padding( 85 | padding: const EdgeInsets.fromLTRB(50.0, 0.0, 0.0, 0.0), 86 | child: Divider(height: 1.0,), 87 | ); 88 | case TAG_BLANK: 89 | return Container( 90 | height: 20.0, 91 | ); 92 | } 93 | } else if (item is ListItem) { 94 | var listItemContent = Padding( 95 | padding: const EdgeInsets.fromLTRB(10.0, 15.0, 10.0, 15.0), 96 | child: Row( 97 | children: [ 98 | getIconImage(item.icon), 99 | Expanded( 100 | child: Text(item.title, style: titleTextStyle,) 101 | ), 102 | rightArrowIcon 103 | ], 104 | ), 105 | ); 106 | return InkWell( 107 | onTap: () { 108 | handleListItemClick(ctx, item); 109 | }, 110 | child: listItemContent, 111 | ); 112 | } 113 | } 114 | 115 | void handleListItemClick(BuildContext ctx, ListItem item) { 116 | String title = item.title; 117 | if (title == "扫一扫") { 118 | scan(); 119 | } else if (title == "线下活动") { 120 | Navigator.of(ctx).push(MaterialPageRoute( 121 | builder: (context) { 122 | return OfflineActivityPage(); 123 | } 124 | )); 125 | } else if (title == "码云推荐") { 126 | Navigator.of(ctx).push(MaterialPageRoute( 127 | builder: (context) { 128 | return CommonWebPage(title: "码云推荐", url: "https://m.gitee.com/explore"); 129 | } 130 | )); 131 | } else if (title == "代码片段") { 132 | Navigator.of(ctx).push(MaterialPageRoute( 133 | builder: (context) { 134 | return CommonWebPage(title: "代码片段", url: "https://m.gitee.com/gists"); 135 | } 136 | )); 137 | } else if (title == "开源软件") { 138 | Navigator.of(ctx).push(MaterialPageRoute( 139 | builder: (context) { 140 | return CommonWebPage(title: "开源软件", url: "https://m.gitee.com/explore"); 141 | } 142 | )); 143 | } else if (title == "码云封面人物") { 144 | Navigator.of(ctx).push(MaterialPageRoute( 145 | builder: (context) { 146 | return CommonWebPage(title: "码云封面人物", url: "https://m.gitee.com/gitee-stars/"); 147 | } 148 | )); 149 | } 150 | } 151 | 152 | Future scan() async { 153 | try { 154 | String barcode = await BarcodeScanner.scan(); 155 | print(barcode); 156 | } on Exception catch (e) { 157 | print(e); 158 | } 159 | } 160 | 161 | @override 162 | Widget build(BuildContext context) { 163 | return Padding( 164 | padding: const EdgeInsets.fromLTRB(0.0, 20.0, 0.0, 0.0), 165 | child: ListView.builder( 166 | itemCount: listData.length, 167 | itemBuilder: (context, i) => renderRow(context, i), 168 | ), 169 | ); 170 | } 171 | 172 | } 173 | 174 | class ListItem { 175 | String icon; 176 | String title; 177 | ListItem({this.icon, this.title}); 178 | } -------------------------------------------------------------------------------- /lib/pages/LoginPage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | 4 | import 'package:flutter/cupertino.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; 7 | 8 | import '../constants/Constants.dart'; 9 | import '../util/DataUtils.dart'; 10 | 11 | // 登录页面,使用网页加载的开源中国三方登录页面 12 | class LoginPage extends StatefulWidget { 13 | @override 14 | State createState() => LoginPageState(); 15 | } 16 | 17 | class LoginPageState extends State { 18 | // 标记是否是加载中 19 | bool loading = true; 20 | // 标记当前页面是否是我们自定义的回调页面 21 | bool isLoadingCallbackPage = false; 22 | // 是否解析了结果,这个字段用于确保parseResult方法只调用一次,真是MMP噢,不用这个字段标记,parseResult就调用了两次,导致黑屏产生,查黑屏问题用了我一天时间! 23 | bool parsedResult = false; 24 | GlobalKey _scaffoldKey = GlobalKey(); 25 | // URL变化监听器 26 | StreamSubscription _onUrlChanged; 27 | // WebView加载状态变化监听器 28 | StreamSubscription _onStateChanged; 29 | // 插件提供的对象,该对象用于WebView的各种操作 30 | FlutterWebviewPlugin flutterWebViewPlugin = FlutterWebviewPlugin(); 31 | 32 | @override 33 | void initState() { 34 | super.initState(); 35 | // 监听WebView的加载事件,该监听器已不起作用,不回调 36 | _onStateChanged = flutterWebViewPlugin.onStateChanged.listen((WebViewStateChanged state) { 37 | // state.type是一个枚举类型,取值有:WebViewState.shouldStart, WebViewState.startLoad, WebViewState.finishLoad 38 | switch (state.type) { 39 | case WebViewState.shouldStart: 40 | // 准备加载 41 | setState(() { 42 | loading = true; 43 | }); 44 | break; 45 | case WebViewState.startLoad: 46 | // 开始加载 47 | break; 48 | case WebViewState.finishLoad: 49 | // 加载完成 50 | setState(() { 51 | loading = false; 52 | }); 53 | if (isLoadingCallbackPage) { 54 | // 当前是回调页面,则调用js方法获取数据 55 | parseResult(); 56 | } 57 | break; 58 | case WebViewState.abortLoad: 59 | break; 60 | } 61 | }); 62 | _onUrlChanged = flutterWebViewPlugin.onUrlChanged.listen((url) { 63 | // 登录成功会跳转到自定义的回调页面,该页面地址为http://yubo725.top/osc/osc.php?code=xxx 64 | // 该页面会接收code,然后根据code换取AccessToken,并将获取到的token及其他信息,通过js的get()方法返回 65 | if (url != null && url.length > 0 && url.contains("/logincallback?")) { 66 | isLoadingCallbackPage = true; 67 | } 68 | }); 69 | } 70 | 71 | // 解析WebView中的数据 72 | void parseResult() { 73 | if (parsedResult) { 74 | return; 75 | } 76 | parsedResult = true; 77 | flutterWebViewPlugin.evalJavascript("window.atob(get())").then((result) { 78 | // result json字符串,包含token信息 79 | if (result != null && result.length > 0) { 80 | // 拿到了js中的数据 81 | try { 82 | // what the fuck?? need twice decode?? 83 | var map = json.decode(result); // s is String 84 | if (map is String) { 85 | map = json.decode(map); // map is Map 86 | } 87 | if (map != null) { 88 | // 登录成功,取到了token,关闭当前页面 89 | DataUtils.saveLoginInfo(map); 90 | Navigator.pop(context, "refresh"); 91 | } 92 | } catch (e) { 93 | print(e); 94 | } 95 | } 96 | }); 97 | } 98 | 99 | @override 100 | Widget build(BuildContext context) { 101 | List titleContent = []; 102 | titleContent.add(Text( 103 | "登录开源中国", 104 | style: TextStyle(color: Colors.white), 105 | )); 106 | if (loading) { 107 | // 如果还在加载中,就在标题栏上显示一个圆形进度条 108 | titleContent.add(CupertinoActivityIndicator()); 109 | } 110 | titleContent.add(Container(width: 50.0)); 111 | // WebviewScaffold是插件提供的组件,用于在页面上显示一个WebView并加载URL 112 | return WebviewScaffold( 113 | key: _scaffoldKey, 114 | url: Constants.loginUrl, // 登录的URL 115 | appBar: AppBar( 116 | title: Row( 117 | mainAxisAlignment: MainAxisAlignment.center, 118 | children: titleContent, 119 | ), 120 | iconTheme: IconThemeData(color: Colors.white), 121 | ), 122 | withZoom: true, // 允许网页缩放 123 | withLocalStorage: true, // 允许LocalStorage 124 | withJavascript: true, // 允许执行js代码 125 | ); 126 | } 127 | 128 | @override 129 | void dispose() { 130 | // 回收相关资源 131 | // Every listener should be canceled, the same should be done with this stream. 132 | _onUrlChanged.cancel(); 133 | _onStateChanged.cancel(); 134 | flutterWebViewPlugin.dispose(); 135 | 136 | super.dispose(); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /lib/pages/MyInfoPage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_osc/constants/Constants.dart'; 3 | import 'package:flutter_osc/events/ChangeThemeEvent.dart'; 4 | import 'package:flutter_osc/events/LoginEvent.dart'; 5 | import 'package:flutter_osc/events/LogoutEvent.dart'; 6 | import 'package:flutter_osc/util/ThemeUtils.dart'; 7 | import '../pages/CommonWebPage.dart'; 8 | import '../pages/LoginPage.dart'; 9 | import '../pages/NewLoginPage.dart'; 10 | import 'dart:convert'; 11 | import 'package:shared_preferences/shared_preferences.dart'; 12 | import '../api/Api.dart'; 13 | import '../util/NetUtils.dart'; 14 | import '../util/DataUtils.dart'; 15 | import '../model/UserInfo.dart'; 16 | 17 | class MyInfoPage extends StatefulWidget { 18 | @override 19 | State createState() { 20 | return MyInfoPageState(); 21 | } 22 | } 23 | 24 | class MyInfoPageState extends State { 25 | Color themeColor = ThemeUtils.currentColorTheme; 26 | 27 | static const double IMAGE_ICON_WIDTH = 30.0; 28 | static const double ARROW_ICON_WIDTH = 16.0; 29 | 30 | var titles = ["我的消息", "阅读记录", "我的博客", "我的问答", "我的活动", "我的团队", "邀请好友"]; 31 | var imagePaths = [ 32 | "images/ic_my_message.png", 33 | "images/ic_my_blog.png", 34 | "images/ic_my_blog.png", 35 | "images/ic_my_question.png", 36 | "images/ic_discover_pos.png", 37 | "images/ic_my_team.png", 38 | "images/ic_my_recommend.png" 39 | ]; 40 | var icons = []; 41 | var userAvatar; 42 | var userName; 43 | var titleTextStyle = TextStyle(fontSize: 16.0); 44 | var rightArrowIcon = Image.asset( 45 | 'images/ic_arrow_right.png', 46 | width: ARROW_ICON_WIDTH, 47 | height: ARROW_ICON_WIDTH, 48 | ); 49 | 50 | MyInfoPageState() { 51 | for (int i = 0; i < imagePaths.length; i++) { 52 | icons.add(getIconImage(imagePaths[i])); 53 | } 54 | } 55 | 56 | @override 57 | void initState() { 58 | super.initState(); 59 | _showUserInfo(); 60 | Constants.eventBus.on().listen((event) { 61 | // 收到退出登录的消息,刷新个人信息显示 62 | _showUserInfo(); 63 | }); 64 | Constants.eventBus.on().listen((event) { 65 | // 收到登录的消息,重新获取个人信息 66 | getUserInfo(); 67 | }); 68 | Constants.eventBus.on().listen((event) { 69 | setState(() { 70 | themeColor = event.color; 71 | }); 72 | }); 73 | } 74 | 75 | _showUserInfo() { 76 | DataUtils.getUserInfo().then((UserInfo userInfo) { 77 | if (userInfo != null) { 78 | print(userInfo.name); 79 | print(userInfo.avatar); 80 | setState(() { 81 | userAvatar = userInfo.avatar; 82 | userName = userInfo.name; 83 | }); 84 | } else { 85 | setState(() { 86 | userAvatar = null; 87 | userName = null; 88 | }); 89 | } 90 | }); 91 | } 92 | 93 | Widget getIconImage(path) { 94 | return Padding( 95 | padding: const EdgeInsets.fromLTRB(0.0, 0.0, 10.0, 0.0), 96 | child: Image.asset(path, 97 | width: IMAGE_ICON_WIDTH, height: IMAGE_ICON_WIDTH), 98 | ); 99 | } 100 | 101 | @override 102 | Widget build(BuildContext context) { 103 | var listView = ListView.builder( 104 | itemCount: titles.length * 2, 105 | itemBuilder: (context, i) => renderRow(i), 106 | ); 107 | return listView; 108 | } 109 | 110 | // 获取用户信息 111 | getUserInfo() async { 112 | try { 113 | SharedPreferences sp = await SharedPreferences.getInstance(); 114 | String accessToken = sp.get(DataUtils.SP_AC_TOKEN); 115 | Map params = Map(); 116 | params['access_token'] = accessToken; 117 | NetUtils.get(Api.userInfo, params: params).then((data) { 118 | if (data != null) { 119 | var map = json.decode(data); 120 | setState(() { 121 | userAvatar = map['avatar']; 122 | userName = map['name']; 123 | }); 124 | DataUtils.saveUserInfo(map); 125 | } 126 | }); 127 | } catch (e) { 128 | print(e); 129 | } 130 | } 131 | 132 | _login() async { 133 | // 打开登录页并处理登录成功的回调 134 | final result = await Navigator 135 | .of(context) 136 | .push(MaterialPageRoute(builder: (context) { 137 | return NewLoginPage(); 138 | })); 139 | // result为"refresh"代表登录成功 140 | if (result != null && result == "refresh") { 141 | // 刷新用户信息 142 | getUserInfo(); 143 | // 通知动弹页面刷新 144 | Constants.eventBus.fire(LoginEvent()); 145 | } 146 | } 147 | 148 | _showUserInfoDetail() {} 149 | 150 | renderRow(i) { 151 | if (i == 0) { 152 | var avatarContainer = Container( 153 | color: themeColor, 154 | height: 200.0, 155 | child: Center( 156 | child: Column( 157 | mainAxisAlignment: MainAxisAlignment.center, 158 | children: [ 159 | userAvatar == null 160 | ? Image.asset( 161 | "images/ic_avatar_default.png", 162 | width: 60.0, 163 | ) 164 | : Container( 165 | width: 60.0, 166 | height: 60.0, 167 | decoration: BoxDecoration( 168 | shape: BoxShape.circle, 169 | color: Colors.transparent, 170 | image: DecorationImage( 171 | image: NetworkImage(userAvatar), 172 | fit: BoxFit.cover), 173 | border: Border.all( 174 | color: Colors.white, 175 | width: 2.0, 176 | ), 177 | ), 178 | ), 179 | Text( 180 | userName == null ? "点击头像登录" : userName, 181 | style: TextStyle(color: Colors.white, fontSize: 16.0), 182 | ), 183 | ], 184 | ), 185 | ), 186 | ); 187 | return GestureDetector( 188 | onTap: () { 189 | DataUtils.isLogin().then((isLogin) { 190 | if (isLogin) { 191 | // 已登录,显示用户详细信息 192 | _showUserInfoDetail(); 193 | } else { 194 | // 未登录,跳转到登录页面 195 | _login(); 196 | } 197 | }); 198 | }, 199 | child: avatarContainer, 200 | ); 201 | } 202 | --i; 203 | if (i.isOdd) { 204 | return Divider( 205 | height: 1.0, 206 | ); 207 | } 208 | i = i ~/ 2; 209 | String title = titles[i]; 210 | var listItemContent = Padding( 211 | padding: const EdgeInsets.fromLTRB(10.0, 15.0, 10.0, 15.0), 212 | child: Row( 213 | children: [ 214 | icons[i], 215 | Expanded( 216 | child: Text( 217 | title, 218 | style: titleTextStyle, 219 | )), 220 | rightArrowIcon 221 | ], 222 | ), 223 | ); 224 | return InkWell( 225 | child: listItemContent, 226 | onTap: () { 227 | _handleListItemClick(title); 228 | // Navigator 229 | // .of(context) 230 | // .push(MaterialPageRoute(builder: (context) => CommonWebPage(title: "Test", url: "https://my.oschina.net/u/815261/blog"))); 231 | }, 232 | ); 233 | } 234 | 235 | _showLoginDialog() { 236 | showDialog( 237 | context: context, 238 | builder: (BuildContext ctx) { 239 | return AlertDialog( 240 | title: Text('提示'), 241 | content: Text('没有登录,现在去登录吗?'), 242 | actions: [ 243 | FlatButton( 244 | child: Text( 245 | '取消', 246 | style: TextStyle(color: Colors.red), 247 | ), 248 | onPressed: () { 249 | Navigator.of(context).pop(); 250 | }, 251 | ), 252 | FlatButton( 253 | child: Text( 254 | '确定', 255 | style: TextStyle(color: Colors.blue), 256 | ), 257 | onPressed: () { 258 | Navigator.of(context).pop(); 259 | _login(); 260 | }, 261 | ) 262 | ], 263 | ); 264 | }); 265 | } 266 | 267 | _handleListItemClick(String title) { 268 | DataUtils.isLogin().then((isLogin) { 269 | if (!isLogin) { 270 | // 未登录 271 | _showLoginDialog(); 272 | } else { 273 | DataUtils.getUserInfo().then((info) { 274 | Navigator.of(context).push( 275 | MaterialPageRoute( 276 | builder: (context) => CommonWebPage( 277 | title: "我的博客", 278 | url: "https://my.oschina.net/u/${info.id}/blog" 279 | ) 280 | )); 281 | }); 282 | } 283 | }); 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /lib/pages/NewLoginPage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_osc/constants/Constants.dart'; 6 | import 'package:flutter_osc/pages/LoginPage.dart'; 7 | import 'package:flutter_osc/util/DataUtils.dart'; 8 | import 'package:flutter_osc/util/ThemeUtils.dart'; 9 | import 'package:flutter_osc/widgets/CommonButton.dart'; 10 | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; 11 | 12 | import 'AboutPage.dart'; 13 | 14 | // 新的登录界面,隐藏WebView登录页面 15 | class NewLoginPage extends StatefulWidget { 16 | @override 17 | State createState() => NewLoginPageState(); 18 | } 19 | 20 | class NewLoginPageState extends State { 21 | // 首次加载登录页 22 | static const int stateFirstLoad = 1; 23 | // 加载完毕登录页,且当前页面是输入账号密码的页面 24 | static const int stateLoadedInputPage = 2; 25 | // 加载完毕登录页,且当前页面不是输入账号密码的页面 26 | static const int stateLoadedNotInputPage = 3; 27 | 28 | int curState = stateFirstLoad; 29 | 30 | // 标记是否是加载中 31 | bool loading = true; 32 | // 标记当前页面是否是我们自定义的回调页面 33 | bool isLoadingCallbackPage = false; 34 | // 是否正在登录 35 | bool isOnLogin = false; 36 | // 是否隐藏输入的文本 37 | bool obscureText = true; 38 | // 是否解析了结果 39 | bool parsedResult = false; 40 | 41 | final usernameCtrl = TextEditingController(text: ''); 42 | final passwordCtrl = TextEditingController(text: ''); 43 | 44 | // 检查当前是否是输入账号密码界面,返回1表示是,0表示否 45 | final scriptCheckIsInputAccountPage = "document.getElementById('f_email') != null"; 46 | 47 | final jsCtrl = TextEditingController(text: 'document.getElementById(\'f_email\') != null'); 48 | GlobalKey _scaffoldKey = GlobalKey(); 49 | 50 | // URL变化监听器 51 | StreamSubscription _onUrlChanged; 52 | // WebView加载状态变化监听器 53 | StreamSubscription _onStateChanged; 54 | // 插件提供的对象,该对象用于WebView的各种操作 55 | FlutterWebviewPlugin flutterWebViewPlugin = FlutterWebviewPlugin(); 56 | 57 | @override 58 | void initState() { 59 | super.initState(); 60 | // 监听WebView的加载事件 61 | _onStateChanged = flutterWebViewPlugin.onStateChanged.listen((WebViewStateChanged state) { 62 | // state.type是一个枚举类型,取值有:WebViewState.shouldStart, WebViewState.startLoad, WebViewState.finishLoad 63 | switch (state.type) { 64 | case WebViewState.shouldStart: 65 | // 准备加载 66 | setState(() { 67 | loading = true; 68 | }); 69 | break; 70 | case WebViewState.startLoad: 71 | // 开始加载 72 | break; 73 | case WebViewState.finishLoad: 74 | // 加载完成 75 | setState(() { 76 | loading = false; 77 | }); 78 | if (isLoadingCallbackPage) { 79 | // 当前是回调页面,则调用js方法获取数据,延迟加载防止get()获取不到数据 80 | Timer(const Duration(seconds: 1), () { 81 | parseResult(); 82 | }); 83 | return; 84 | } 85 | switch (curState) { 86 | case stateFirstLoad: 87 | case stateLoadedInputPage: 88 | // 首次加载完登录页,判断是否是输入账号密码的界面 89 | isInputPage().then((result) { 90 | if ("true".compareTo(result) == 0) { 91 | // 是输入账号的页面,则直接填入账号密码并模拟点击登录按钮 92 | // autoLogin(); 93 | } else { 94 | // 不是输入账号的页面,则需要模拟点击"换个账号"按钮 95 | redirectToInputPage(); 96 | } 97 | }); 98 | break; 99 | case stateLoadedNotInputPage: 100 | // 不是输入账号密码的界面,则需要模拟点击"换个账号"按钮 101 | break; 102 | } 103 | break; 104 | case WebViewState.abortLoad: 105 | break; 106 | } 107 | }); 108 | _onUrlChanged = flutterWebViewPlugin.onUrlChanged.listen((url) { 109 | // 登录成功会跳转到自定义的回调页面,该页面地址为http://yubo725.top/osc/osc.php?code=xxx 110 | // 该页面会接收code,然后根据code换取AccessToken,并将获取到的token及其他信息,通过js的get()方法返回 111 | if (url != null && url.length > 0 && url.contains("/logincallback?")) { 112 | isLoadingCallbackPage = true; 113 | } 114 | }); 115 | } 116 | 117 | // 检查当前WebView是否是输入账号密码的页面 118 | Future isInputPage() async { 119 | return await flutterWebViewPlugin.evalJavascript("document.getElementById('f_email') != null"); 120 | } 121 | 122 | // 跳转到输入界面 123 | redirectToInputPage() { 124 | curState = stateLoadedInputPage; 125 | String js = "document.getElementsByClassName('userbar')[0].getElementsByTagName('a')[1].click()"; 126 | flutterWebViewPlugin.evalJavascript(js); 127 | } 128 | 129 | // 自动登录 130 | void autoLogin(String account, String pwd) { 131 | setState(() { 132 | isOnLogin = true; 133 | }); 134 | // 填账号 135 | String jsInputAccount = "document.getElementById('f_email').value='$account'"; 136 | // 填密码 137 | String jsInputPwd = "document.getElementById('f_pwd').value='$pwd'"; 138 | // 点击"连接"按钮 139 | String jsClickLoginBtn = "document.getElementsByClassName('rndbutton')[0].click()"; 140 | // 执行上面3条js语句 141 | flutterWebViewPlugin.evalJavascript("$jsInputAccount;$jsInputPwd;$jsClickLoginBtn"); 142 | } 143 | 144 | // 解析WebView中的数据 145 | void parseResult() { 146 | if (parsedResult) { 147 | return; 148 | } 149 | parsedResult = true; 150 | flutterWebViewPlugin.evalJavascript("window.atob(get())").then((result) { 151 | // result json字符串,包含token信息 152 | if (result != null && result.length > 0) { 153 | // 拿到了js中的数据 154 | try { 155 | // what the fuck?? need twice decode?? 156 | var map = json.decode(result); // s is String 157 | if (map is String) { 158 | map = json.decode(map); // map is Map 159 | } 160 | if (map != null) { 161 | // 登录成功,取到了token,关闭当前页面 162 | DataUtils.saveLoginInfo(map); 163 | Navigator.pop(context, "refresh"); 164 | } 165 | } catch (e) { 166 | print("parse login result error: $e"); 167 | } 168 | } 169 | }).catchError((error) { 170 | print("get() error: $error"); 171 | }); 172 | } 173 | 174 | @override 175 | Widget build(BuildContext context) { 176 | var loginBtn = Builder(builder: (ctx) { 177 | return CommonButton(text: "登录", onTap: () { 178 | if (isOnLogin) return; 179 | // 拿到用户输入的账号密码 180 | String username = usernameCtrl.text.trim(); 181 | String password = passwordCtrl.text.trim(); 182 | if (username.isEmpty || password.isEmpty) { 183 | Scaffold.of(ctx).showSnackBar(SnackBar( 184 | content: Text("账号和密码不能为空!"), 185 | )); 186 | return; 187 | } 188 | // 关闭键盘 189 | FocusScope.of(context).requestFocus(FocusNode()); 190 | // 发送给webview,让webview登录后再取回token 191 | autoLogin(username, password); 192 | }); 193 | }); 194 | var loadingView; 195 | if (isOnLogin) { 196 | loadingView = Center( 197 | child: Padding( 198 | padding: const EdgeInsets.fromLTRB(0, 30, 0, 0), 199 | child: Column( 200 | crossAxisAlignment: CrossAxisAlignment.center, 201 | children: [ 202 | CupertinoActivityIndicator(), 203 | Text("登录中,请稍等...") 204 | ], 205 | ), 206 | ) 207 | ); 208 | } else { 209 | loadingView = Center(); 210 | } 211 | return Scaffold( 212 | appBar: AppBar( 213 | title: Text("登录", style: TextStyle(color: Colors.white)), 214 | iconTheme: IconThemeData(color: Colors.white), 215 | ), 216 | body: Container( 217 | padding: const EdgeInsets.all(10.0), 218 | child: Column( 219 | children: [ 220 | Container( 221 | width: MediaQuery.of(context).size.width, 222 | height: 0.0, 223 | child: WebviewScaffold( 224 | key: _scaffoldKey, 225 | hidden: true, 226 | url: Constants.loginUrl, // 登录的URL 227 | withZoom: true, // 允许网页缩放 228 | withLocalStorage: true, // 允许LocalStorage 229 | withJavascript: true, // 允许执行js代码 230 | ), 231 | ), 232 | Center(child: Text("请使用OSC帐号密码登录")), 233 | Container(height: 20.0), 234 | Row( 235 | crossAxisAlignment: CrossAxisAlignment.center, 236 | children: [ 237 | Text("用户名:"), 238 | Expanded(child: TextField( 239 | controller: usernameCtrl, 240 | decoration: InputDecoration( 241 | hintText: "OSC帐号/注册邮箱", 242 | hintStyle: TextStyle( 243 | color: const Color(0xFF808080) 244 | ), 245 | border: OutlineInputBorder( 246 | borderRadius: const BorderRadius.all(const Radius.circular(6.0)) 247 | ), 248 | contentPadding: const EdgeInsets.all(10.0) 249 | ), 250 | )) 251 | ], 252 | ), 253 | Container(height: 20.0), 254 | Row( 255 | crossAxisAlignment: CrossAxisAlignment.center, 256 | children: [ 257 | Text("密 码:"), 258 | Expanded(child: TextField( 259 | controller: passwordCtrl, 260 | obscureText: obscureText, 261 | decoration: InputDecoration( 262 | hintText: "登录密码", 263 | hintStyle: TextStyle( 264 | color: const Color(0xFF808080) 265 | ), 266 | border: OutlineInputBorder( 267 | borderRadius: const BorderRadius.all(const Radius.circular(6.0)) 268 | ), 269 | contentPadding: const EdgeInsets.all(10.0) 270 | ), 271 | )), 272 | InkWell( 273 | child: Container( 274 | width: 40, 275 | height: 40, 276 | alignment: Alignment.center, 277 | child: Image.asset("images/ic_eye.png", width: 20, height: 20), 278 | ), 279 | onTap: () { 280 | setState(() { 281 | obscureText = !obscureText; 282 | }); 283 | }, 284 | ) 285 | ], 286 | ), 287 | Container(height: 20.0), 288 | loginBtn, 289 | Expanded( 290 | child: Column( 291 | children: [ 292 | Expanded( 293 | child: loadingView 294 | ), 295 | // Container( 296 | // margin: const EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 10.0), 297 | // alignment: Alignment.bottomCenter, 298 | // child: InkWell( 299 | // child: Padding( 300 | // padding: const EdgeInsets.all(10.0), 301 | // child: Text("使用WebView登录方式", style: TextStyle(fontSize: 13.0, color: ThemeUtils.currentColorTheme)) 302 | // ), 303 | // onTap: () async { 304 | // // 跳转到LoginPage 305 | // final result = await Navigator.push(context, MaterialPageRoute(builder: (context) { 306 | // return LoginPage(); 307 | // })); 308 | // if (result != null && result == "refresh") { 309 | // Navigator.pop(context, "refresh"); 310 | // } 311 | // }, 312 | // ), 313 | // ), 314 | ], 315 | ) 316 | ) 317 | ], 318 | ), 319 | ) 320 | ); 321 | } 322 | 323 | @override 324 | void dispose() { 325 | // 回收相关资源 326 | // Every listener should be canceled, the same should be done with this stream. 327 | _onUrlChanged.cancel(); 328 | _onStateChanged.cancel(); 329 | flutterWebViewPlugin.dispose(); 330 | 331 | super.dispose(); 332 | } 333 | } -------------------------------------------------------------------------------- /lib/pages/NewsDetailPage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; 4 | 5 | class NewsDetailPage extends StatefulWidget { 6 | 7 | final String id; 8 | 9 | NewsDetailPage({Key key, this.id}):super(key: key); 10 | 11 | @override 12 | State createState() => NewsDetailPageState(id: this.id); 13 | } 14 | 15 | class NewsDetailPageState extends State { 16 | 17 | String id; 18 | bool loaded = false; 19 | String detailDataStr; 20 | final flutterWebViewPlugin = FlutterWebviewPlugin(); 21 | 22 | NewsDetailPageState({Key key, this.id}); 23 | 24 | @override 25 | void initState() { 26 | super.initState(); 27 | // 监听WebView的加载事件 28 | flutterWebViewPlugin.onStateChanged.listen((state) { 29 | print("state: ${state.type}"); 30 | if (state.type == WebViewState.finishLoad) { 31 | // 加载完成 32 | setState(() { 33 | loaded = true; 34 | }); 35 | } 36 | }); 37 | } 38 | 39 | @override 40 | Widget build(BuildContext context) { 41 | List titleContent = []; 42 | titleContent.add(Text("资讯详情", style: TextStyle(color: Colors.white),)); 43 | if (!loaded) { 44 | titleContent.add(CupertinoActivityIndicator()); 45 | } 46 | titleContent.add(Container(width: 50.0)); 47 | return WebviewScaffold( 48 | url: this.id, 49 | appBar: AppBar( 50 | title: Row( 51 | mainAxisAlignment: MainAxisAlignment.center, 52 | children: titleContent, 53 | ), 54 | iconTheme: IconThemeData(color: Colors.white), 55 | ), 56 | withZoom: false, 57 | withLocalStorage: true, 58 | withJavascript: true, 59 | ); 60 | } 61 | } -------------------------------------------------------------------------------- /lib/pages/NewsListPage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flutter/material.dart'; 3 | import '../util/NetUtils.dart'; 4 | import '../api/Api.dart'; 5 | import 'dart:convert'; 6 | import '../constants/Constants.dart'; 7 | import '../widgets/SlideView.dart'; 8 | import '../pages/NewsDetailPage.dart'; 9 | import '../widgets/CommonEndLine.dart'; 10 | import '../widgets/SlideViewIndicator.dart'; 11 | 12 | final slideViewIndicatorStateKey = GlobalKey(); 13 | 14 | class NewsListPage extends StatefulWidget { 15 | @override 16 | State createState() => NewsListPageState(); 17 | } 18 | 19 | class NewsListPageState extends State { 20 | final ScrollController _controller = ScrollController(); 21 | final TextStyle titleTextStyle = TextStyle(fontSize: 15.0); 22 | final TextStyle subtitleStyle = TextStyle(color: const Color(0xFFB5BDC0), fontSize: 12.0); 23 | 24 | var listData; 25 | var slideData; 26 | var curPage = 1; 27 | SlideView slideView; 28 | var listTotalSize = 0; 29 | SlideViewIndicator indicator; 30 | 31 | @override 32 | void initState() { 33 | super.initState(); 34 | _controller.addListener(() { 35 | var maxScroll = _controller.position.maxScrollExtent; 36 | var pixels = _controller.position.pixels; 37 | if (maxScroll == pixels && listData.length < listTotalSize) { 38 | // scroll to bottom, get next page data 39 | // print("load more ... "); 40 | curPage++; 41 | getNewsList(true); 42 | } 43 | }); 44 | getNewsList(false); 45 | } 46 | 47 | Future _pullToRefresh() async { 48 | curPage = 1; 49 | getNewsList(false); 50 | return null; 51 | } 52 | 53 | @override 54 | Widget build(BuildContext context) { 55 | // 无数据时,显示Loading 56 | if (listData == null) { 57 | return Center( 58 | // CircularProgressIndicator是一个圆形的Loading进度条 59 | child: CircularProgressIndicator(), 60 | ); 61 | } else { 62 | // 有数据,显示ListView 63 | Widget listView = ListView.builder( 64 | itemCount: listData.length * 2, 65 | itemBuilder: (context, i) => renderRow(i), 66 | controller: _controller, 67 | ); 68 | return RefreshIndicator(child: listView, onRefresh: _pullToRefresh); 69 | } 70 | } 71 | 72 | // 从网络获取数据,isLoadMore表示是否是加载更多数据 73 | getNewsList(bool isLoadMore) { 74 | String url = Api.newsList; 75 | url += "?pageIndex=$curPage&pageSize=10"; 76 | NetUtils.get(url).then((data) { 77 | if (data != null) { 78 | // 将接口返回的json字符串解析为map类型 79 | Map map = json.decode(data); 80 | if (map['code'] == 0) { 81 | // code=0表示请求成功 82 | var msg = map['msg']; 83 | // total表示资讯总条数 84 | listTotalSize = msg['news']['total']; 85 | // data为数据内容,其中包含slide和news两部分,分别表示头部轮播图数据,和下面的列表数据 86 | var _listData = msg['news']['data']; 87 | var _slideData = msg['slide']; 88 | setState(() { 89 | if (!isLoadMore) { 90 | // 不是加载更多,则直接为变量赋值 91 | listData = _listData; 92 | slideData = _slideData; 93 | } else { 94 | // 是加载更多,则需要将取到的news数据追加到原来的数据后面 95 | List list1 = List(); 96 | // 添加原来的数据 97 | list1.addAll(listData); 98 | // 添加新取到的数据 99 | list1.addAll(_listData); 100 | // 判断是否获取了所有的数据,如果是,则需要显示底部的"我也是有底线的"布局 101 | if (list1.length >= listTotalSize) { 102 | list1.add(Constants.endLineTag); 103 | } 104 | // 给列表数据赋值 105 | listData = list1; 106 | // 轮播图数据 107 | slideData = _slideData; 108 | } 109 | initSlider(); 110 | }); 111 | } 112 | } 113 | }); 114 | } 115 | 116 | void initSlider() { 117 | indicator = SlideViewIndicator(slideData.length, key: slideViewIndicatorStateKey); 118 | slideView = SlideView(slideData, indicator, slideViewIndicatorStateKey); 119 | } 120 | 121 | Widget renderRow(i) { 122 | if (i == 0) { 123 | return Container( 124 | height: 180.0, 125 | child: Stack( 126 | children: [ 127 | slideView, 128 | Container( 129 | alignment: Alignment.bottomCenter, 130 | child: indicator, 131 | ) 132 | ], 133 | ) 134 | ); 135 | } 136 | i -= 1; 137 | if (i.isOdd) { 138 | return Divider(height: 1.0); 139 | } 140 | i = i ~/ 2; 141 | var itemData = listData[i]; 142 | if (itemData is String && itemData == Constants.endLineTag) { 143 | return CommonEndLine(); 144 | } 145 | var titleRow = Row( 146 | children: [ 147 | Expanded( 148 | child: Text(itemData['title'], style: titleTextStyle), 149 | ) 150 | ], 151 | ); 152 | var timeRow = Row( 153 | children: [ 154 | Container( 155 | width: 20.0, 156 | height: 20.0, 157 | decoration: BoxDecoration( 158 | shape: BoxShape.circle, 159 | color: const Color(0xFFECECEC), 160 | image: DecorationImage( 161 | image: NetworkImage(itemData['authorImg']), fit: BoxFit.cover), 162 | border: Border.all( 163 | color: const Color(0xFFECECEC), 164 | width: 2.0, 165 | ), 166 | ), 167 | ), 168 | Padding( 169 | padding: const EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 0.0), 170 | child: Text( 171 | itemData['timeStr'], 172 | style: subtitleStyle, 173 | ), 174 | ), 175 | Expanded( 176 | flex: 1, 177 | child: Row( 178 | mainAxisAlignment: MainAxisAlignment.end, 179 | children: [ 180 | Text("${itemData['commCount']}", style: subtitleStyle), 181 | Image.asset('./images/ic_comment.png', width: 16.0, height: 16.0), 182 | ], 183 | ), 184 | ) 185 | ], 186 | ); 187 | var thumbImgUrl = itemData['thumb']; 188 | var thumbImg = Container( 189 | margin: const EdgeInsets.all(10.0), 190 | width: 60.0, 191 | height: 60.0, 192 | decoration: BoxDecoration( 193 | shape: BoxShape.circle, 194 | color: const Color(0xFFECECEC), 195 | image: DecorationImage( 196 | image: ExactAssetImage('./images/ic_img_default.jpg'), 197 | fit: BoxFit.cover), 198 | border: Border.all( 199 | color: const Color(0xFFECECEC), 200 | width: 2.0, 201 | ), 202 | ), 203 | ); 204 | if (thumbImgUrl != null && thumbImgUrl.length > 0) { 205 | thumbImg = Container( 206 | margin: const EdgeInsets.all(10.0), 207 | width: 60.0, 208 | height: 60.0, 209 | decoration: BoxDecoration( 210 | shape: BoxShape.circle, 211 | color: const Color(0xFFECECEC), 212 | image: DecorationImage( 213 | image: NetworkImage(thumbImgUrl), fit: BoxFit.cover), 214 | border: Border.all( 215 | color: const Color(0xFFECECEC), 216 | width: 2.0, 217 | ), 218 | ), 219 | ); 220 | } 221 | var row = Row( 222 | children: [ 223 | Expanded( 224 | flex: 1, 225 | child: Padding( 226 | padding: const EdgeInsets.all(10.0), 227 | child: Column( 228 | children: [ 229 | titleRow, 230 | Padding( 231 | padding: const EdgeInsets.fromLTRB(0.0, 8.0, 0.0, 0.0), 232 | child: timeRow, 233 | ) 234 | ], 235 | ), 236 | ), 237 | ), 238 | Padding( 239 | padding: const EdgeInsets.all(6.0), 240 | child: Container( 241 | width: 100.0, 242 | height: 80.0, 243 | color: const Color(0xFFECECEC), 244 | child: Center( 245 | child: thumbImg, 246 | ), 247 | ), 248 | ) 249 | ], 250 | ); 251 | return InkWell( 252 | child: row, 253 | onTap: () { 254 | Navigator.of(context).push(MaterialPageRoute( 255 | builder: (ctx) => NewsDetailPage(id: itemData['detailUrl']) 256 | )); 257 | }, 258 | ); 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /lib/pages/OfflineActivityPage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:flutter/material.dart'; 3 | import '../util/NetUtils.dart'; 4 | import '../api/Api.dart'; 5 | import '../pages/CommonWebPage.dart'; 6 | 7 | // 线下活动 8 | class OfflineActivityPage extends StatefulWidget { 9 | @override 10 | State createState() { 11 | return OfflineActivityPageState(); 12 | } 13 | } 14 | 15 | class OfflineActivityPageState extends State { 16 | String eventTypeLatest = "latest"; 17 | String eventTypeYch = "ych"; 18 | String eventTypeRec = "recommend"; 19 | int curPage = 1; 20 | 21 | TextStyle titleTextStyle = TextStyle(color: Colors.black, fontSize: 18.0); 22 | 23 | List recData, latestData, ychData; 24 | 25 | @override 26 | void initState() { 27 | super.initState(); 28 | getData(eventTypeRec); 29 | getData(eventTypeLatest); 30 | getData(eventTypeYch); 31 | } 32 | 33 | void getData(String type) { 34 | String url = Api.eventList; 35 | url += "$type?pageIndex=$curPage&pageSize=5"; 36 | NetUtils.get(url).then((data) { 37 | if (data != null) { 38 | var obj = json.decode(data); 39 | if (obj != null && obj['code'] == 0) { 40 | print(obj); 41 | setState(() { 42 | if (type == eventTypeRec) { 43 | recData = obj['msg']; 44 | } else if (type == eventTypeLatest) { 45 | latestData = obj['msg']; 46 | } else { 47 | ychData = obj['msg']; 48 | } 49 | }); 50 | } 51 | } 52 | }); 53 | } 54 | 55 | Widget getRecBody() { 56 | if (recData == null) { 57 | return Center(child: CircularProgressIndicator()); 58 | } else if (recData.length == 0) { 59 | return Center(child: Text("暂无数据")); 60 | } else { 61 | return ListView.builder(itemBuilder: _renderRecRow, itemCount: recData.length); 62 | } 63 | } 64 | 65 | Widget getLatestBody() { 66 | if (latestData == null) { 67 | return Center(child: CircularProgressIndicator()); 68 | } else if (latestData.length == 0) { 69 | return Center(child: Text("暂无数据")); 70 | } else { 71 | return ListView.builder(itemBuilder: _renderLatestRow, itemCount: latestData.length); 72 | } 73 | } 74 | 75 | Widget getYchBody() { 76 | if (ychData == null) { 77 | return Center(child: CircularProgressIndicator()); 78 | } else if (ychData.length == 0) { 79 | return Center(child: Text("暂无数据")); 80 | } else { 81 | return ListView.builder(itemBuilder: _renderYchRow, itemCount: ychData.length); 82 | } 83 | } 84 | 85 | Widget getCard(itemData) { 86 | return Card( 87 | child: Column( 88 | children: [ 89 | Image.network(itemData['cover'], fit: BoxFit.cover,), 90 | Container( 91 | margin: const EdgeInsets.fromLTRB(10.0, 5.0, 10.0, 5.0), 92 | alignment: Alignment.centerLeft, 93 | child: Text(itemData['title'], style: titleTextStyle,), 94 | ), 95 | Container( 96 | margin: const EdgeInsets.fromLTRB(10.0, 0.0, 10.0, 5.0), 97 | child: Row( 98 | children: [ 99 | Expanded(child: Text(itemData['authorName']), flex: 1,), 100 | Text(itemData['timeStr']) 101 | ], 102 | ) 103 | ) 104 | ], 105 | ), 106 | ); 107 | } 108 | 109 | Widget _renderRecRow(BuildContext ctx, int i) { 110 | Map itemData = recData[i]; 111 | return InkWell( 112 | child: getCard(itemData), 113 | onTap: () { 114 | _showDetail(itemData['detailUrl']); 115 | }, 116 | ); 117 | } 118 | 119 | Widget _renderLatestRow(BuildContext ctx, int i) { 120 | Map itemData = latestData[i]; 121 | return InkWell( 122 | child: getCard(itemData), 123 | onTap: () { 124 | _showDetail(itemData['detailUrl']); 125 | }, 126 | ); 127 | } 128 | 129 | Widget _renderYchRow(BuildContext ctx, int i) { 130 | Map itemData = ychData[i]; 131 | return InkWell( 132 | child: getCard(itemData), 133 | onTap: () { 134 | _showDetail(itemData['detailUrl']); 135 | }, 136 | ); 137 | } 138 | 139 | _showDetail(detailUrl) { 140 | Navigator.of(context).push(MaterialPageRoute( 141 | builder: (ctx) { 142 | return CommonWebPage(title: '活动详情', url: detailUrl); 143 | } 144 | )); 145 | } 146 | 147 | @override 148 | Widget build(BuildContext context) { 149 | return Scaffold( 150 | appBar: AppBar( 151 | title: Text("线下活动", style: TextStyle(color: Colors.white)), 152 | iconTheme: IconThemeData(color: Colors.white), 153 | ), 154 | body: DefaultTabController( 155 | length: 3, 156 | child: Scaffold( 157 | appBar: TabBar( 158 | labelColor: Colors.black, 159 | tabs: [ 160 | Tab( 161 | text: "强力推荐", 162 | ), 163 | Tab( 164 | text: "最新活动", 165 | ), 166 | Tab( 167 | text: "源创会", 168 | ) 169 | ], 170 | ), 171 | body: TabBarView( 172 | children: [ 173 | getRecBody(), 174 | getLatestBody(), 175 | getYchBody() 176 | ], 177 | )), 178 | ), 179 | ); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /lib/pages/PublishTweetPage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'dart:io'; 4 | import 'package:async/async.dart'; 5 | import 'package:flutter/material.dart'; 6 | import '../api/Api.dart'; 7 | import 'package:http/http.dart' as http; 8 | import '../util/DataUtils.dart'; 9 | import 'package:image_picker/image_picker.dart'; 10 | 11 | class PublishTweetPage extends StatefulWidget { 12 | @override 13 | State createState() { 14 | return PublishTweetPageState(); 15 | } 16 | } 17 | 18 | class PublishTweetPageState extends State { 19 | 20 | TextEditingController _controller = TextEditingController(); 21 | List fileList = List(); 22 | Future _imageFile; 23 | bool isLoading = false; 24 | String msg = ""; 25 | 26 | Widget getBody() { 27 | // 输入框 28 | var textField = TextField( 29 | decoration: InputDecoration( 30 | hintText: "说点什么吧~", 31 | hintStyle: TextStyle( 32 | color: const Color(0xFF808080) 33 | ), 34 | border: OutlineInputBorder( 35 | borderRadius: const BorderRadius.all(const Radius.circular(10.0)) 36 | ) 37 | ), 38 | maxLines: 6, 39 | maxLength: 150, 40 | controller: _controller, 41 | ); 42 | // gridView用来显示选择的图片 43 | var gridView = Builder( 44 | builder: (ctx) { 45 | return GridView.count( 46 | // 分4列显示 47 | crossAxisCount: 4, 48 | children: List.generate(fileList.length + 1, (index) { 49 | // 这个方法体用于生成GridView中的一个item 50 | var content; 51 | if (index == 0) { 52 | // 添加图片按钮 53 | var addCell = Center( 54 | child: Image.asset('./images/ic_add_pics.png', width: 80.0, height: 80.0,) 55 | ); 56 | content = GestureDetector( 57 | onTap: () { 58 | // 添加图片 59 | pickImage(ctx); 60 | }, 61 | child: addCell, 62 | ); 63 | } else { 64 | // 被选中的图片 65 | content = Center( 66 | child: Image.file(fileList[index - 1], width: 80.0, height: 80.0, fit: BoxFit.cover,) 67 | ); 68 | } 69 | return Container( 70 | margin: const EdgeInsets.all(2.0), 71 | width: 80.0, 72 | height: 80.0, 73 | color: const Color(0xFFECECEC), 74 | child: content, 75 | ); 76 | }), 77 | ); 78 | }, 79 | ); 80 | var children = [ 81 | Text("提示:由于OSC的openapi限制,发布动弹的接口只支持上传一张图片,本项目可添加最多9张图片,但OSC只会接收最后一张图片。", style: TextStyle(fontSize: 12.0),), 82 | textField, 83 | Container( 84 | margin: const EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0), 85 | height: 200.0, 86 | child: gridView 87 | ) 88 | ]; 89 | if (isLoading) { 90 | children.add(Container( 91 | margin: const EdgeInsets.fromLTRB(0.0, 20.0, 0.0, 0.0), 92 | child: Center( 93 | child: CircularProgressIndicator(), 94 | ), 95 | )); 96 | } else { 97 | children.add(Container( 98 | margin: const EdgeInsets.fromLTRB(0.0, 20.0, 0.0, 0.0), 99 | child: Center( 100 | child: Text(msg), 101 | ) 102 | )); 103 | } 104 | return Container( 105 | padding: const EdgeInsets.all(5.0), 106 | child: Column( 107 | children: children, 108 | ), 109 | ); 110 | } 111 | 112 | // 相机拍照或者从图库选择图片 113 | pickImage(ctx) { 114 | // 如果已添加了9张图片,则提示不允许添加更多 115 | num size = fileList.length; 116 | if (size >= 9) { 117 | Scaffold.of(ctx).showSnackBar(SnackBar( 118 | content: Text("最多只能添加9张图片!"), 119 | )); 120 | return; 121 | } 122 | showModalBottomSheet(context: context, builder: _bottomSheetBuilder); 123 | } 124 | 125 | Widget _bottomSheetBuilder(BuildContext context) { 126 | return Container( 127 | height: 182.0, 128 | child: Padding( 129 | padding: const EdgeInsets.fromLTRB(0.0, 30.0, 0.0, 30.0), 130 | child: Column( 131 | children: [ 132 | _renderBottomMenuItem("相机拍照", ImageSource.camera), 133 | Divider(height: 2.0,), 134 | _renderBottomMenuItem("图库选择照片", ImageSource.gallery) 135 | ], 136 | ), 137 | ) 138 | ); 139 | } 140 | 141 | _renderBottomMenuItem(title, ImageSource source) { 142 | var item = Container( 143 | height: 60.0, 144 | child: Center( 145 | child: Text(title) 146 | ), 147 | ); 148 | return InkWell( 149 | child: item, 150 | onTap: () { 151 | Navigator.of(context).pop(); 152 | setState(() { 153 | _imageFile = ImagePicker.pickImage(source: source); 154 | }); 155 | }, 156 | ); 157 | } 158 | 159 | sendTweet(ctx, token) async { 160 | // 未登录或者未输入动弹内容时,使用SnackBar提示用户 161 | if (token == null) { 162 | Scaffold.of(ctx).showSnackBar(SnackBar( 163 | content: Text("未登录!"), 164 | )); 165 | return; 166 | } 167 | String content = _controller.text; 168 | if (content == null || content.length == 0 || content.trim().length == 0) { 169 | Scaffold.of(ctx).showSnackBar(SnackBar( 170 | content: Text("请输入动弹内容!"), 171 | )); 172 | } 173 | // 下面是调用接口发布动弹的逻辑 174 | try { 175 | Map params = Map(); 176 | params['msg'] = content; 177 | params['access_token'] = token; 178 | // 构造一个MultipartRequest对象用于上传图片 179 | var request = http.MultipartRequest('POST', Uri.parse(Api.pubTweet)); 180 | request.fields.addAll(params); 181 | if (fileList != null && fileList.length > 0) { 182 | // 这里虽然是添加了多个图片文件,但是开源中国提供的接口只接收一张图片 183 | for (File f in fileList) { 184 | // 文件流 185 | var stream = http.ByteStream( 186 | DelegatingStream.typed(f.openRead())); 187 | // 文件长度 188 | var length = await f.length(); 189 | // 文件名 190 | var filename = f.path.substring(f.path.lastIndexOf("/") + 1); 191 | // 将文件加入到请求体中 192 | request.files.add(http.MultipartFile( 193 | 'img', stream, length, filename: filename)); 194 | } 195 | } 196 | setState(() { 197 | isLoading = true; 198 | }); 199 | // 发送请求 200 | var response = await request.send(); 201 | // 解析请求返回的数据 202 | response.stream.transform(utf8.decoder).listen((value) { 203 | print(value); 204 | if (value != null) { 205 | var obj = json.decode(value); 206 | var error = obj['error']; 207 | setState(() { 208 | if (error != null && error == '200') { 209 | // 成功 210 | setState(() { 211 | isLoading = false; 212 | msg = "发布成功"; 213 | fileList.clear(); 214 | }); 215 | _controller.clear(); 216 | } else { 217 | setState(() { 218 | isLoading = false; 219 | msg = "发布失败:$error"; 220 | }); 221 | } 222 | }); 223 | } 224 | }); 225 | } catch (exception) { 226 | print(exception); 227 | } 228 | } 229 | 230 | @override 231 | Widget build(BuildContext context) { 232 | return Scaffold( 233 | appBar: AppBar( 234 | title: Text("发布动弹", style: TextStyle(color: Colors.white)), 235 | iconTheme: IconThemeData(color: Colors.white), 236 | actions: [ 237 | Builder( 238 | builder: (ctx) { 239 | return IconButton(icon: Icon(Icons.send), onPressed: () { 240 | // 发送动弹 241 | DataUtils.isLogin().then((isLogin) { 242 | if (isLogin) { 243 | return DataUtils.getAccessToken(); 244 | } else { 245 | return null; 246 | } 247 | }).then((token) { 248 | sendTweet(ctx, token); 249 | }); 250 | }); 251 | }, 252 | ) 253 | ], 254 | ), 255 | // 在这里接收选择的图片 256 | body: FutureBuilder( 257 | future: _imageFile, 258 | builder: (BuildContext context, AsyncSnapshot snapshot) { 259 | if (snapshot.connectionState == ConnectionState.done && 260 | snapshot.data != null && _imageFile != null) { 261 | // 选择了图片(拍照或图库选择),添加到List中 262 | fileList.add(snapshot.data); 263 | _imageFile = null; 264 | } 265 | // 返回的widget 266 | return getBody(); 267 | }, 268 | ), 269 | ); 270 | } 271 | } -------------------------------------------------------------------------------- /lib/pages/SettingsPage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_osc/constants/Constants.dart'; 3 | import 'package:flutter_osc/events/LogoutEvent.dart'; 4 | import 'package:flutter_osc/pages/ChangeThemePage.dart'; 5 | import '../util/DataUtils.dart'; 6 | 7 | class SettingsPage extends StatelessWidget { 8 | static const String TAG_START = "startDivider"; 9 | static const String TAG_END = "endDivider"; 10 | static const String TAG_CENTER = "centerDivider"; 11 | static const String TAG_BLANK = "blankDivider"; 12 | 13 | static const double IMAGE_ICON_WIDTH = 30.0; 14 | static const double ARROW_ICON_WIDTH = 16.0; 15 | 16 | final titleTextStyle = TextStyle(fontSize: 16.0); 17 | final rightArrowIcon = Image.asset( 18 | 'images/ic_arrow_right.png', 19 | width: ARROW_ICON_WIDTH, 20 | height: ARROW_ICON_WIDTH, 21 | ); 22 | 23 | final List listData = []; 24 | 25 | SettingsPage() { 26 | listData.add(TAG_BLANK); 27 | listData.add(TAG_START); 28 | listData.add(ListItem(title: '切换主题', icon: 'images/ic_discover_nearby.png')); 29 | listData.add(TAG_CENTER); 30 | listData.add(ListItem(title: '退出登录', icon: 'images/ic_discover_nearby.png')); 31 | listData.add(TAG_END); 32 | } 33 | 34 | Widget getIconImage(path) { 35 | return Padding( 36 | padding: const EdgeInsets.fromLTRB(0.0, 0.0, 10.0, 0.0), 37 | child: Image.asset(path, 38 | width: IMAGE_ICON_WIDTH, height: IMAGE_ICON_WIDTH), 39 | ); 40 | } 41 | 42 | _renderRow(BuildContext ctx, int i) { 43 | var item = listData[i]; 44 | if (item is String) { 45 | Widget w; 46 | switch (item) { 47 | case TAG_START: 48 | case TAG_END: 49 | w = Divider( 50 | height: 1.0, 51 | ); 52 | break; 53 | case TAG_CENTER: 54 | w = Padding( 55 | padding: const EdgeInsets.fromLTRB(50.0, 0.0, 0.0, 0.0), 56 | child: Divider( 57 | height: 1.0, 58 | ), 59 | ); 60 | break; 61 | case TAG_BLANK: 62 | w = Container( 63 | height: 20.0, 64 | ); 65 | break; 66 | default: 67 | w = Divider( 68 | height: 1.0, 69 | ); 70 | } 71 | return w; 72 | } else if (item is ListItem) { 73 | var listItemContent = Padding( 74 | padding: const EdgeInsets.fromLTRB(10.0, 15.0, 10.0, 15.0), 75 | child: Row( 76 | children: [ 77 | getIconImage(item.icon), 78 | Expanded( 79 | child: Text( 80 | item.title, 81 | style: titleTextStyle, 82 | )), 83 | rightArrowIcon 84 | ], 85 | ), 86 | ); 87 | return InkWell( 88 | onTap: () { 89 | String title = item.title; 90 | if (title == '退出登录') { 91 | DataUtils.clearLoginInfo().then((arg) { 92 | Navigator.of(ctx).pop(); 93 | Constants.eventBus.fire(LogoutEvent()); 94 | print("event fired!"); 95 | }); 96 | } else if (title == '切换主题') { 97 | Navigator.push(ctx, MaterialPageRoute(builder: (ctx) { 98 | return ChangeThemePage(); 99 | })); 100 | } 101 | }, 102 | child: listItemContent, 103 | ); 104 | } 105 | } 106 | 107 | @override 108 | Widget build(BuildContext context) { 109 | return Scaffold( 110 | appBar: AppBar( 111 | title: Text("设置", style: TextStyle(color: Colors.white)), 112 | iconTheme: IconThemeData(color: Colors.white), 113 | ), 114 | body: ListView.builder( 115 | itemBuilder: (ctx, i) => _renderRow(ctx, i), 116 | itemCount: listData.length, 117 | ), 118 | ); 119 | } 120 | } 121 | 122 | class ListItem { 123 | String icon; 124 | String title; 125 | ListItem({this.icon, this.title}); 126 | } 127 | -------------------------------------------------------------------------------- /lib/pages/TweetDetailPage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../util/NetUtils.dart'; 3 | import '../api/Api.dart'; 4 | import '../constants/Constants.dart'; 5 | import 'dart:convert'; 6 | import '../widgets/CommonEndLine.dart'; 7 | import '../util/DataUtils.dart'; 8 | 9 | // 动弹详情 10 | 11 | class TweetDetailPage extends StatefulWidget { 12 | final Map tweetData; 13 | 14 | TweetDetailPage({Key key, this.tweetData}):super(key: key); 15 | 16 | @override 17 | State createState() { 18 | return TweetDetailPageState(tweetData: tweetData); 19 | } 20 | } 21 | 22 | class TweetDetailPageState extends State { 23 | 24 | Map tweetData; 25 | List commentList; 26 | RegExp regExp1 = RegExp(""); 27 | RegExp regExp2 = RegExp("<.*>"); 28 | TextStyle subtitleStyle = TextStyle( 29 | fontSize: 12.0, 30 | color: const Color(0xFFB5BDC0) 31 | ); 32 | TextStyle contentStyle = TextStyle( 33 | fontSize: 15.0, 34 | color: Colors.black 35 | ); 36 | num curPage = 1; 37 | ScrollController _controller = ScrollController(); 38 | TextEditingController _inputController = TextEditingController(); 39 | 40 | TweetDetailPageState({Key key, this.tweetData}); 41 | 42 | // 获取动弹的回复 43 | getReply(bool isLoadMore) { 44 | DataUtils.isLogin().then((isLogin) { 45 | if (isLogin) { 46 | DataUtils.getAccessToken().then((token) { 47 | if (token == null || token.length == 0) { 48 | return; 49 | } 50 | Map params = Map(); 51 | var id = this.tweetData['id']; 52 | params['id'] = '$id'; 53 | params['catalog'] = '3';// 3是动弹评论 54 | params['access_token'] = token; 55 | params['page'] = '$curPage'; 56 | params['pageSize'] = '20'; 57 | params['dataType'] = 'json'; 58 | NetUtils.get(Api.commentList, params: params).then((data) { 59 | setState(() { 60 | if (!isLoadMore) { 61 | commentList = json.decode(data)['commentList']; 62 | if (commentList == null) { 63 | commentList = List(); 64 | } 65 | } else { 66 | // 加载更多数据 67 | List list = List(); 68 | list.addAll(commentList); 69 | list.addAll(json.decode(data)['commentList']); 70 | if (list.length >= tweetData['commentCount']) { 71 | list.add(Constants.endLineTag); 72 | } 73 | commentList = list; 74 | } 75 | }); 76 | }); 77 | }); 78 | } 79 | }); 80 | } 81 | 82 | @override 83 | void initState() { 84 | super.initState(); 85 | getReply(false); 86 | _controller.addListener(() { 87 | var max = _controller.position.maxScrollExtent; 88 | var pixels = _controller.position.pixels; 89 | if (max == pixels && commentList.length < tweetData['commentCount']) { 90 | // scroll to end, load next page 91 | curPage++; 92 | getReply(true); 93 | } 94 | }); 95 | } 96 | 97 | @override 98 | Widget build(BuildContext context) { 99 | var _body = commentList == null ? Center( 100 | child: CircularProgressIndicator(), 101 | ) : ListView.builder( 102 | itemCount: commentList.length == 0 ? 1 : commentList.length * 2, 103 | itemBuilder: renderListItem, 104 | controller: _controller, 105 | ); 106 | return Scaffold( 107 | appBar: AppBar( 108 | title: Text("动弹详情", style: TextStyle(color: Colors.white)), 109 | iconTheme: IconThemeData(color: Colors.white), 110 | actions: [ 111 | IconButton( 112 | icon: Icon(Icons.send), 113 | onPressed: () { 114 | // 回复楼主 115 | showReplyBottomView(context, true); 116 | }, 117 | ) 118 | ], 119 | ), 120 | body: _body 121 | ); 122 | } 123 | 124 | Widget renderListItem(BuildContext context, int i) { 125 | if (i == 0) { 126 | return getTweetView(this.tweetData); 127 | } 128 | i -= 1; 129 | if (i.isOdd) { 130 | return Divider(height: 1.0,); 131 | } 132 | i ~/= 2; 133 | return _renderCommentRow(context, i); 134 | } 135 | 136 | // 渲染评论列表 137 | _renderCommentRow(context, i) { 138 | var listItem = commentList[i]; 139 | if (listItem is String && listItem == Constants.endLineTag) { 140 | return CommonEndLine(); 141 | } 142 | String avatar = listItem['commentPortrait']; 143 | String author = listItem['commentAuthor']; 144 | String date = listItem['pubDate']; 145 | String content = listItem['content']; 146 | content = clearHtmlContent(content); 147 | var row = Row( 148 | children: [ 149 | Padding( 150 | padding: const EdgeInsets.all(10.0), 151 | child: Image.network(avatar, width: 35.0, height: 35.0,) 152 | ), 153 | Expanded( 154 | child: Container( 155 | margin: const EdgeInsets.fromLTRB(0.0, 5.0, 0.0, 5.0), 156 | child: Column( 157 | children: [ 158 | Row( 159 | children: [ 160 | Expanded( 161 | child: Text(author, style: TextStyle(color: const Color(0xFF63CA6C)),), 162 | ), 163 | Padding( 164 | padding: const EdgeInsets.fromLTRB(0.0, 0.0, 10.0, 0.0), 165 | child: Text(date, style: subtitleStyle,) 166 | ) 167 | ], 168 | ), 169 | Padding( 170 | padding: const EdgeInsets.fromLTRB(0.0, 0.0, 10.0, 0.0), 171 | child: Row( 172 | children: [ 173 | Expanded( 174 | child: Text(content, style: contentStyle,) 175 | ) 176 | ], 177 | ) 178 | ) 179 | ], 180 | ), 181 | ) 182 | ) 183 | ], 184 | ); 185 | return Builder( 186 | builder: (ctx) { 187 | return InkWell( 188 | onTap: () { 189 | showReplyBottomView(ctx, false, data: listItem); 190 | }, 191 | child: row, 192 | ); 193 | }, 194 | ); 195 | } 196 | 197 | showReplyBottomView(ctx, bool isMainFloor, {data}) { 198 | String title; 199 | String authorId; 200 | if (isMainFloor) { 201 | title = "@${tweetData['author']}"; 202 | authorId = "${tweetData['authorid']}"; 203 | } else { 204 | title = "@${data['commentAuthor']}"; 205 | authorId = "${data['commentAuthorId']}"; 206 | } 207 | print("authorId = $authorId"); 208 | showModalBottomSheet( 209 | context: ctx, 210 | builder: (sheetCtx) { 211 | return Container( 212 | height: 230.0, 213 | padding: const EdgeInsets.all(20.0), 214 | child: Column( 215 | children: [ 216 | Row( 217 | children: [ 218 | Text(isMainFloor ? "回复楼主" : "回复"), 219 | Expanded(child: Text(title, style: TextStyle(color: const Color(0xFF63CA6C)),)), 220 | InkWell( 221 | child: Container( 222 | padding: const EdgeInsets.fromLTRB(10.0, 6.0, 10.0, 6.0), 223 | decoration: BoxDecoration( 224 | border: Border.all( 225 | color: const Color(0xFF63CA6C), 226 | width: 1.0, 227 | ), 228 | borderRadius: BorderRadius.all(Radius.circular(6.0)) 229 | ), 230 | child: Text("发送", style: TextStyle(color: const Color(0xFF63CA6C)),), 231 | ), 232 | onTap: () { 233 | // 发送回复 234 | sendReply(authorId); 235 | }, 236 | ) 237 | ], 238 | ), 239 | Container( 240 | height: 10.0, 241 | ), 242 | TextField( 243 | maxLines: 5, 244 | controller: _inputController, 245 | decoration: InputDecoration( 246 | hintText: "说点啥~", 247 | hintStyle: TextStyle( 248 | color: const Color(0xFF808080) 249 | ), 250 | border: OutlineInputBorder( 251 | borderRadius: const BorderRadius.all(const Radius.circular(10.0)), 252 | ) 253 | ), 254 | ) 255 | ], 256 | ) 257 | ); 258 | } 259 | ); 260 | } 261 | 262 | void sendReply(authorId) { 263 | String replyStr = _inputController.text; 264 | if (replyStr == null || replyStr.length == 0 || replyStr.trim().length == 0) { 265 | return; 266 | } else { 267 | DataUtils.isLogin().then((isLogin) { 268 | if (isLogin) { 269 | DataUtils.getAccessToken().then((token) { 270 | Map params = Map(); 271 | params['access_token'] = token; 272 | params['id'] = "${tweetData['id']}"; 273 | print("id: ${tweetData['id']}"); 274 | params['catalog'] = "3"; 275 | params['content'] = replyStr; 276 | params['authorid'] = "$authorId"; 277 | print("authorId: $authorId"); 278 | params['isPostToMyZone'] = "0"; 279 | params['dataType'] = "json"; 280 | NetUtils.get(Api.commentReply, params: params).then((data) { 281 | if (data != null) { 282 | var obj = json.decode(data); 283 | var error = obj['error']; 284 | if (error != null && error == '200') { 285 | // 回复成功 286 | Navigator.of(context).pop(); 287 | getReply(false); 288 | } 289 | } 290 | }); 291 | }); 292 | } 293 | }); 294 | } 295 | } 296 | 297 | Widget getTweetView(Map listItem) { 298 | var authorRow = Row( 299 | children: [ 300 | Container( 301 | width: 35.0, 302 | height: 35.0, 303 | decoration: BoxDecoration( 304 | shape: BoxShape.circle, 305 | color: Colors.blue, 306 | image: DecorationImage( 307 | image: NetworkImage(listItem['portrait']), 308 | fit: BoxFit.cover 309 | ), 310 | border: Border.all( 311 | color: Colors.white, 312 | width: 2.0, 313 | ), 314 | ), 315 | ), 316 | Padding( 317 | padding: const EdgeInsets.fromLTRB(6.0, 0.0, 0.0, 0.0), 318 | child: Text(listItem['author'], style: TextStyle( 319 | fontSize: 16.0, 320 | )) 321 | ), 322 | Expanded( 323 | child: Row( 324 | mainAxisAlignment: MainAxisAlignment.end, 325 | children: [ 326 | Text('${listItem['commentCount']}', style: subtitleStyle,), 327 | Image.asset('./images/ic_comment.png', width: 20.0, height: 20.0,) 328 | ], 329 | ), 330 | ) 331 | ], 332 | ); 333 | var _body = listItem['body']; 334 | _body = clearHtmlContent(_body); 335 | var contentRow = Row( 336 | children: [ 337 | Expanded(child: Text(_body),) 338 | ], 339 | ); 340 | var timeRow = Row( 341 | mainAxisAlignment: MainAxisAlignment.start, 342 | children: [ 343 | Text(listItem['pubDate'], style: subtitleStyle,) 344 | ], 345 | ); 346 | var columns = [ 347 | Padding( 348 | padding: const EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 2.0), 349 | child: authorRow, 350 | ), 351 | Padding( 352 | padding: const EdgeInsets.fromLTRB(52.0, 0.0, 10.0, 0.0), 353 | child: contentRow, 354 | ), 355 | ]; 356 | String imgSmall = listItem['imgSmall']; 357 | if (imgSmall != null && imgSmall.length > 0) { 358 | // 动弹中有图片 359 | List list = imgSmall.split(","); 360 | List imgUrlList = List(); 361 | for (String s in list) { 362 | if (s.startsWith("http")) { 363 | imgUrlList.add(s); 364 | } else { 365 | imgUrlList.add("https://static.oschina.net/uploads/space/" + s); 366 | } 367 | } 368 | List imgList = []; 369 | List rows = []; 370 | num len = imgUrlList.length; 371 | for (var row = 0; row < getRow(len); row++) { 372 | List rowArr = []; 373 | for (var col = 0; col < 3; col++) { 374 | num index = row * 3 + col; 375 | num screenWidth = MediaQuery.of(context).size.width; 376 | double cellWidth = (screenWidth - 100) / 3; 377 | if (index < len) { 378 | rowArr.add(Padding( 379 | padding: const EdgeInsets.all(2.0), 380 | child: Image.network(imgUrlList[index], width: cellWidth, height: cellWidth), 381 | )); 382 | } 383 | } 384 | rows.add(rowArr); 385 | } 386 | for (var row in rows) { 387 | imgList.add(Row( 388 | children: row, 389 | )); 390 | } 391 | columns.add(Padding( 392 | padding: const EdgeInsets.fromLTRB(52.0, 5.0, 10.0, 0.0), 393 | child: Column( 394 | children: imgList, 395 | ), 396 | )); 397 | } 398 | columns.add(Padding( 399 | padding: const EdgeInsets.fromLTRB(52.0, 10.0, 10.0, 6.0), 400 | child: timeRow, 401 | )); 402 | columns.add(Divider(height: 5.0,)); 403 | columns.add(Container( 404 | margin: const EdgeInsets.fromLTRB(0.0, 6.0, 0.0, 0.0), 405 | child: Row( 406 | children: [ 407 | Container( 408 | width: 4.0, 409 | height: 20.0, 410 | color: const Color(0xFF63CA6C), 411 | ), 412 | Expanded( 413 | flex: 1, 414 | child: Container( 415 | height: 20.0, 416 | color: const Color(0xFFECECEC), 417 | child: Text("评论列表", style: TextStyle(color: const Color(0xFF63CA6C)),) 418 | ), 419 | ) 420 | ], 421 | ), 422 | )); 423 | return Column( 424 | children: columns, 425 | ); 426 | } 427 | 428 | int getRow(int n) { 429 | int a = n % 3; 430 | int b = n ~/ 3; 431 | if (a != 0) { 432 | return b + 1; 433 | } 434 | return b; 435 | } 436 | 437 | // 去掉文本中的html代码 438 | String clearHtmlContent(String str) { 439 | if (str.startsWith(" convert(List objList) { 11 | if (objList == null || objList.isEmpty) { 12 | return List(); 13 | } 14 | List intList = List(); 15 | for (var obj in objList) { 16 | intList.add(obj['authorid']); 17 | } 18 | return intList; 19 | } 20 | 21 | // 字符串转化为整型数组 22 | static List _str2intList(String str) { 23 | if (str != null && str.length > 0) { 24 | List list = str.split(","); 25 | if (list != null && list.isNotEmpty) { 26 | List intList = List(); 27 | for (String s in list) { 28 | intList.add(int.parse(s)); 29 | } 30 | return intList; 31 | } 32 | } 33 | return null; 34 | } 35 | 36 | // 整型数组转化为字符串 37 | static String _intList2Str(List list) { 38 | if (list == null || list.isEmpty) { 39 | return null; 40 | } 41 | StringBuffer sb = StringBuffer(); 42 | for (int id in list) { 43 | sb.write("$id,"); 44 | } 45 | String result = sb.toString(); 46 | return result.substring(0, result.length - 1); 47 | } 48 | 49 | // 保存黑名单的id 50 | static Future saveBlackListIds(List list) async { 51 | String str = _intList2Str(list); 52 | if (str != null) { 53 | SharedPreferences sp = await SharedPreferences.getInstance(); 54 | sp.setString(spBlackList, str); 55 | } else { 56 | SharedPreferences sp = await SharedPreferences.getInstance(); 57 | sp.setString(spBlackList, ""); 58 | } 59 | return str; 60 | } 61 | 62 | // 获取本地保存的黑名单id数据 63 | static Future> getBlackListIds() async { 64 | SharedPreferences sp = await SharedPreferences.getInstance(); 65 | String str = sp.getString(spBlackList); 66 | if (str != null && str.length > 0) { 67 | return _str2intList(str); 68 | } 69 | return null; 70 | } 71 | 72 | // 向黑名单中添加一个id 73 | static Future> addBlackId(int id) async { 74 | List list = await getBlackListIds(); 75 | if (list != null && list.isNotEmpty) { 76 | if (!list.contains(id)) { 77 | list.add(id); 78 | String str = await saveBlackListIds(list); 79 | return _str2intList(str); 80 | } else { 81 | return list; 82 | } 83 | } else { 84 | List l = List(); 85 | l.add(id); 86 | String str = await saveBlackListIds(l); 87 | return _str2intList(str); 88 | } 89 | } 90 | 91 | // 向黑名单中移除一个id 92 | static Future> removeBlackId(int id) async { 93 | List list = await getBlackListIds(); 94 | if (list != null && list.isNotEmpty) { 95 | if (list.contains(id)) { 96 | list.remove(id); 97 | String str = await saveBlackListIds(list); 98 | return _str2intList(str); 99 | } 100 | } 101 | return list; 102 | } 103 | 104 | } -------------------------------------------------------------------------------- /lib/util/DataUtils.dart: -------------------------------------------------------------------------------- 1 | import 'package:shared_preferences/shared_preferences.dart'; 2 | import 'dart:async'; 3 | import '../model/UserInfo.dart'; 4 | 5 | class DataUtils { 6 | static const String SP_AC_TOKEN = "accessToken"; 7 | static const String SP_RE_TOKEN = "refreshToken"; 8 | static const String SP_UID = "uid"; 9 | static const String SP_IS_LOGIN = "isLogin"; 10 | static const String SP_EXPIRES_IN = "expiresIn"; 11 | static const String SP_TOKEN_TYPE = "tokenType"; 12 | 13 | static const String SP_USER_NAME = "name"; 14 | static const String SP_USER_ID = "id"; 15 | static const String SP_USER_LOC = "location"; 16 | static const String SP_USER_GENDER = "gender"; 17 | static const String SP_USER_AVATAR = "avatar"; 18 | static const String SP_USER_EMAIL = "email"; 19 | static const String SP_USER_URL = "url"; 20 | 21 | static const String SP_COLOR_THEME_INDEX = "colorThemeIndex"; 22 | 23 | // 保存用户登录信息,data中包含了token等信息 24 | static saveLoginInfo(Map data) async { 25 | if (data != null) { 26 | SharedPreferences sp = await SharedPreferences.getInstance(); 27 | String accessToken = data['access_token']; 28 | await sp.setString(SP_AC_TOKEN, accessToken); 29 | String refreshToken = data['refresh_token']; 30 | await sp.setString(SP_RE_TOKEN, refreshToken); 31 | num uid = data['uid']; 32 | await sp.setInt(SP_UID, uid); 33 | String tokenType = data['tokenType']; 34 | await sp.setString(SP_TOKEN_TYPE, tokenType); 35 | num expiresIn = data['expires_in']; 36 | await sp.setInt(SP_EXPIRES_IN, expiresIn); 37 | 38 | await sp.setBool(SP_IS_LOGIN, true); 39 | } 40 | } 41 | 42 | // 清除登录信息 43 | static clearLoginInfo() async { 44 | SharedPreferences sp = await SharedPreferences.getInstance(); 45 | await sp.setString(SP_AC_TOKEN, ""); 46 | await sp.setString(SP_RE_TOKEN, ""); 47 | await sp.setInt(SP_UID, -1); 48 | await sp.setString(SP_TOKEN_TYPE, ""); 49 | await sp.setInt(SP_EXPIRES_IN, -1); 50 | await sp.setBool(SP_IS_LOGIN, false); 51 | } 52 | 53 | // 保存用户个人信息 54 | static Future saveUserInfo(Map data) async { 55 | if (data != null) { 56 | SharedPreferences sp = await SharedPreferences.getInstance(); 57 | String name = data['name']; 58 | num id = data['id']; 59 | String gender = data['gender']; 60 | String location = data['location']; 61 | String avatar = data['avatar']; 62 | String email = data['email']; 63 | String url = data['url']; 64 | await sp.setString(SP_USER_NAME, name); 65 | await sp.setInt(SP_USER_ID, id); 66 | await sp.setString(SP_USER_GENDER, gender); 67 | await sp.setString(SP_USER_AVATAR, avatar); 68 | await sp.setString(SP_USER_LOC, location); 69 | await sp.setString(SP_USER_EMAIL, email); 70 | await sp.setString(SP_USER_URL, url); 71 | UserInfo userInfo = UserInfo( 72 | id: id, 73 | name: name, 74 | gender: gender, 75 | avatar: avatar, 76 | email: email, 77 | location: location, 78 | url: url); 79 | return userInfo; 80 | } 81 | return null; 82 | } 83 | 84 | // 获取用户信息 85 | static Future getUserInfo() async { 86 | SharedPreferences sp = await SharedPreferences.getInstance(); 87 | bool isLogin = sp.getBool(SP_IS_LOGIN); 88 | if (isLogin == null || !isLogin) { 89 | return null; 90 | } 91 | UserInfo userInfo = UserInfo(); 92 | userInfo.id = sp.getInt(SP_USER_ID); 93 | userInfo.name = sp.getString(SP_USER_NAME); 94 | userInfo.avatar = sp.getString(SP_USER_AVATAR); 95 | userInfo.email = sp.getString(SP_USER_EMAIL); 96 | userInfo.location = sp.getString(SP_USER_LOC); 97 | userInfo.gender = sp.getString(SP_USER_GENDER); 98 | userInfo.url = sp.getString(SP_USER_URL); 99 | return userInfo; 100 | } 101 | 102 | // 是否登录 103 | static Future isLogin() async { 104 | SharedPreferences sp = await SharedPreferences.getInstance(); 105 | bool b = sp.getBool(SP_IS_LOGIN); 106 | return b != null && b; 107 | } 108 | 109 | // 获取accesstoken 110 | static Future getAccessToken() async { 111 | SharedPreferences sp = await SharedPreferences.getInstance(); 112 | return sp.getString(SP_AC_TOKEN); 113 | } 114 | 115 | // 设置选择的主题色 116 | static setColorTheme(int colorThemeIndex) async { 117 | SharedPreferences sp = await SharedPreferences.getInstance(); 118 | sp.setInt(SP_COLOR_THEME_INDEX, colorThemeIndex); 119 | } 120 | 121 | static Future getColorThemeIndex() async { 122 | SharedPreferences sp = await SharedPreferences.getInstance(); 123 | return sp.getInt(SP_COLOR_THEME_INDEX); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /lib/util/NetUtils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:http/http.dart' as http; 3 | 4 | class NetUtils { 5 | // get请求的封装,传入的两个参数分别是请求URL和请求参数,请求参数以map的形式传入,会在方法体中自动拼接到URL后面 6 | static Future get(String url, {Map params}) async { 7 | if (params != null && params.isNotEmpty) { 8 | // 如果参数不为空,则将参数拼接到URL后面 9 | StringBuffer sb = StringBuffer("?"); 10 | params.forEach((key, value) { 11 | sb.write("$key" + "=" + "$value" + "&"); 12 | }); 13 | String paramStr = sb.toString(); 14 | paramStr = paramStr.substring(0, paramStr.length - 1); 15 | url += paramStr; 16 | } 17 | http.Response res = await http.get(url, headers: getCommonHeader()); 18 | return res.body; 19 | } 20 | 21 | // post请求 22 | static Future post(String url, {Map params}) async { 23 | http.Response res = await http.post(url, body: params, headers: getCommonHeader()); 24 | return res.body; 25 | } 26 | 27 | static Map getCommonHeader() { 28 | Map header = Map(); 29 | header['is_flutter_osc'] = "1"; 30 | return header; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/util/ThemeUtils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ThemeUtils { 4 | // 默认主题色 5 | static const Color defaultColor = const Color(0xFF63CA6C); 6 | 7 | // 可选的主题色 8 | static const List supportColors = [ 9 | defaultColor, 10 | Colors.purple, 11 | Colors.orange, 12 | Colors.deepPurpleAccent, 13 | Colors.redAccent, 14 | Colors.blue, 15 | Colors.amber, 16 | Colors.green, 17 | Colors.lime, 18 | Colors.indigo, 19 | Colors.cyan, 20 | Colors.teal 21 | ]; 22 | 23 | // 当前的主题色 24 | static Color currentColorTheme = defaultColor; 25 | } 26 | -------------------------------------------------------------------------------- /lib/util/Utf8Utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | class Utf8Utils { 4 | static String encode(String origin) { 5 | if (origin == null || origin.length == 0) { 6 | return null; 7 | } 8 | List list = utf8.encode(origin); 9 | StringBuffer sb = StringBuffer(); 10 | for (int i in list) { 11 | sb.write("$i,"); 12 | } 13 | String result = sb.toString(); 14 | return result.substring(0, result.length - 1); 15 | } 16 | 17 | static String decode(String encodeStr) { 18 | if (encodeStr == null || encodeStr.length == 0) { 19 | return null; 20 | } 21 | List list = encodeStr.split(","); 22 | if (list != null && list.isNotEmpty) { 23 | List intList = List(); 24 | for (String s in list) { 25 | intList.add(int.parse(s)); 26 | } 27 | return utf8.decode(intList); 28 | } 29 | return null; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/widgets/CircleImage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | enum CircleImageType {network, asset} 5 | 6 | class CircleImage extends StatefulWidget { 7 | double width; 8 | double height; 9 | String path; 10 | CircleImageType type; // network, asset 11 | 12 | CircleImage({@required this.width, @required this.height, @required this.path, @required this.type}); 13 | 14 | @override 15 | State createState() { 16 | return null; 17 | } 18 | } 19 | 20 | class CircleImageState extends State { 21 | @override 22 | Widget build(BuildContext context) { 23 | var img; 24 | if (widget.type == CircleImageType.network) { 25 | img = Image.network(widget.path, width: widget.width, height: widget.height); 26 | } else { 27 | img = Image.asset(widget.path, width: widget.width, height: widget.height); 28 | } 29 | return Container( 30 | width: widget.width, 31 | height: widget.height, 32 | decoration: BoxDecoration( 33 | shape: BoxShape.circle, 34 | color: Colors.blue, 35 | image: DecorationImage( 36 | image: img, 37 | fit: BoxFit.cover 38 | ), 39 | border: Border.all( 40 | color: Colors.white, 41 | width: 2.0, 42 | ), 43 | ), 44 | ); 45 | } 46 | } -------------------------------------------------------------------------------- /lib/widgets/CommonButton.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_osc/util/ThemeUtils.dart'; 3 | 4 | class CommonButton extends StatefulWidget { 5 | final String text; 6 | final GestureTapCallback onTap; 7 | 8 | CommonButton({@required this.text, @required this.onTap}); 9 | 10 | @override 11 | State createState() => CommonButtonState(); 12 | } 13 | 14 | class CommonButtonState extends State { 15 | 16 | Color color = ThemeUtils.currentColorTheme; 17 | TextStyle textStyle = TextStyle(color: Colors.white, fontSize: 17); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return InkWell( 22 | onTap: () { 23 | this.widget.onTap(); 24 | }, 25 | child: Container( 26 | height: 45, 27 | decoration: BoxDecoration( 28 | color: color, 29 | border: Border.all(color: const Color(0xffcccccc)), 30 | borderRadius: BorderRadius.all(Radius.circular(30)) 31 | ), 32 | child: Center( 33 | child: Text(this.widget.text, style: textStyle,), 34 | ), 35 | ), 36 | ); 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /lib/widgets/CommonEndLine.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CommonEndLine extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return Container( 7 | color: const Color(0xFFEEEEEE), 8 | padding: const EdgeInsets.fromLTRB(5.0, 15.0, 5.0, 15.0), 9 | child: Row( 10 | children: [ 11 | Expanded( 12 | child: Divider(height: 10.0,), 13 | flex: 1, 14 | ), 15 | Text("我也是有底线的"), 16 | Expanded( 17 | child: Divider(height: 10.0,), 18 | flex: 1, 19 | ), 20 | ], 21 | ), 22 | ); 23 | } 24 | } -------------------------------------------------------------------------------- /lib/widgets/MyDrawer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../pages/AboutPage.dart'; 3 | import '../pages/BlackHousePage.dart'; 4 | import '../pages/PublishTweetPage.dart'; 5 | import '../pages/SettingsPage.dart'; 6 | 7 | class MyDrawer extends StatelessWidget { 8 | // 菜单文本前面的图标大小 9 | static const double IMAGE_ICON_WIDTH = 30.0; 10 | // 菜单后面的箭头的图标大小 11 | static const double ARROW_ICON_WIDTH = 16.0; 12 | // 菜单后面的箭头图片 13 | var rightArrowIcon = Image.asset( 14 | 'images/ic_arrow_right.png', 15 | width: ARROW_ICON_WIDTH, 16 | height: ARROW_ICON_WIDTH, 17 | ); 18 | // 菜单的文本 19 | List menuTitles = ['发布动弹', '动弹小黑屋', '关于', '设置']; 20 | // 菜单文本前面的图标 21 | List menuIcons = [ 22 | './images/leftmenu/ic_fabu.png', 23 | './images/leftmenu/ic_xiaoheiwu.png', 24 | './images/leftmenu/ic_about.png', 25 | './images/leftmenu/ic_settings.png' 26 | ]; 27 | // 菜单文本的样式 28 | TextStyle menuStyle = TextStyle( 29 | fontSize: 15.0, 30 | ); 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return ConstrainedBox( 35 | constraints: const BoxConstraints.expand(width: 304.0), 36 | child: Material( 37 | elevation: 16.0, 38 | child: ListView.builder( 39 | itemCount: menuTitles.length * 2 + 1, 40 | itemBuilder: renderRow, 41 | padding: const EdgeInsets.all(0.0), // 加上这一行可以让Drawer展开时,状态栏中不显示白色 42 | ), 43 | ), 44 | ); 45 | } 46 | 47 | Widget getIconImage(path) { 48 | return Padding( 49 | padding: const EdgeInsets.fromLTRB(2.0, 0.0, 6.0, 0.0), 50 | child: Image.asset(path, width: 28.0, height: 28.0), 51 | ); 52 | } 53 | 54 | Widget renderRow(BuildContext context, int index) { 55 | if (index == 0) { 56 | // render cover image 57 | var img = Image.asset( 58 | './images/cover_img.jpg', 59 | width: 304.0, 60 | height: 304.0, 61 | ); 62 | return Container( 63 | width: 304.0, 64 | height: 304.0, 65 | margin: const EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 10.0), 66 | child: img, 67 | ); 68 | } 69 | // 舍去之前的封面图 70 | index -= 1; 71 | // 如果是奇数则渲染分割线 72 | if (index.isOdd) { 73 | return Divider(); 74 | } 75 | // 偶数,就除2取整,然后渲染菜单item 76 | index = index ~/ 2; 77 | // 菜单item组件 78 | var listItemContent = Padding( 79 | // 设置item的外边距 80 | padding: const EdgeInsets.fromLTRB(10.0, 15.0, 10.0, 15.0), 81 | // Row组件构成item的一行 82 | child: Row( 83 | children: [ 84 | // 菜单item的图标 85 | getIconImage(menuIcons[index]), 86 | // 菜单item的文本,需要 87 | Expanded( 88 | child: Text( 89 | menuTitles[index], 90 | style: menuStyle, 91 | ) 92 | ), 93 | rightArrowIcon 94 | ], 95 | ), 96 | ); 97 | 98 | return InkWell( 99 | child: listItemContent, 100 | onTap: () { 101 | switch (index) { 102 | case 0: 103 | // 发布动弹 104 | Navigator.of(context).push(MaterialPageRoute(builder: (ctx) { 105 | return PublishTweetPage(); 106 | })); 107 | break; 108 | case 1: 109 | // 小黑屋 110 | Navigator.of(context).push(MaterialPageRoute(builder: (ctx) { 111 | return BlackHousePage(); 112 | })); 113 | break; 114 | case 2: 115 | // 关于 116 | Navigator.of(context).push(MaterialPageRoute(builder: (ctx) { 117 | return AboutPage(); 118 | })); 119 | break; 120 | case 3: 121 | // 设置 122 | Navigator.of(context).push(MaterialPageRoute(builder: (ctx) { 123 | return SettingsPage(); 124 | })); 125 | break; 126 | } 127 | }, 128 | ); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /lib/widgets/SlideView.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../pages/NewsDetailPage.dart'; 4 | import 'SlideViewIndicator.dart'; 5 | 6 | class SlideView extends StatefulWidget { 7 | final List data; 8 | final SlideViewIndicator slideViewIndicator; 9 | final GlobalKey globalKey; 10 | 11 | SlideView(this.data, this.slideViewIndicator, this.globalKey); 12 | 13 | @override 14 | State createState() { 15 | return SlideViewState(); 16 | } 17 | } 18 | 19 | class SlideViewState extends State 20 | with SingleTickerProviderStateMixin { 21 | TabController tabController; 22 | List slideData; 23 | 24 | @override 25 | void initState() { 26 | super.initState(); 27 | slideData = this.widget.data; 28 | tabController = TabController( 29 | length: slideData == null ? 0 : slideData.length, vsync: this); 30 | tabController.addListener(() { 31 | this.widget.globalKey.currentState.setSelectedIndex(tabController.index); 32 | }); 33 | } 34 | 35 | @override 36 | void dispose() { 37 | tabController.dispose(); 38 | super.dispose(); 39 | } 40 | 41 | Widget generateCard() { 42 | return Card( 43 | color: Colors.blue, 44 | child: Image.asset( 45 | "images/ic_avatar_default.png", 46 | width: 20.0, 47 | height: 20.0, 48 | ), 49 | ); 50 | } 51 | 52 | @override 53 | Widget build(BuildContext context) { 54 | List items = []; 55 | if (slideData != null && slideData.length > 0) { 56 | for (var i = 0; i < slideData.length; i++) { 57 | var item = slideData[i]; 58 | var imgUrl = item['imgUrl']; 59 | var title = item['title']; 60 | var detailUrl = item['detailUrl']; 61 | items.add(GestureDetector( 62 | onTap: () { 63 | // 点击跳转到详情 64 | Navigator.of(context).push(MaterialPageRoute( 65 | builder: (ctx) => NewsDetailPage(id: detailUrl))); 66 | }, 67 | child: Stack( 68 | children: [ 69 | Image.network(imgUrl, 70 | width: MediaQuery.of(context).size.width, fit: BoxFit.cover), 71 | Container( 72 | width: MediaQuery.of(context).size.width, 73 | color: const Color(0x50000000), 74 | child: Padding( 75 | padding: const EdgeInsets.all(6.0), 76 | child: Text(title, 77 | style: TextStyle(color: Colors.white, fontSize: 15.0)), 78 | )) 79 | ], 80 | ), 81 | )); 82 | } 83 | } 84 | // items.add(Container( 85 | // color: const Color(0x00000000), 86 | // alignment: Alignment.bottomCenter, 87 | // child: SlideViewIndicator(slideData.length), 88 | // )); 89 | return TabBarView( 90 | controller: tabController, 91 | children: items, 92 | ); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lib/widgets/SlideViewIndicator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SlideViewIndicator extends StatefulWidget { 4 | final int count; 5 | 6 | SlideViewIndicator(this.count, { Key key }) : super(key: key); 7 | 8 | @override 9 | State createState() => SlideViewIndicatorState(); 10 | } 11 | 12 | class SlideViewIndicatorState extends State { 13 | 14 | final double dotWidth = 8.0; 15 | int selectedIndex = 0; 16 | 17 | setSelectedIndex(int index) { 18 | setState(() { 19 | this.selectedIndex = index; 20 | }); 21 | } 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | List dots = []; 26 | for (int i = 0; i < this.widget.count; i++) { 27 | if (i == this.selectedIndex) { 28 | // 选中的dot 29 | dots.add(Container( 30 | width: dotWidth, 31 | height: dotWidth, 32 | decoration: BoxDecoration( 33 | color: const Color(0xffffffff), 34 | shape: BoxShape.circle 35 | ), 36 | margin: const EdgeInsets.all(3.0), 37 | )); 38 | } else { 39 | // 未选中的dot 40 | dots.add(Container( 41 | width: dotWidth, 42 | height: dotWidth, 43 | decoration: BoxDecoration( 44 | color: const Color(0xff888888), 45 | shape: BoxShape.circle 46 | ), 47 | margin: const EdgeInsets.all(3.0), 48 | )); 49 | } 50 | } 51 | return Container( 52 | height: 30.0, 53 | color: const Color(0x00000000), 54 | margin: const EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 5.0), 55 | child: Center( 56 | child: Row( 57 | children: dots, 58 | mainAxisAlignment: MainAxisAlignment.center, 59 | ) 60 | ), 61 | ); 62 | } 63 | } -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://www.dartlang.org/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.flutter-io.cn" 9 | source: hosted 10 | version: "2.1.0" 11 | barcode_scan: 12 | dependency: "direct main" 13 | description: 14 | name: barcode_scan 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "0.0.8" 18 | boolean_selector: 19 | dependency: transitive 20 | description: 21 | name: boolean_selector 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "1.0.4" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "1.1.2" 32 | collection: 33 | dependency: transitive 34 | description: 35 | name: collection 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "1.14.11" 39 | cupertino_icons: 40 | dependency: "direct main" 41 | description: 42 | name: cupertino_icons 43 | url: "https://pub.flutter-io.cn" 44 | source: hosted 45 | version: "0.1.2" 46 | event_bus: 47 | dependency: "direct main" 48 | description: 49 | name: event_bus 50 | url: "https://pub.flutter-io.cn" 51 | source: hosted 52 | version: "1.1.0" 53 | flutter: 54 | dependency: "direct main" 55 | description: flutter 56 | source: sdk 57 | version: "0.0.0" 58 | flutter_test: 59 | dependency: "direct dev" 60 | description: flutter 61 | source: sdk 62 | version: "0.0.0" 63 | flutter_webview_plugin: 64 | dependency: "direct main" 65 | description: 66 | name: flutter_webview_plugin 67 | url: "https://pub.flutter-io.cn" 68 | source: hosted 69 | version: "0.3.3" 70 | http: 71 | dependency: "direct main" 72 | description: 73 | name: http 74 | url: "https://pub.flutter-io.cn" 75 | source: hosted 76 | version: "0.12.0+2" 77 | http_parser: 78 | dependency: transitive 79 | description: 80 | name: http_parser 81 | url: "https://pub.flutter-io.cn" 82 | source: hosted 83 | version: "3.1.3" 84 | image_picker: 85 | dependency: "direct main" 86 | description: 87 | name: image_picker 88 | url: "https://pub.flutter-io.cn" 89 | source: hosted 90 | version: "0.4.12+1" 91 | matcher: 92 | dependency: transitive 93 | description: 94 | name: matcher 95 | url: "https://pub.flutter-io.cn" 96 | source: hosted 97 | version: "0.12.5" 98 | meta: 99 | dependency: transitive 100 | description: 101 | name: meta 102 | url: "https://pub.flutter-io.cn" 103 | source: hosted 104 | version: "1.1.6" 105 | path: 106 | dependency: transitive 107 | description: 108 | name: path 109 | url: "https://pub.flutter-io.cn" 110 | source: hosted 111 | version: "1.6.2" 112 | pedantic: 113 | dependency: transitive 114 | description: 115 | name: pedantic 116 | url: "https://pub.flutter-io.cn" 117 | source: hosted 118 | version: "1.5.0" 119 | quiver: 120 | dependency: transitive 121 | description: 122 | name: quiver 123 | url: "https://pub.flutter-io.cn" 124 | source: hosted 125 | version: "2.0.2" 126 | shared_preferences: 127 | dependency: "direct main" 128 | description: 129 | name: shared_preferences 130 | url: "https://pub.flutter-io.cn" 131 | source: hosted 132 | version: "0.4.3" 133 | sky_engine: 134 | dependency: transitive 135 | description: flutter 136 | source: sdk 137 | version: "0.0.99" 138 | source_span: 139 | dependency: transitive 140 | description: 141 | name: source_span 142 | url: "https://pub.flutter-io.cn" 143 | source: hosted 144 | version: "1.5.5" 145 | stack_trace: 146 | dependency: transitive 147 | description: 148 | name: stack_trace 149 | url: "https://pub.flutter-io.cn" 150 | source: hosted 151 | version: "1.9.3" 152 | stream_channel: 153 | dependency: transitive 154 | description: 155 | name: stream_channel 156 | url: "https://pub.flutter-io.cn" 157 | source: hosted 158 | version: "2.0.0" 159 | string_scanner: 160 | dependency: transitive 161 | description: 162 | name: string_scanner 163 | url: "https://pub.flutter-io.cn" 164 | source: hosted 165 | version: "1.0.4" 166 | term_glyph: 167 | dependency: transitive 168 | description: 169 | name: term_glyph 170 | url: "https://pub.flutter-io.cn" 171 | source: hosted 172 | version: "1.1.0" 173 | test_api: 174 | dependency: transitive 175 | description: 176 | name: test_api 177 | url: "https://pub.flutter-io.cn" 178 | source: hosted 179 | version: "0.2.4" 180 | typed_data: 181 | dependency: transitive 182 | description: 183 | name: typed_data 184 | url: "https://pub.flutter-io.cn" 185 | source: hosted 186 | version: "1.1.6" 187 | vector_math: 188 | dependency: transitive 189 | description: 190 | name: vector_math 191 | url: "https://pub.flutter-io.cn" 192 | source: hosted 193 | version: "2.0.8" 194 | sdks: 195 | dart: ">=2.2.0 <3.0.0" 196 | flutter: ">=0.1.4 <2.0.0" 197 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_osc 2 | description: An oschina Flutter application. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # Read more about versioning at semver.org. 10 | version: 1.0.0+1 11 | 12 | environment: 13 | sdk: ">=2.0.0-dev.68.0 <3.0.0" 14 | 15 | dependencies: 16 | flutter: 17 | sdk: flutter 18 | 19 | # The following adds the Cupertino Icons font to your application. 20 | # Use with the CupertinoIcons class for iOS style icons. 21 | cupertino_icons: ^0.1.2 22 | http: ^0.12.0 23 | flutter_webview_plugin: 0.3.3 24 | image_picker: ^0.4.10 25 | shared_preferences: ^0.4.3 26 | event_bus: ^1.0.1 27 | barcode_scan: ^0.0.4 28 | 29 | dev_dependencies: 30 | flutter_test: 31 | sdk: flutter 32 | 33 | 34 | # For information on the generic Dart part of this file, see the 35 | # following page: https://www.dartlang.org/tools/pub/pubspec 36 | 37 | # The following section is specific to Flutter. 38 | flutter: 39 | 40 | # The following line ensures that the Material Icons font is 41 | # included with your application, so that you can use the icons in 42 | # the material Icons class. 43 | uses-material-design: true 44 | 45 | # To add assets to your application, add an assets section, like this: 46 | assets: 47 | - images/ic_avatar_default.png 48 | - images/ic_nav_news_normal.png 49 | - images/ic_nav_news_actived.png 50 | - images/ic_nav_tweet_normal.png 51 | - images/ic_nav_tweet_actived.png 52 | - images/ic_nav_discover_normal.png 53 | - images/ic_nav_discover_actived.png 54 | - images/ic_nav_my_normal.png 55 | - images/ic_nav_my_pressed.png 56 | - images/ic_discover_gist.png 57 | - images/ic_discover_git.png 58 | - images/ic_discover_nearby.png 59 | - images/ic_discover_pos.png 60 | - images/ic_discover_scan.png 61 | - images/ic_discover_shake.png 62 | - images/ic_discover_softwares.png 63 | - images/ic_arrow_right.png 64 | - images/ic_my_blog.png 65 | - images/ic_my_message.png 66 | - images/ic_my_question.png 67 | - images/ic_my_recommend.png 68 | - images/ic_my_team.png 69 | - images/ic_test.png 70 | - images/ic_hongshu.jpg 71 | - images/ic_img_default.jpg 72 | - images/ic_comment.png 73 | - images/cover_img.jpg 74 | - images/leftmenu/ic_xiaoheiwu.png 75 | - images/leftmenu/ic_fabu.png 76 | - images/leftmenu/ic_about.png 77 | - images/leftmenu/ic_settings.png 78 | - images/ic_osc_logo.png 79 | - images/ic_add_pics.png 80 | - images/ic_eye.png 81 | 82 | # An image asset can refer to one or more resolution-specific "variants", see 83 | # https://flutter.io/assets-and-images/#resolution-aware. 84 | 85 | # For details regarding adding assets from package dependencies, see 86 | # https://flutter.io/assets-and-images/#from-packages 87 | 88 | # To add custom fonts to your application, add a fonts section here, 89 | # in this "flutter" section. Each entry in this list should have a 90 | # "family" key with the font family name, and a "fonts" key with a 91 | # list giving the asset and other descriptors for the font. For 92 | # example: 93 | # fonts: 94 | # - family: Schyler 95 | # fonts: 96 | # - asset: fonts/Schyler-Regular.ttf 97 | # - asset: fonts/Schyler-Italic.ttf 98 | # style: italic 99 | # - family: Trajan Pro 100 | # fonts: 101 | # - asset: fonts/TrajanPro.ttf 102 | # - asset: fonts/TrajanPro_Bold.ttf 103 | # weight: 700 104 | # 105 | # For details regarding fonts from package dependencies, 106 | # see https://flutter.io/custom-fonts/#from-packages 107 | -------------------------------------------------------------------------------- /screenshots/android01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/screenshots/android01.jpg -------------------------------------------------------------------------------- /screenshots/android02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/screenshots/android02.jpg -------------------------------------------------------------------------------- /screenshots/android03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/screenshots/android03.jpg -------------------------------------------------------------------------------- /screenshots/android04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/screenshots/android04.jpg -------------------------------------------------------------------------------- /screenshots/android05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/screenshots/android05.jpg -------------------------------------------------------------------------------- /screenshots/android06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/screenshots/android06.jpg -------------------------------------------------------------------------------- /screenshots/android07.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/screenshots/android07.jpg -------------------------------------------------------------------------------- /screenshots/android08.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/screenshots/android08.jpg -------------------------------------------------------------------------------- /screenshots/android09.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/screenshots/android09.jpg -------------------------------------------------------------------------------- /screenshots/flutter_osc_qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/screenshots/flutter_osc_qrcode.png -------------------------------------------------------------------------------- /screenshots/ios-theme-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/screenshots/ios-theme-01.png -------------------------------------------------------------------------------- /screenshots/ios-theme-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/screenshots/ios-theme-02.png -------------------------------------------------------------------------------- /screenshots/ios-theme-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/screenshots/ios-theme-03.png -------------------------------------------------------------------------------- /screenshots/ios01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/screenshots/ios01.png -------------------------------------------------------------------------------- /screenshots/ios02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/screenshots/ios02.png -------------------------------------------------------------------------------- /screenshots/ios03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/screenshots/ios03.png -------------------------------------------------------------------------------- /screenshots/ios04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/screenshots/ios04.png -------------------------------------------------------------------------------- /screenshots/ios05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/screenshots/ios05.png -------------------------------------------------------------------------------- /screenshots/ios06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/screenshots/ios06.png -------------------------------------------------------------------------------- /screenshots/ios07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/screenshots/ios07.png -------------------------------------------------------------------------------- /screenshots/ios08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/screenshots/ios08.png -------------------------------------------------------------------------------- /screenshots/ios09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/screenshots/ios09.png -------------------------------------------------------------------------------- /screenshots/ios10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/screenshots/ios10.png -------------------------------------------------------------------------------- /screenshots/qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yubo725/flutter-osc/c03ed82a855cea97847cb58a2b7e273ecd9d9337/screenshots/qrcode.png -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // To perform an interaction with a widget in your test, use the WidgetTester utility that Flutter 3 | // provides. For example, you can send tap and scroll gestures. You can also use WidgetTester to 4 | // find child widgets in the widget tree, read text, and verify that the values of widget properties 5 | // are correct. 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_test/flutter_test.dart'; 9 | 10 | import 'package:flutter_osc/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 MyOSCClient()); 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 | --------------------------------------------------------------------------------