├── .gitignore ├── .metadata ├── README.md ├── android ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── xing │ │ │ │ └── wanandroid │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-xxhdpi │ │ │ └── splash.png │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values │ │ │ ├── colors.xml │ │ │ └── styles.xml │ │ │ └── xml │ │ │ └── network_security_config.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── images ├── android_logo.jpg ├── avatar.jpeg ├── avatar_def.png ├── favorite_cancel.png ├── placeholder.png └── trophy.png ├── ios ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ └── contents.xcworkspacedata └── Runner │ ├── AppDelegate.swift │ ├── 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 │ └── Runner-Bridging-Header.h ├── lib ├── http │ ├── api.dart │ ├── http.dart │ ├── interceptor.dart │ └── response.dart ├── main.dart ├── manager │ └── userinfo_manager.dart ├── models │ ├── article.dart │ ├── article.g.dart │ ├── article_response.dart │ ├── article_response.g.dart │ ├── counter.dart │ ├── home_article.dart │ ├── home_article.g.dart │ ├── home_banner.dart │ ├── home_banner.g.dart │ ├── hot_search.dart │ ├── hot_search.g.dart │ ├── meizi.dart │ ├── meizi.g.dart │ ├── my_points.dart │ ├── my_points.g.dart │ ├── own_points.dart │ ├── own_points.g.dart │ ├── project_article.dart │ ├── project_article.g.dart │ ├── project_tab.dart │ ├── project_tab.g.dart │ ├── system_article.dart │ ├── system_article.g.dart │ ├── system_category.dart │ ├── system_category.g.dart │ ├── theme_color.dart │ └── user_info.dart ├── pages │ ├── about_page.dart │ ├── favorite_page.dart │ ├── home_page.dart │ ├── image_preview_page.dart │ ├── login_page.dart │ ├── main_page.dart │ ├── meizi_page.dart │ ├── mine_page.dart │ ├── my_points_page.dart │ ├── project_list_page.dart │ ├── project_page.dart │ ├── register_page.dart │ ├── search_history_page.dart │ ├── search_page.dart │ ├── search_result_page.dart │ ├── settings_page.dart │ ├── splash_page.dart │ ├── system_article_list_page.dart │ ├── system_category_page.dart │ ├── system_page.dart │ ├── system_square_page.dart │ └── webview_page.dart ├── provider │ ├── app_theme.dart │ ├── dark_mode.dart │ └── login_state.dart ├── res │ ├── colors.dart │ ├── strings.dart │ └── theme_colors.dart ├── utils │ ├── file_utils.dart │ ├── keyboard_utils.dart │ └── screen_utils.dart └── widgets │ ├── article_item.dart │ ├── beizier_path_painter.dart │ ├── bezier_clipper.dart │ ├── circle_degree_ring.dart │ ├── content_empty.dart │ ├── gradient_appbar.dart │ ├── header_list_view.dart │ ├── item_creator.dart │ ├── link_text.dart │ ├── painter.dart │ ├── section_item.dart │ └── xtextfield.dart ├── png ├── about.png ├── article_detail.png ├── dark_mine.png ├── dark_project.png ├── dark_setting.png ├── favorite.png ├── home.png ├── login.png ├── mine.png ├── my_points.png ├── project.png ├── register.png ├── setting.png ├── system.png ├── theme.png ├── theme_green_mine.png ├── theme_green_project.png └── theme_green_system.png ├── pubspec.lock └── pubspec.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | /build/ 31 | 32 | # Android related 33 | **/android/**/gradle-wrapper.jar 34 | **/android/.gradle 35 | **/android/captures/ 36 | **/android/gradlew 37 | **/android/gradlew.bat 38 | **/android/local.properties 39 | **/android/**/GeneratedPluginRegistrant.java 40 | 41 | # iOS/XCode related 42 | **/ios/**/*.mode1v3 43 | **/ios/**/*.mode2v3 44 | **/ios/**/*.moved-aside 45 | **/ios/**/*.pbxuser 46 | **/ios/**/*.perspectivev3 47 | **/ios/**/*sync/ 48 | **/ios/**/.sconsign.dblite 49 | **/ios/**/.tags* 50 | **/ios/**/.vagrant/ 51 | **/ios/**/DerivedData/ 52 | **/ios/**/Icon? 53 | **/ios/**/Pods/ 54 | **/ios/**/.symlinks/ 55 | **/ios/**/profile 56 | **/ios/**/xcuserdata 57 | **/ios/.generated/ 58 | **/ios/Flutter/App.framework 59 | **/ios/Flutter/Flutter.framework 60 | **/ios/Flutter/Generated.xcconfig 61 | **/ios/Flutter/app.flx 62 | **/ios/Flutter/app.zip 63 | **/ios/Flutter/flutter_assets/ 64 | **/ios/Flutter/flutter_export_environment.sh 65 | **/ios/ServiceDefinitions.json 66 | **/ios/Runner/GeneratedPluginRegistrant.* 67 | 68 | # Exceptions to above rules. 69 | !**/ios/**/default.mode1v3 70 | !**/ios/**/default.mode2v3 71 | !**/ios/**/default.pbxuser 72 | !**/ios/**/default.perspectivev3 73 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 74 | -------------------------------------------------------------------------------- /.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: 1aedbb1835bd6eb44550293d57d4d124f19901f0 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wanandroid_flutter 2 | 3 | 玩 Android 是基于 Flutter 开发的跨平台的客户端应用,包括首页,项目,体系,搜索,妹子浏览,积分,主题切换,暗黑模式等功能。 4 | 5 | 项目地址: [https://github.com/xing16/WanAndroid-Flutter](https://github.com/xing16/WanAndroid-Flutter) 6 | 7 | ### 部分效果图 8 | 9 | | ![首页](https://user-gold-cdn.xitu.io/2020/2/6/170193113623bc19?w=1080&h=1920&f=png&s=827052) | ![项目](https://user-gold-cdn.xitu.io/2020/2/6/1701931501d12c29?w=1080&h=1920&f=png&s=556203) | ![体系](https://user-gold-cdn.xitu.io/2020/2/6/17019319fbda90a5?w=1080&h=1920&f=png&s=110945) | 10 | | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | 11 | | ![我的](https://user-gold-cdn.xitu.io/2020/2/6/1701931d53221928?w=1080&h=1920&f=png&s=133422) | ![主题](https://user-gold-cdn.xitu.io/2020/2/6/17019322279af48e?w=1080&h=1920&f=png&s=145150) | ![设置](https://user-gold-cdn.xitu.io/2020/2/6/170193370931dc61?w=1080&h=1920&f=png&s=63072) | 12 | | ![详情](https://user-gold-cdn.xitu.io/2020/2/6/1701932a5d3e0d76?w=1080&h=1920&f=png&s=124802) | ![关于](https://user-gold-cdn.xitu.io/2020/2/6/1701930904e5ab95?w=1080&h=1920&f=png&s=134333) | ![](https://user-gold-cdn.xitu.io/2020/2/22/1706ccf3424eacc7?w=1080&h=1920&f=png&s=184855) | 13 | | ![登录](https://user-gold-cdn.xitu.io/2020/2/6/170193409eb3e610?w=1080&h=1920&f=png&s=54714) | ![暗黑模式](https://user-gold-cdn.xitu.io/2020/2/6/1701933a64fb52f9?w=1080&h=1920&f=png&s=62779) | ![](https://user-gold-cdn.xitu.io/2020/2/22/1706cce91878dae8?w=1080&h=1920&f=png&s=536824) | 14 | | ![项目](https://user-gold-cdn.xitu.io/2020/2/22/1706ccedad9d4cd6?w=1080&h=1920&f=png&s=555684) | ![体系](https://user-gold-cdn.xitu.io/2020/2/22/1706cceb773da9de?w=1080&h=1920&f=png&s=132800) | ![主题切换](https://user-gold-cdn.xitu.io/2020/2/6/17019325f0bdea10?w=1080&h=1920&f=png&s=133326) | 15 | 16 | ### 依赖库 17 | 18 | - Dio 19 | - flutter_swiper 20 | - webview_flutter 21 | - flutter_staggered_grid_view 22 | - json_annotation 23 | - fluttertoast 24 | - provider 25 | - shared_preferences 26 | - flutter_easyrefresh 27 | - photo_view -------------------------------------------------------------------------------- /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 plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 28 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | applicationId "com.xing.wanandroid" 41 | minSdkVersion 16 42 | targetSdkVersion 28 43 | versionCode flutterVersionCode.toInteger() 44 | versionName flutterVersionName 45 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 46 | } 47 | 48 | buildTypes { 49 | release { 50 | // Signing with the debug keys for now, so `flutter run --release` works. 51 | signingConfig signingConfigs.debug 52 | } 53 | } 54 | } 55 | 56 | flutter { 57 | source '../..' 58 | } 59 | 60 | dependencies { 61 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 62 | testImplementation 'junit:junit:4.12' 63 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 64 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 65 | } 66 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | 13 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/xing/wanandroid/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.xing.wanandroid 2 | 3 | import android.os.Bundle 4 | 5 | import io.flutter.app.FlutterActivity 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | GeneratedPluginRegistrant.registerWith(this) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/android/app/src/main/res/drawable-xxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ff0000 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.10' 3 | repositories { 4 | google() 5 | jcenter() 6 | maven { url 'https://maven.google.com' } 7 | maven { url "https://jitpack.io" } 8 | mavenCentral() // add repository 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:3.5.0' 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | google() 20 | jcenter() 21 | } 22 | } 23 | 24 | rootProject.buildDir = '../build' 25 | subprojects { 26 | project.buildDir = "${rootProject.buildDir}/${project.name}" 27 | } 28 | subprojects { 29 | project.evaluationDependsOn(':app') 30 | } 31 | 32 | task clean(type: Delete) { 33 | delete rootProject.buildDir 34 | } 35 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /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-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /images/android_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/images/android_logo.jpg -------------------------------------------------------------------------------- /images/avatar.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/images/avatar.jpeg -------------------------------------------------------------------------------- /images/avatar_def.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/images/avatar_def.png -------------------------------------------------------------------------------- /images/favorite_cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/images/favorite_cancel.png -------------------------------------------------------------------------------- /images/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/images/placeholder.png -------------------------------------------------------------------------------- /images/trophy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/images/trophy.png -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 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/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/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/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/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/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/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/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/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/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/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/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/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/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/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/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/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/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/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/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/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/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/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/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/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/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/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/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/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/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/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/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/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 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | wanandroid_flutter 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/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /lib/http/api.dart: -------------------------------------------------------------------------------- 1 | class Api { 2 | static const String BASE_URL = "https://www.wanandroid.com/"; 3 | static const String GANK_URL = "http://gank.io/"; 4 | 5 | static const String HOME_ARTICLE = "article/list/:page/json"; 6 | 7 | // 首页 banner 8 | static const String BANNER = "banner/json"; 9 | 10 | // 收藏文章列表 11 | static const String FAVORITE_LIST = "lg/collect/list/:page/json"; 12 | 13 | // 搜索 14 | static const String ARTICLE_SEARCH = "article/query/"; 15 | 16 | // 收藏,取消收藏 17 | static const String FAVORITE = "lg/collect/"; 18 | 19 | static const String UNCOLLECT_ORIGINID = "lg/uncollect_originId/"; 20 | 21 | // 收藏列表中取消收藏 22 | static const String UNCOLLECT_LIST = "lg/uncollect/"; 23 | 24 | // 登录 25 | static const String USER_LOGIN = "user/login"; 26 | 27 | // 注册 28 | static const String USER_REGISTER = "user/register"; 29 | 30 | // 知识体系 31 | static const String SYSTEM_CATEGORY = "tree/json"; 32 | 33 | // 知识体系文章列表 34 | static const String SYSTEM_ARTICLE_LIST = "article/list/:page/json?cid=:cid"; 35 | 36 | // 搜索热词 37 | static const String SEARCH_HOT = "hotkey/json"; 38 | 39 | // 项目分类 40 | static const String PROJECT_TABS = "project/tree/json"; 41 | 42 | // 项目分类 43 | static const String PROJECT_LIST = "project/list/:page/json?cid=:cid"; 44 | 45 | // 广场 46 | static const String SQUARE_ARTICLE = "user_article/list/:page/json"; 47 | 48 | // 妹子 49 | static const String GANK_MEIZI = 50 | GANK_URL + "api/data/%E7%A6%8F%E5%88%A9/:pageSize/:page"; 51 | 52 | // 我的积分 53 | static const String POINTS_OWN = "lg/coin/userinfo/json"; 54 | 55 | // 积分排行榜 56 | static const String POINTS_RANK = "coin/rank/:page/json"; 57 | } 58 | -------------------------------------------------------------------------------- /lib/http/http.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:cookie_jar/cookie_jar.dart'; 4 | import 'package:dio/dio.dart'; 5 | import 'package:dio_cookie_manager/dio_cookie_manager.dart'; 6 | import 'package:fluttertoast/fluttertoast.dart'; 7 | import 'package:wanandroid_flutter/http/api.dart'; 8 | import 'package:wanandroid_flutter/http/interceptor.dart'; 9 | import 'package:wanandroid_flutter/utils/file_utils.dart'; 10 | 11 | class HttpClient { 12 | static const int ERROR_DIO = 101; 13 | static const int ERROR_PARSE = 102; 14 | 15 | static const String GET = "GET"; 16 | static const String POST = "POST"; 17 | Dio dio; 18 | PersistCookieJar persistCookieJar; 19 | 20 | /// 私有构造函数 21 | HttpClient._internal() { 22 | dio = new Dio(); 23 | dio.options.baseUrl = Api.BASE_URL; 24 | dio.options.connectTimeout = 10 * 1000; 25 | dio.options.sendTimeout = 10 * 1000; 26 | dio.options.receiveTimeout = 10 * 1000; 27 | dio.interceptors.add(new HeaderInterceptor()); 28 | dio.interceptors 29 | .add(new LogInterceptor(requestBody: true, responseBody: true)); 30 | 31 | /// cookie 32 | getCookiePath().then((path) { 33 | persistCookieJar = new PersistCookieJar(dir: path); 34 | CookieManager cookieManager = new CookieManager(persistCookieJar); 35 | dio.interceptors.add(cookieManager); 36 | }); 37 | } 38 | 39 | /// 保存单例对象 40 | static HttpClient _client = new HttpClient._internal(); 41 | 42 | factory HttpClient() => _client; 43 | 44 | static HttpClient getInstance() { 45 | return _client; 46 | } 47 | 48 | /// GET 请求 49 | get(String path, {Map data}) async { 50 | return _request(path, GET, data: data); 51 | } 52 | 53 | /// POST 请求 54 | post(String path, {Map data}) async { 55 | return _request(path, POST, data: data); 56 | } 57 | 58 | /// 私有方法,只可本类访问 59 | _request(String path, String method, {Map data}) async { 60 | data = data ?? {}; 61 | var tempData; 62 | method = method ?? GET; 63 | if (method == GET) { 64 | data.forEach((key, value) { 65 | if (path.indexOf(key) != -1) { 66 | path = path.replaceAll(":$key", value.toString()); 67 | } 68 | }); 69 | } else if (method == POST) { 70 | tempData = new FormData.fromMap(data); 71 | } 72 | try { 73 | Response response = await dio.request(path, 74 | data: tempData, options: Options(method: method)); 75 | if (response?.statusCode != 200) { 76 | _handleError(response?.statusCode, response?.statusMessage); 77 | return; 78 | } 79 | var jsonString = json.encode(response.data); 80 | // map中的泛型为 dynamic 81 | Map dataMap = json.decode(jsonString); 82 | if (dataMap != null) { 83 | int errorCode = dataMap['errorCode']; 84 | String errorMsg = dataMap['errorMsg']; 85 | bool error = dataMap['error'] ?? true; 86 | var results = dataMap['results']; 87 | var data = dataMap['data']; 88 | // 请求失败 89 | if (errorCode != 0 && error) { 90 | _handleError(errorCode, errorMsg); 91 | return; 92 | } 93 | if (data != null) { 94 | return data; 95 | } else if (results != null) { 96 | return results; 97 | } 98 | } else { 99 | _handleError(ERROR_PARSE, "数据解析失败"); 100 | } 101 | } on DioError catch (e) { 102 | // 请求错误 103 | _handleError(ERROR_DIO, "网络连接异常"); 104 | } 105 | } 106 | 107 | void _handleError(int errorCode, String errorMsg) { 108 | print("_handleError = $errorMsg"); 109 | Fluttertoast.showToast( 110 | msg: errorMsg, 111 | toastLength: Toast.LENGTH_SHORT, 112 | gravity: ToastGravity.BOTTOM); 113 | // 未登录 114 | if (errorCode == -1001) {} 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /lib/http/interceptor.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | 3 | class HeaderInterceptor extends Interceptor { 4 | @override 5 | Future onRequest(RequestOptions options) { 6 | var path = options.path; 7 | var parameters = options.queryParameters; 8 | print("onRequest: path = $path"); 9 | print("onRequest: parameters = $parameters"); 10 | print("onRequest: options.headers = ${options.headers}"); 11 | options.headers.addAll({"developer": "xing"}); 12 | return super.onRequest(options); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/http/response.dart: -------------------------------------------------------------------------------- 1 | class WanAndResponse { 2 | int errorCode; 3 | String errorMsg; 4 | T data; 5 | 6 | WanAndResponse(this.errorCode, this.errorMsg, this.data); 7 | 8 | WanAndResponse.fromJson(Map json) { 9 | errorCode = json['errorCode']; 10 | errorMsg = json['errorMsg']; 11 | data = json['data']; 12 | } 13 | 14 | Map toJson() { 15 | final Map data = new Map(); 16 | data['errorCode'] = this.errorCode; 17 | data['errorMsg'] = this.errorMsg; 18 | data['data'] = this.data; 19 | return data; 20 | } 21 | 22 | @override 23 | String toString() { 24 | return 'WanAndResponse{errorCode: $errorCode, errorMsg: $errorMsg, data: $data}'; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/physics.dart'; 4 | import 'package:flutter/services.dart'; 5 | import 'package:flutter_localizations/flutter_localizations.dart'; 6 | import 'package:provider/provider.dart'; 7 | import 'package:pull_to_refresh/pull_to_refresh.dart'; 8 | import 'package:wanandroid_flutter/pages/main_page.dart'; 9 | import 'package:wanandroid_flutter/provider/app_theme.dart'; 10 | import 'package:wanandroid_flutter/provider/dark_mode.dart'; 11 | import 'package:wanandroid_flutter/provider/login_state.dart'; 12 | import 'package:wanandroid_flutter/res/colors.dart'; 13 | 14 | void main() { 15 | final appTheme = new AppTheme(); 16 | final darkMode = new DarkMode(); 17 | final loginState = new LoginState(); 18 | 19 | runApp( 20 | MultiProvider( 21 | providers: [ 22 | ChangeNotifierProvider.value(value: appTheme), 23 | ChangeNotifierProvider.value(value: darkMode), 24 | ChangeNotifierProvider.value(value: loginState), 25 | ], 26 | child: MyApp(), 27 | ), 28 | ); 29 | // 设置状态栏和 appbar 颜色一致 30 | if (Platform.isAndroid) { 31 | var systemUiOverlayStyle = 32 | SystemUiOverlayStyle(statusBarColor: Colors.transparent); 33 | SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle); 34 | } 35 | } 36 | 37 | class MyApp extends StatelessWidget { 38 | @override 39 | Widget build(BuildContext context) { 40 | var appTheme = Provider.of(context); 41 | var darkMode = Provider.of(context); 42 | // 全局配置子树下的SmartRefresher,下面列举几个特别重要的属性 43 | return RefreshConfiguration( 44 | // 配置默认头部指示器,假如你每个页面的头部指示器都一样的话,你需要设置这个 45 | headerBuilder: () => WaterDropHeader(), 46 | // 配置默认底部指示器 47 | footerBuilder: () => ClassicFooter( 48 | loadingText: "上拉加载更多", 49 | ), 50 | // 头部触发刷新的越界距离 51 | headerTriggerDistance: 60.0, 52 | // 自定义回弹动画,三个属性值意义请查询flutter api 53 | springDescription: 54 | SpringDescription(stiffness: 120, damping: 16, mass: 1.9), 55 | //头部最大可以拖动的范围,如果发生冲出视图范围区域,请设置这个属性 56 | maxOverScrollExtent: 60, 57 | // 底部最大可以拖动的范围 58 | maxUnderScrollExtent: 0, 59 | //这个属性不兼容PageView和TabBarView,如果你特别需要TabBarView左右滑动,你需要把它设置为true 60 | enableScrollWhenRefreshCompleted: true, 61 | //在加载失败的状态下,用户仍然可以通过手势上拉来触发加载更多 62 | enableLoadingWhenFailed: true, 63 | // Viewport不满一屏时,禁用上拉加载更多功能 64 | hideFooterWhenNotFull: true, 65 | // 可以通过惯性滑动触发加载更多 66 | enableBallisticLoad: true, 67 | child: MaterialApp( 68 | localizationsDelegates: [ 69 | // 这行是关键 70 | RefreshLocalizations.delegate, 71 | GlobalWidgetsLocalizations.delegate, 72 | GlobalMaterialLocalizations.delegate 73 | ], 74 | supportedLocales: [ 75 | const Locale('en'), 76 | const Locale('zh'), 77 | ], 78 | localeResolutionCallback: 79 | (Locale locale, Iterable supportedLocales) { 80 | //print("change language"); 81 | return locale; 82 | }, 83 | title: 'WanAndroid', 84 | debugShowCheckedModeBanner: false, 85 | theme: getTheme(appTheme.themeColor, isDarkMode: darkMode.isDark), 86 | home: MainPage(), 87 | ), 88 | ); 89 | } 90 | 91 | getTheme(Color themeColor, {bool isDarkMode = false}) { 92 | return ThemeData( 93 | // 页面背景颜色 94 | scaffoldBackgroundColor: 95 | isDarkMode ? Colours.darkAppBackground : Colours.appBackground, 96 | accentColor: isDarkMode ? Colours.darkAppSubText : Colours.appSubText, 97 | // tab 指示器颜色 98 | indicatorColor: Colors.white, 99 | backgroundColor: 100 | isDarkMode ? Colours.darkAppForeground : Colours.appForeground, 101 | // 底部菜单背景颜色 102 | bottomAppBarColor: 103 | isDarkMode ? Colours.darkAppForeground : Colours.appForeground, 104 | primaryColor: Colours.appThemeColor, 105 | primaryColorDark: Colours.appBackground, 106 | // brightness: isDarkMode ? Brightness.light : Brightness.dark, 107 | /// appBar theme 108 | appBarTheme: AppBarTheme( 109 | color: Colors.yellow, 110 | // 状态栏字体颜色 111 | brightness: Brightness.dark, 112 | iconTheme: IconThemeData(color: Colors.white), 113 | actionsIconTheme: IconThemeData( 114 | color: Colors.white, 115 | ), 116 | ), 117 | textTheme: TextTheme( 118 | // 一级文本 119 | body1: isDarkMode 120 | ? TextStyle( 121 | color: Colours.darkAppText, 122 | ) 123 | : TextStyle( 124 | color: Colours.appText, 125 | ), 126 | // subtitle: isDarkMode 127 | // ? TextStyle(color: Colors.amber) 128 | // : TextStyle(color: Colors.cyan), 129 | // 二级文本 130 | body2: isDarkMode 131 | ? TextStyle( 132 | color: Colours.darkAppSubText, 133 | fontSize: 14, 134 | ) 135 | : TextStyle( 136 | color: Colours.appSubText, 137 | fontSize: 14, 138 | ), 139 | display1: isDarkMode 140 | ? TextStyle(color: Colours.darkAppActionClip) 141 | : TextStyle(color: Colours.appActionClip), 142 | button: TextStyle(color: isDarkMode ? Colors.white30 : Colors.black54), 143 | ), 144 | inputDecorationTheme: InputDecorationTheme( 145 | enabledBorder: UnderlineInputBorder( 146 | borderSide: BorderSide( 147 | color: isDarkMode ? Colours.darkAppDivider : Colours.appDivider, 148 | width: 1, 149 | style: BorderStyle.solid, 150 | ), 151 | ), 152 | border: UnderlineInputBorder( 153 | borderSide: BorderSide( 154 | color: isDarkMode ? Colours.darkAppDivider : Colours.appDivider, 155 | width: 1, 156 | style: BorderStyle.solid, 157 | ), 158 | ), 159 | focusedBorder: UnderlineInputBorder( 160 | borderSide: BorderSide( 161 | color: isDarkMode ? Colours.darkAppDivider : Colours.appDivider, 162 | width: 1, 163 | style: BorderStyle.solid, 164 | ), 165 | ), 166 | ), 167 | dialogTheme: DialogTheme( 168 | shape: RoundedRectangleBorder( 169 | borderRadius: BorderRadius.circular(10), 170 | ), 171 | backgroundColor: 172 | isDarkMode ? Colours.darkDialogBackground : Colors.white, 173 | titleTextStyle: TextStyle( 174 | color: isDarkMode ? Colours.darkAppText : Colours.appText, 175 | fontSize: 20, 176 | ), 177 | contentTextStyle: TextStyle( 178 | color: Colors.yellow, 179 | ), 180 | ), 181 | bottomSheetTheme: BottomSheetThemeData( 182 | backgroundColor: 183 | isDarkMode ? Colours.darkAppBackground : Colours.appBackground, 184 | ), 185 | dividerColor: isDarkMode ? Colours.darkAppDivider : Colours.appDivider, 186 | cursorColor: Colours.appThemeColor, 187 | bottomAppBarTheme: BottomAppBarTheme( 188 | color: isDarkMode ? Colours.darkAppForeground : Colours.appForeground, 189 | ), 190 | toggleButtonsTheme: ToggleButtonsThemeData(color: Colors.yellow), 191 | ); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /lib/manager/userinfo_manager.dart: -------------------------------------------------------------------------------- 1 | import 'package:wanandroid_flutter/models/user_info.dart'; 2 | 3 | class UserInfoManager { 4 | UserInfo userInfo; 5 | 6 | // 工厂模式 7 | factory UserInfoManager() => getInstance(); 8 | 9 | static UserInfoManager _instance; 10 | 11 | UserInfoManager._internal() { 12 | // 初始化 13 | userInfo = UserInfo.fromMap({}); 14 | } 15 | 16 | static UserInfoManager getInstance() { 17 | if (_instance == null) { 18 | _instance = new UserInfoManager._internal(); 19 | } 20 | return _instance; 21 | } 22 | 23 | void setUserInfo(UserInfo userInfo) { 24 | this.userInfo = userInfo; 25 | } 26 | 27 | UserInfo getUserInfo() { 28 | return userInfo; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/models/article.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'article.g.dart'; 4 | 5 | /// https://caijinglong.github.io/json2dart/index.html 6 | /// 7 | /// flutter packages pub run build_runner build 8 | @JsonSerializable() 9 | class Article extends Object { 10 | @JsonKey(name: 'apkLink') 11 | String apkLink; 12 | 13 | @JsonKey(name: 'audit') 14 | int audit; 15 | 16 | @JsonKey(name: 'author') 17 | String author; 18 | 19 | @JsonKey(name: 'chapterId') 20 | int chapterId; 21 | 22 | @JsonKey(name: 'chapterName') 23 | String chapterName; 24 | 25 | @JsonKey(name: 'collect') 26 | bool collect; 27 | 28 | @JsonKey(name: 'courseId') 29 | int courseId; 30 | 31 | @JsonKey(name: 'desc') 32 | String desc; 33 | 34 | @JsonKey(name: 'envelopePic') 35 | String envelopePic; 36 | 37 | @JsonKey(name: 'fresh') 38 | bool fresh; 39 | 40 | @JsonKey(name: 'id') 41 | int id; 42 | 43 | @JsonKey(name: 'link') 44 | String link; 45 | 46 | @JsonKey(name: 'niceDate') 47 | String niceDate; 48 | 49 | @JsonKey(name: 'niceShareDate') 50 | String niceShareDate; 51 | 52 | @JsonKey(name: 'origin') 53 | String origin; 54 | 55 | @JsonKey(name: 'prefix') 56 | String prefix; 57 | 58 | @JsonKey(name: 'projectLink') 59 | String projectLink; 60 | 61 | @JsonKey(name: 'publishTime') 62 | int publishTime; 63 | 64 | @JsonKey(name: 'selfVisible') 65 | int selfVisible; 66 | 67 | @JsonKey(name: 'shareDate') 68 | int shareDate; 69 | 70 | @JsonKey(name: 'shareUser') 71 | String shareUser; 72 | 73 | @JsonKey(name: 'superChapterId') 74 | int superChapterId; 75 | 76 | @JsonKey(name: 'superChapterName') 77 | String superChapterName; 78 | 79 | @JsonKey(name: 'tags') 80 | List tags; 81 | 82 | @JsonKey(name: 'title') 83 | String title; 84 | 85 | @JsonKey(name: 'type') 86 | int type; 87 | 88 | @JsonKey(name: 'userId') 89 | int userId; 90 | 91 | @JsonKey(name: 'visible') 92 | int visible; 93 | 94 | @JsonKey(name: 'zan') 95 | int zan; 96 | 97 | Article( 98 | this.apkLink, 99 | this.audit, 100 | this.author, 101 | this.chapterId, 102 | this.chapterName, 103 | this.collect, 104 | this.courseId, 105 | this.desc, 106 | this.envelopePic, 107 | this.fresh, 108 | this.id, 109 | this.link, 110 | this.niceDate, 111 | this.niceShareDate, 112 | this.origin, 113 | this.prefix, 114 | this.projectLink, 115 | this.publishTime, 116 | this.selfVisible, 117 | this.shareDate, 118 | this.shareUser, 119 | this.superChapterId, 120 | this.superChapterName, 121 | this.tags, 122 | this.title, 123 | this.type, 124 | this.userId, 125 | this.visible, 126 | this.zan, 127 | ); 128 | 129 | factory Article.fromJson(Map srcJson) => 130 | _$ArticleFromJson(srcJson); 131 | 132 | Map toJson() => _$ArticleToJson(this); 133 | 134 | @override 135 | String toString() { 136 | return 'Article{apkLink: $apkLink, audit: $audit, author: $author, chapterId: $chapterId, chapterName: $chapterName, collect: $collect, courseId: $courseId, desc: $desc, envelopePic: $envelopePic, fresh: $fresh, id: $id, link: $link, niceDate: $niceDate, niceShareDate: $niceShareDate, origin: $origin, prefix: $prefix, projectLink: $projectLink, publishTime: $publishTime, selfVisible: $selfVisible, shareDate: $shareDate, shareUser: $shareUser, superChapterId: $superChapterId, superChapterName: $superChapterName, tags: $tags, title: $title, type: $type, userId: $userId, visible: $visible, zan: $zan}'; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /lib/models/article.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'article.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | Article _$ArticleFromJson(Map json) { 10 | return Article( 11 | json['apkLink'] as String, 12 | json['audit'] as int, 13 | json['author'] as String, 14 | json['chapterId'] as int, 15 | json['chapterName'] as String, 16 | json['collect'] as bool, 17 | json['courseId'] as int, 18 | json['desc'] as String, 19 | json['envelopePic'] as String, 20 | json['fresh'] as bool, 21 | json['id'] as int, 22 | json['link'] as String, 23 | json['niceDate'] as String, 24 | json['niceShareDate'] as String, 25 | json['origin'] as String, 26 | json['prefix'] as String, 27 | json['projectLink'] as String, 28 | json['publishTime'] as int, 29 | json['selfVisible'] as int, 30 | json['shareDate'] as int, 31 | json['shareUser'] as String, 32 | json['superChapterId'] as int, 33 | json['superChapterName'] as String, 34 | json['tags'] as List, 35 | json['title'] as String, 36 | json['type'] as int, 37 | json['userId'] as int, 38 | json['visible'] as int, 39 | json['zan'] as int, 40 | ); 41 | } 42 | 43 | Map _$ArticleToJson(Article instance) => { 44 | 'apkLink': instance.apkLink, 45 | 'audit': instance.audit, 46 | 'author': instance.author, 47 | 'chapterId': instance.chapterId, 48 | 'chapterName': instance.chapterName, 49 | 'collect': instance.collect, 50 | 'courseId': instance.courseId, 51 | 'desc': instance.desc, 52 | 'envelopePic': instance.envelopePic, 53 | 'fresh': instance.fresh, 54 | 'id': instance.id, 55 | 'link': instance.link, 56 | 'niceDate': instance.niceDate, 57 | 'niceShareDate': instance.niceShareDate, 58 | 'origin': instance.origin, 59 | 'prefix': instance.prefix, 60 | 'projectLink': instance.projectLink, 61 | 'publishTime': instance.publishTime, 62 | 'selfVisible': instance.selfVisible, 63 | 'shareDate': instance.shareDate, 64 | 'shareUser': instance.shareUser, 65 | 'superChapterId': instance.superChapterId, 66 | 'superChapterName': instance.superChapterName, 67 | 'tags': instance.tags, 68 | 'title': instance.title, 69 | 'type': instance.type, 70 | 'userId': instance.userId, 71 | 'visible': instance.visible, 72 | 'zan': instance.zan, 73 | }; 74 | -------------------------------------------------------------------------------- /lib/models/article_response.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | import 'package:wanandroid_flutter/models/article.dart'; 3 | 4 | part 'article_response.g.dart'; 5 | 6 | @JsonSerializable() 7 | class ArticleResponse extends Object { 8 | @JsonKey(name: 'curPage') 9 | int curPage; 10 | 11 | @JsonKey(name: 'datas') 12 | List
datas; 13 | 14 | @JsonKey(name: 'offset') 15 | int offset; 16 | 17 | @JsonKey(name: 'over') 18 | bool over; 19 | 20 | @JsonKey(name: 'pageCount') 21 | int pageCount; 22 | 23 | @JsonKey(name: 'size') 24 | int size; 25 | 26 | @JsonKey(name: 'total') 27 | int total; 28 | 29 | ArticleResponse( 30 | this.curPage, 31 | this.datas, 32 | this.offset, 33 | this.over, 34 | this.pageCount, 35 | this.size, 36 | this.total, 37 | ); 38 | 39 | factory ArticleResponse.fromJson(Map srcJson) => 40 | _$ArticleResponseFromJson(srcJson); 41 | 42 | Map toJson() => _$ArticleResponseToJson(this); 43 | } 44 | -------------------------------------------------------------------------------- /lib/models/article_response.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'article_response.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | ArticleResponse _$ArticleResponseFromJson(Map json) { 10 | return ArticleResponse( 11 | json['curPage'] as int, 12 | (json['datas'] as List) 13 | ?.map((e) => 14 | e == null ? null : Article.fromJson(e as Map)) 15 | ?.toList(), 16 | json['offset'] as int, 17 | json['over'] as bool, 18 | json['pageCount'] as int, 19 | json['size'] as int, 20 | json['total'] as int, 21 | ); 22 | } 23 | 24 | Map _$ArticleResponseToJson(ArticleResponse instance) => 25 | { 26 | 'curPage': instance.curPage, 27 | 'datas': instance.datas, 28 | 'offset': instance.offset, 29 | 'over': instance.over, 30 | 'pageCount': instance.pageCount, 31 | 'size': instance.size, 32 | 'total': instance.total, 33 | }; 34 | -------------------------------------------------------------------------------- /lib/models/counter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | class Counter with ChangeNotifier { 4 | int _count = 0; 5 | 6 | addCount() { 7 | _count++; 8 | notifyListeners(); 9 | } 10 | 11 | subCount() { 12 | _count--; 13 | notifyListeners(); 14 | } 15 | 16 | get count => _count; 17 | } 18 | -------------------------------------------------------------------------------- /lib/models/home_article.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | import 'package:wanandroid_flutter/models/article.dart'; 3 | 4 | part 'home_article.g.dart'; 5 | 6 | @JsonSerializable() 7 | class HomeArticle extends Object { 8 | @JsonKey(name: 'curPage') 9 | int curPage; 10 | 11 | @JsonKey(name: 'datas') 12 | List
datas; 13 | 14 | @JsonKey(name: 'offset') 15 | int offset; 16 | 17 | @JsonKey(name: 'over') 18 | bool over; 19 | 20 | @JsonKey(name: 'pageCount') 21 | int pageCount; 22 | 23 | @JsonKey(name: 'size') 24 | int size; 25 | 26 | @JsonKey(name: 'total') 27 | int total; 28 | 29 | HomeArticle( 30 | this.curPage, 31 | this.datas, 32 | this.offset, 33 | this.over, 34 | this.pageCount, 35 | this.size, 36 | this.total, 37 | ); 38 | 39 | factory HomeArticle.fromJson(Map srcJson) => 40 | _$HomeArticleFromJson(srcJson); 41 | 42 | Map toJson() => _$HomeArticleToJson(this); 43 | } 44 | -------------------------------------------------------------------------------- /lib/models/home_article.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'home_article.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | HomeArticle _$HomeArticleFromJson(Map json) { 10 | return HomeArticle( 11 | json['curPage'] as int, 12 | (json['datas'] as List) 13 | ?.map((e) => 14 | e == null ? null : Article.fromJson(e as Map)) 15 | ?.toList(), 16 | json['offset'] as int, 17 | json['over'] as bool, 18 | json['pageCount'] as int, 19 | json['size'] as int, 20 | json['total'] as int, 21 | ); 22 | } 23 | 24 | Map _$HomeArticleToJson(HomeArticle instance) => 25 | { 26 | 'curPage': instance.curPage, 27 | 'datas': instance.datas, 28 | 'offset': instance.offset, 29 | 'over': instance.over, 30 | 'pageCount': instance.pageCount, 31 | 'size': instance.size, 32 | 'total': instance.total, 33 | }; 34 | -------------------------------------------------------------------------------- /lib/models/home_banner.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'home_banner.g.dart'; 4 | 5 | @JsonSerializable() 6 | class HomeBanner extends Object { 7 | @JsonKey(name: 'desc') 8 | String desc; 9 | 10 | @JsonKey(name: 'id') 11 | int id; 12 | 13 | @JsonKey(name: 'imagePath') 14 | String imagePath; 15 | 16 | @JsonKey(name: 'isVisible') 17 | int isVisible; 18 | 19 | @JsonKey(name: 'order') 20 | int order; 21 | 22 | @JsonKey(name: 'title') 23 | String title; 24 | 25 | @JsonKey(name: 'type') 26 | int type; 27 | 28 | @JsonKey(name: 'url') 29 | String url; 30 | 31 | HomeBanner( 32 | this.desc, 33 | this.id, 34 | this.imagePath, 35 | this.isVisible, 36 | this.order, 37 | this.title, 38 | this.type, 39 | this.url, 40 | ); 41 | 42 | factory HomeBanner.fromJson(Map srcJson) => 43 | _$HomeBannerFromJson(srcJson); 44 | 45 | Map toJson() => _$HomeBannerToJson(this); 46 | } 47 | -------------------------------------------------------------------------------- /lib/models/home_banner.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'home_banner.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | HomeBanner _$HomeBannerFromJson(Map json) { 10 | return HomeBanner( 11 | json['desc'] as String, 12 | json['id'] as int, 13 | json['imagePath'] as String, 14 | json['isVisible'] as int, 15 | json['order'] as int, 16 | json['title'] as String, 17 | json['type'] as int, 18 | json['url'] as String, 19 | ); 20 | } 21 | 22 | Map _$HomeBannerToJson(HomeBanner instance) => 23 | { 24 | 'desc': instance.desc, 25 | 'id': instance.id, 26 | 'imagePath': instance.imagePath, 27 | 'isVisible': instance.isVisible, 28 | 'order': instance.order, 29 | 'title': instance.title, 30 | 'type': instance.type, 31 | 'url': instance.url, 32 | }; 33 | -------------------------------------------------------------------------------- /lib/models/hot_search.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'hot_search.g.dart'; 4 | 5 | @JsonSerializable() 6 | class HotSearch extends Object { 7 | @JsonKey(name: 'id') 8 | int id; 9 | 10 | @JsonKey(name: 'link') 11 | String link; 12 | 13 | @JsonKey(name: 'name') 14 | String name; 15 | 16 | @JsonKey(name: 'order') 17 | int order; 18 | 19 | @JsonKey(name: 'visible') 20 | int visible; 21 | 22 | HotSearch( 23 | this.id, 24 | this.link, 25 | this.name, 26 | this.order, 27 | this.visible, 28 | ); 29 | 30 | factory HotSearch.fromJson(Map srcJson) => 31 | _$HotSearchFromJson(srcJson); 32 | 33 | Map toJson() => _$HotSearchToJson(this); 34 | } 35 | -------------------------------------------------------------------------------- /lib/models/hot_search.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'hot_search.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | HotSearch _$HotSearchFromJson(Map json) { 10 | return HotSearch( 11 | json['id'] as int, 12 | json['link'] as String, 13 | json['name'] as String, 14 | json['order'] as int, 15 | json['visible'] as int, 16 | ); 17 | } 18 | 19 | Map _$HotSearchToJson(HotSearch instance) => { 20 | 'id': instance.id, 21 | 'link': instance.link, 22 | 'name': instance.name, 23 | 'order': instance.order, 24 | 'visible': instance.visible, 25 | }; 26 | -------------------------------------------------------------------------------- /lib/models/meizi.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'meizi.g.dart'; 4 | 5 | @JsonSerializable() 6 | class Meizi extends Object { 7 | @JsonKey(name: '_id') 8 | String id; 9 | 10 | @JsonKey(name: 'createdAt') 11 | String createdAt; 12 | 13 | @JsonKey(name: 'desc') 14 | String desc; 15 | 16 | @JsonKey(name: 'publishedAt') 17 | String publishedAt; 18 | 19 | @JsonKey(name: 'source') 20 | String source; 21 | 22 | @JsonKey(name: 'type') 23 | String type; 24 | 25 | @JsonKey(name: 'url') 26 | String url; 27 | 28 | @JsonKey(name: 'used') 29 | bool used; 30 | 31 | @JsonKey(name: 'who') 32 | String who; 33 | 34 | Meizi( 35 | this.id, 36 | this.createdAt, 37 | this.desc, 38 | this.publishedAt, 39 | this.source, 40 | this.type, 41 | this.url, 42 | this.used, 43 | this.who, 44 | ); 45 | 46 | factory Meizi.fromJson(Map srcJson) => 47 | _$MeiziFromJson(srcJson); 48 | 49 | Map toJson() => _$MeiziToJson(this); 50 | } 51 | -------------------------------------------------------------------------------- /lib/models/meizi.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'meizi.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | Meizi _$MeiziFromJson(Map json) { 10 | return Meizi( 11 | json['_id'] as String, 12 | json['createdAt'] as String, 13 | json['desc'] as String, 14 | json['publishedAt'] as String, 15 | json['source'] as String, 16 | json['type'] as String, 17 | json['url'] as String, 18 | json['used'] as bool, 19 | json['who'] as String, 20 | ); 21 | } 22 | 23 | Map _$MeiziToJson(Meizi instance) => { 24 | '_id': instance.id, 25 | 'createdAt': instance.createdAt, 26 | 'desc': instance.desc, 27 | 'publishedAt': instance.publishedAt, 28 | 'source': instance.source, 29 | 'type': instance.type, 30 | 'url': instance.url, 31 | 'used': instance.used, 32 | 'who': instance.who, 33 | }; 34 | -------------------------------------------------------------------------------- /lib/models/my_points.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'my_points.g.dart'; 4 | 5 | @JsonSerializable() 6 | class PointsRank extends Object { 7 | @JsonKey(name: 'curPage') 8 | int curPage; 9 | 10 | @JsonKey(name: 'datas') 11 | List datas; 12 | 13 | @JsonKey(name: 'offset') 14 | int offset; 15 | 16 | @JsonKey(name: 'over') 17 | bool over; 18 | 19 | @JsonKey(name: 'pageCount') 20 | int pageCount; 21 | 22 | @JsonKey(name: 'size') 23 | int size; 24 | 25 | @JsonKey(name: 'total') 26 | int total; 27 | 28 | PointsRank( 29 | this.curPage, 30 | this.datas, 31 | this.offset, 32 | this.over, 33 | this.pageCount, 34 | this.size, 35 | this.total, 36 | ); 37 | 38 | factory PointsRank.fromJson(Map srcJson) => 39 | _$PointsRankFromJson(srcJson); 40 | 41 | Map toJson() => _$PointsRankToJson(this); 42 | } 43 | 44 | @JsonSerializable() 45 | class UserPoints extends Object { 46 | @JsonKey(name: 'coinCount') 47 | int coinCount; 48 | 49 | @JsonKey(name: 'level') 50 | int level; 51 | 52 | @JsonKey(name: 'rank') 53 | int rank; 54 | 55 | @JsonKey(name: 'userId') 56 | int userId; 57 | 58 | @JsonKey(name: 'username') 59 | String username; 60 | 61 | UserPoints( 62 | this.coinCount, 63 | this.level, 64 | this.rank, 65 | this.userId, 66 | this.username, 67 | ); 68 | 69 | factory UserPoints.fromJson(Map srcJson) => 70 | _$UserPointsFromJson(srcJson); 71 | 72 | Map toJson() => _$UserPointsToJson(this); 73 | } 74 | -------------------------------------------------------------------------------- /lib/models/my_points.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'my_points.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | PointsRank _$PointsRankFromJson(Map json) { 10 | return PointsRank( 11 | json['curPage'] as int, 12 | (json['datas'] as List) 13 | ?.map((e) => 14 | e == null ? null : UserPoints.fromJson(e as Map)) 15 | ?.toList(), 16 | json['offset'] as int, 17 | json['over'] as bool, 18 | json['pageCount'] as int, 19 | json['size'] as int, 20 | json['total'] as int, 21 | ); 22 | } 23 | 24 | Map _$PointsRankToJson(PointsRank instance) => 25 | { 26 | 'curPage': instance.curPage, 27 | 'datas': instance.datas, 28 | 'offset': instance.offset, 29 | 'over': instance.over, 30 | 'pageCount': instance.pageCount, 31 | 'size': instance.size, 32 | 'total': instance.total, 33 | }; 34 | 35 | UserPoints _$UserPointsFromJson(Map json) { 36 | return UserPoints( 37 | json['coinCount'] as int, 38 | json['level'] as int, 39 | json['rank'] as int, 40 | json['userId'] as int, 41 | json['username'] as String, 42 | ); 43 | } 44 | 45 | Map _$UserPointsToJson(UserPoints instance) => 46 | { 47 | 'coinCount': instance.coinCount, 48 | 'level': instance.level, 49 | 'rank': instance.rank, 50 | 'userId': instance.userId, 51 | 'username': instance.username, 52 | }; 53 | -------------------------------------------------------------------------------- /lib/models/own_points.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'own_points.g.dart'; 4 | 5 | @JsonSerializable() 6 | class OwnPoints extends Object { 7 | @JsonKey(name: 'coinCount') 8 | int coinCount; 9 | 10 | @JsonKey(name: 'level') 11 | int level; 12 | 13 | @JsonKey(name: 'rank') 14 | int rank; 15 | 16 | @JsonKey(name: 'userId') 17 | int userId; 18 | 19 | @JsonKey(name: 'username') 20 | String username; 21 | 22 | OwnPoints( 23 | this.coinCount, 24 | this.level, 25 | this.rank, 26 | this.userId, 27 | this.username, 28 | ); 29 | 30 | factory OwnPoints.fromJson(Map srcJson) => 31 | _$OwnPointsFromJson(srcJson); 32 | 33 | Map toJson() => _$OwnPointsToJson(this); 34 | } 35 | -------------------------------------------------------------------------------- /lib/models/own_points.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'own_points.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | OwnPoints _$OwnPointsFromJson(Map json) { 10 | return OwnPoints( 11 | json['coinCount'] as int, 12 | json['level'] as int, 13 | json['rank'] as int, 14 | json['userId'] as int, 15 | json['username'] as String, 16 | ); 17 | } 18 | 19 | Map _$OwnPointsToJson(OwnPoints instance) => { 20 | 'coinCount': instance.coinCount, 21 | 'level': instance.level, 22 | 'rank': instance.rank, 23 | 'userId': instance.userId, 24 | 'username': instance.username, 25 | }; 26 | -------------------------------------------------------------------------------- /lib/models/project_article.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | import 'package:wanandroid_flutter/models/article.dart'; 3 | 4 | part 'project_article.g.dart'; 5 | 6 | @JsonSerializable() 7 | class ProjectArticle extends Object { 8 | @JsonKey(name: 'curPage') 9 | int curPage; 10 | 11 | @JsonKey(name: 'datas') 12 | List
datas; 13 | 14 | @JsonKey(name: 'offset') 15 | int offset; 16 | 17 | @JsonKey(name: 'over') 18 | bool over; 19 | 20 | @JsonKey(name: 'pageCount') 21 | int pageCount; 22 | 23 | @JsonKey(name: 'size') 24 | int size; 25 | 26 | @JsonKey(name: 'total') 27 | int total; 28 | 29 | ProjectArticle( 30 | this.curPage, 31 | this.datas, 32 | this.offset, 33 | this.over, 34 | this.pageCount, 35 | this.size, 36 | this.total, 37 | ); 38 | 39 | factory ProjectArticle.fromJson(Map srcJson) => 40 | _$ProjectArticleFromJson(srcJson); 41 | 42 | Map toJson() => _$ProjectArticleToJson(this); 43 | } 44 | 45 | @JsonSerializable() 46 | class Tags extends Object { 47 | @JsonKey(name: 'name') 48 | String name; 49 | 50 | @JsonKey(name: 'url') 51 | String url; 52 | 53 | Tags( 54 | this.name, 55 | this.url, 56 | ); 57 | 58 | factory Tags.fromJson(Map srcJson) => 59 | _$TagsFromJson(srcJson); 60 | 61 | Map toJson() => _$TagsToJson(this); 62 | } 63 | -------------------------------------------------------------------------------- /lib/models/project_article.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'project_article.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | ProjectArticle _$ProjectArticleFromJson(Map json) { 10 | return ProjectArticle( 11 | json['curPage'] as int, 12 | (json['datas'] as List) 13 | ?.map((e) => 14 | e == null ? null : Article.fromJson(e as Map)) 15 | ?.toList(), 16 | json['offset'] as int, 17 | json['over'] as bool, 18 | json['pageCount'] as int, 19 | json['size'] as int, 20 | json['total'] as int, 21 | ); 22 | } 23 | 24 | Map _$ProjectArticleToJson(ProjectArticle instance) => 25 | { 26 | 'curPage': instance.curPage, 27 | 'datas': instance.datas, 28 | 'offset': instance.offset, 29 | 'over': instance.over, 30 | 'pageCount': instance.pageCount, 31 | 'size': instance.size, 32 | 'total': instance.total, 33 | }; 34 | 35 | Tags _$TagsFromJson(Map json) { 36 | return Tags( 37 | json['name'] as String, 38 | json['url'] as String, 39 | ); 40 | } 41 | 42 | Map _$TagsToJson(Tags instance) => { 43 | 'name': instance.name, 44 | 'url': instance.url, 45 | }; 46 | -------------------------------------------------------------------------------- /lib/models/project_tab.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'project_tab.g.dart'; 4 | 5 | @JsonSerializable() 6 | class ProjectTab extends Object { 7 | @JsonKey(name: 'children') 8 | List children; 9 | 10 | @JsonKey(name: 'courseId') 11 | int courseId; 12 | 13 | @JsonKey(name: 'id') 14 | int id; 15 | 16 | @JsonKey(name: 'name') 17 | String name; 18 | 19 | @JsonKey(name: 'order') 20 | int order; 21 | 22 | @JsonKey(name: 'parentChapterId') 23 | int parentChapterId; 24 | 25 | @JsonKey(name: 'userControlSetTop') 26 | bool userControlSetTop; 27 | 28 | @JsonKey(name: 'visible') 29 | int visible; 30 | 31 | ProjectTab( 32 | this.children, 33 | this.courseId, 34 | this.id, 35 | this.name, 36 | this.order, 37 | this.parentChapterId, 38 | this.userControlSetTop, 39 | this.visible, 40 | ); 41 | 42 | factory ProjectTab.fromJson(Map srcJson) => 43 | _$ProjectTabFromJson(srcJson); 44 | 45 | Map toJson() => _$ProjectTabToJson(this); 46 | } 47 | -------------------------------------------------------------------------------- /lib/models/project_tab.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'project_tab.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | ProjectTab _$ProjectTabFromJson(Map json) { 10 | return ProjectTab( 11 | json['children'] as List, 12 | json['courseId'] as int, 13 | json['id'] as int, 14 | json['name'] as String, 15 | json['order'] as int, 16 | json['parentChapterId'] as int, 17 | json['userControlSetTop'] as bool, 18 | json['visible'] as int, 19 | ); 20 | } 21 | 22 | Map _$ProjectTabToJson(ProjectTab instance) => 23 | { 24 | 'children': instance.children, 25 | 'courseId': instance.courseId, 26 | 'id': instance.id, 27 | 'name': instance.name, 28 | 'order': instance.order, 29 | 'parentChapterId': instance.parentChapterId, 30 | 'userControlSetTop': instance.userControlSetTop, 31 | 'visible': instance.visible, 32 | }; 33 | -------------------------------------------------------------------------------- /lib/models/system_article.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | import 'package:wanandroid_flutter/models/article.dart'; 3 | 4 | part 'system_article.g.dart'; 5 | 6 | @JsonSerializable() 7 | class SystemArticle extends Object { 8 | @JsonKey(name: 'curPage') 9 | int curPage; 10 | 11 | @JsonKey(name: 'datas') 12 | List
datas; 13 | 14 | @JsonKey(name: 'offset') 15 | int offset; 16 | 17 | @JsonKey(name: 'over') 18 | bool over; 19 | 20 | @JsonKey(name: 'pageCount') 21 | int pageCount; 22 | 23 | @JsonKey(name: 'size') 24 | int size; 25 | 26 | @JsonKey(name: 'total') 27 | int total; 28 | 29 | SystemArticle( 30 | this.curPage, 31 | this.datas, 32 | this.offset, 33 | this.over, 34 | this.pageCount, 35 | this.size, 36 | this.total, 37 | ); 38 | 39 | factory SystemArticle.fromJson(Map srcJson) => 40 | _$SystemArticleFromJson(srcJson); 41 | 42 | Map toJson() => _$SystemArticleToJson(this); 43 | 44 | @override 45 | String toString() { 46 | return 'SquareArticle{curPage: $curPage, datas: $datas, offset: $offset, over: $over, pageCount: $pageCount, size: $size, total: $total}'; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/models/system_article.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'system_article.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | SystemArticle _$SystemArticleFromJson(Map json) { 10 | return SystemArticle( 11 | json['curPage'] as int, 12 | (json['datas'] as List) 13 | ?.map((e) => 14 | e == null ? null : Article.fromJson(e as Map)) 15 | ?.toList(), 16 | json['offset'] as int, 17 | json['over'] as bool, 18 | json['pageCount'] as int, 19 | json['size'] as int, 20 | json['total'] as int, 21 | ); 22 | } 23 | 24 | Map _$SystemArticleToJson(SystemArticle instance) => 25 | { 26 | 'curPage': instance.curPage, 27 | 'datas': instance.datas, 28 | 'offset': instance.offset, 29 | 'over': instance.over, 30 | 'pageCount': instance.pageCount, 31 | 'size': instance.size, 32 | 'total': instance.total, 33 | }; 34 | -------------------------------------------------------------------------------- /lib/models/system_category.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'system_category.g.dart'; 4 | 5 | @JsonSerializable() 6 | class SystemCategory extends Object { 7 | @JsonKey(name: 'children') 8 | List children; 9 | 10 | @JsonKey(name: 'courseId') 11 | int courseId; 12 | 13 | @JsonKey(name: 'id') 14 | int id; 15 | 16 | @JsonKey(name: 'name') 17 | String name; 18 | 19 | @JsonKey(name: 'order') 20 | int order; 21 | 22 | @JsonKey(name: 'parentChapterId') 23 | int parentChapterId; 24 | 25 | @JsonKey(name: 'userControlSetTop') 26 | bool userControlSetTop; 27 | 28 | @JsonKey(name: 'visible') 29 | int visible; 30 | 31 | SystemCategory( 32 | this.children, 33 | this.courseId, 34 | this.id, 35 | this.name, 36 | this.order, 37 | this.parentChapterId, 38 | this.userControlSetTop, 39 | this.visible, 40 | ); 41 | 42 | factory SystemCategory.fromJson(Map srcJson) => 43 | _$SystemCategoryFromJson(srcJson); 44 | 45 | Map toJson() => _$SystemCategoryToJson(this); 46 | } 47 | -------------------------------------------------------------------------------- /lib/models/system_category.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'system_category.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | SystemCategory _$SystemCategoryFromJson(Map json) { 10 | return SystemCategory( 11 | (json['children'] as List) 12 | ?.map((e) => e == null 13 | ? null 14 | : SystemCategory.fromJson(e as Map)) 15 | ?.toList(), 16 | json['courseId'] as int, 17 | json['id'] as int, 18 | json['name'] as String, 19 | json['order'] as int, 20 | json['parentChapterId'] as int, 21 | json['userControlSetTop'] as bool, 22 | json['visible'] as int, 23 | ); 24 | } 25 | 26 | Map _$SystemCategoryToJson(SystemCategory instance) => 27 | { 28 | 'children': instance.children, 29 | 'courseId': instance.courseId, 30 | 'id': instance.id, 31 | 'name': instance.name, 32 | 'order': instance.order, 33 | 'parentChapterId': instance.parentChapterId, 34 | 'userControlSetTop': instance.userControlSetTop, 35 | 'visible': instance.visible, 36 | }; 37 | -------------------------------------------------------------------------------- /lib/models/theme_color.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | class ThemeColor { 4 | int index; 5 | Color color; 6 | 7 | ThemeColor(this.index, this.color); 8 | } 9 | -------------------------------------------------------------------------------- /lib/models/user_info.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'user_info.g.dart'; 4 | 5 | @JsonSerializable() 6 | class UserInfo extends Object { 7 | @JsonKey(name: 'admin') 8 | bool admin; 9 | 10 | @JsonKey(name: 'chapterTops') 11 | List chapterTops; 12 | 13 | @JsonKey(name: 'collectIds') 14 | List collectIds; 15 | 16 | @JsonKey(name: 'email') 17 | String email; 18 | 19 | @JsonKey(name: 'icon') 20 | String icon; 21 | 22 | @JsonKey(name: 'id') 23 | int id; 24 | 25 | @JsonKey(name: 'nickname') 26 | String nickname; 27 | 28 | @JsonKey(name: 'password') 29 | String password; 30 | 31 | @JsonKey(name: 'publicName') 32 | String publicName; 33 | 34 | @JsonKey(name: 'token') 35 | String token; 36 | 37 | @JsonKey(name: 'type') 38 | int type; 39 | 40 | @JsonKey(name: 'username') 41 | String username; 42 | 43 | UserInfo.fromMap(Map map); 44 | 45 | UserInfo( 46 | this.admin, 47 | this.chapterTops, 48 | this.collectIds, 49 | this.email, 50 | this.icon, 51 | this.id, 52 | this.nickname, 53 | this.password, 54 | this.publicName, 55 | this.token, 56 | this.type, 57 | this.username, 58 | ); 59 | 60 | factory UserInfo.fromJson(Map srcJson) => 61 | _$UserInfoFromJson(srcJson); 62 | 63 | Map toJson() => _$UserInfoToJson(this); 64 | 65 | @override 66 | String toString() { 67 | return 'UserInfo{admin: $admin, chapterTops: $chapterTops, collectIds: $collectIds, email: $email, icon: $icon, id: $id, nickname: $nickname, password: $password, publicName: $publicName, token: $token, type: $type, username: $username}'; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/pages/about_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:wanandroid_flutter/widgets/gradient_appbar.dart'; 3 | import 'package:wanandroid_flutter/widgets/link_text.dart'; 4 | 5 | class AboutPage extends StatefulWidget { 6 | @override 7 | State createState() { 8 | return AboutPageState(); 9 | } 10 | } 11 | 12 | class AboutPageState extends State { 13 | double screenWidth = 0; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | screenWidth = MediaQuery.of(context).size.width; 18 | TextStyle linkTextStyle = Theme.of(context).textTheme.body1.copyWith( 19 | color: Colors.blueAccent, 20 | fontSize: 15, 21 | decoration: TextDecoration.underline, 22 | ); 23 | return Scaffold( 24 | backgroundColor: Theme.of(context).backgroundColor, 25 | appBar: GradientAppBar( 26 | title: Text("关于"), 27 | ), 28 | body: SingleChildScrollView( 29 | child: Container( 30 | padding: EdgeInsets.all(12), 31 | child: Column( 32 | crossAxisAlignment: CrossAxisAlignment.start, 33 | children: [ 34 | Container( 35 | child: Text( 36 | "简介", 37 | style: TextStyle( 38 | fontSize: 18, 39 | fontWeight: FontWeight.bold, 40 | ), 41 | ), 42 | ), 43 | Container( 44 | margin: EdgeInsets.only( 45 | top: 10, 46 | bottom: 10, 47 | ), 48 | child: Divider( 49 | height: 1, 50 | ), 51 | ), 52 | Text( 53 | "玩 Android 是基于 Flutter 开发的跨平台的客户端应用,包括首页,项目,体系,搜索,妹子浏览,积分,主题切换,暗黑模式等功能。"), 54 | Container( 55 | margin: EdgeInsets.only( 56 | top: 10, 57 | bottom: 10, 58 | ), 59 | child: Row( 60 | children: [ 61 | Text("项目地址:"), 62 | LinkText( 63 | "https://github.com/xing16/WanAndroid-Flutter", 64 | "https://github.com/xing16/WanAndroid-Flutter", 65 | linkTextStyle, 66 | ), 67 | ], 68 | ), 69 | ), 70 | Container( 71 | child: Text( 72 | "依赖库", 73 | style: TextStyle( 74 | fontSize: 18, 75 | fontWeight: FontWeight.bold, 76 | ), 77 | ), 78 | ), 79 | Container( 80 | margin: EdgeInsets.only( 81 | top: 10, 82 | bottom: 10, 83 | ), 84 | child: Divider( 85 | height: 1, 86 | ), 87 | ), 88 | LinkText( 89 | "Dio", 90 | "https://github.com/flutterchina/dio", 91 | linkTextStyle, 92 | ), 93 | LinkText( 94 | "flutter_swiper", 95 | "https://github.com/best-flutter/flutter_swiper", 96 | linkTextStyle, 97 | ), 98 | LinkText( 99 | "webview_flutter", 100 | "https://github.com/apptreesoftware/flutter_webview", 101 | linkTextStyle, 102 | ), 103 | LinkText( 104 | "flutter_staggered_grid_view", 105 | "https://github.com/letsar/flutter_staggered_grid_view", 106 | linkTextStyle, 107 | ), 108 | LinkText( 109 | "json_annotation", 110 | "https://pub.dev/packages/json_annotation", 111 | linkTextStyle, 112 | ), 113 | LinkText( 114 | "fluttertoast", 115 | "https://pub.dev/packages/fluttertoast", 116 | linkTextStyle, 117 | ), 118 | LinkText( 119 | "provider", 120 | "https://pub.dev/packages/provider", 121 | linkTextStyle, 122 | ), 123 | LinkText( 124 | "shared_preferences", 125 | "https://pub.dev/packages/shared_preferences", 126 | linkTextStyle, 127 | ), 128 | LinkText( 129 | "flutter_easyrefresh", 130 | "https://github.com/xuelongqy/flutter_easyrefresh", 131 | linkTextStyle, 132 | ), 133 | LinkText( 134 | "photo_view", 135 | "https://github.com/renancaraujo/photo_view", 136 | linkTextStyle, 137 | ), 138 | ], 139 | ), 140 | ), 141 | ), 142 | ); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /lib/pages/favorite_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:pull_to_refresh/pull_to_refresh.dart'; 4 | import 'package:wanandroid_flutter/models/article.dart'; 5 | import 'package:wanandroid_flutter/pages/webview_page.dart'; 6 | import 'package:wanandroid_flutter/widgets/article_item.dart'; 7 | import 'package:wanandroid_flutter/widgets/gradient_appbar.dart'; 8 | import 'package:wanandroid_flutter/http/http.dart'; 9 | import 'package:wanandroid_flutter/http/api.dart'; 10 | import 'package:wanandroid_flutter/models/article_response.dart'; 11 | import 'package:flutter_slidable/flutter_slidable.dart'; 12 | 13 | class FavoritePage extends StatefulWidget { 14 | @override 15 | State createState() { 16 | return FavoritePageState(); 17 | } 18 | } 19 | 20 | class FavoritePageState extends State { 21 | double screenWidth = 0; 22 | List
articleList = new List(); 23 | List list = new List(); 24 | int currentPage = 0; 25 | RefreshController _refreshController = 26 | RefreshController(initialRefresh: false); 27 | final SlidableController slidableController = SlidableController(); 28 | 29 | @override 30 | void initState() { 31 | super.initState(); 32 | _onRefresh(); 33 | } 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | screenWidth = MediaQuery.of(context).size.width; 38 | return Scaffold( 39 | appBar: GradientAppBar( 40 | title: Text("收藏"), 41 | ), 42 | body: SmartRefresher( 43 | enablePullDown: true, 44 | enablePullUp: true, 45 | header: WaterDropHeader(), 46 | footer: CustomFooter( 47 | builder: (BuildContext context, LoadStatus mode) { 48 | Widget body; 49 | if (mode == LoadStatus.idle) { 50 | body = Text("上拉加载"); 51 | } else if (mode == LoadStatus.loading) { 52 | body = CupertinoActivityIndicator(); 53 | } else if (mode == LoadStatus.failed) { 54 | body = Text("加载失败!点击重试!"); 55 | } else if (mode == LoadStatus.canLoading) { 56 | body = Text("上拉加载更多"); 57 | } else { 58 | body = Text("没有更多数据了!"); 59 | } 60 | return Container( 61 | height: 55.0, 62 | child: Center(child: body), 63 | ); 64 | }, 65 | ), 66 | controller: _refreshController, 67 | onRefresh: _onRefresh, 68 | onLoading: _onLoadMore, 69 | child: ListView.separated( 70 | itemBuilder: (context, index) { 71 | return createFavoriteListItem(context, index); 72 | }, 73 | separatorBuilder: (context, index) { 74 | return Container( 75 | margin: EdgeInsets.only( 76 | left: 12, 77 | right: 12, 78 | ), 79 | color: Colors.black12, 80 | height: 0.5, 81 | ); 82 | }, 83 | itemCount: articleList.length, 84 | ), 85 | ), 86 | ); 87 | } 88 | 89 | createFavoriteListItem(BuildContext context, int index) { 90 | Article article = articleList[index]; 91 | String author; 92 | if (article.author != null) { 93 | author = article.author; 94 | } else if (article.shareUser != null) { 95 | author = article.shareUser; 96 | } else { 97 | author = ""; 98 | } 99 | return Slidable( 100 | // slidableController 控制只有一个 slidable 处于打开状态 101 | controller: slidableController, 102 | actionPane: SlidableStrechActionPane(), 103 | actionExtentRatio: 0.25, 104 | child: ArticleItem( 105 | article.title, 106 | article.niceDate, 107 | author, 108 | () { 109 | Navigator.push( 110 | context, 111 | MaterialPageRoute( 112 | builder: (BuildContext context) => WebViewPage( 113 | url: article.link, 114 | ), 115 | ), 116 | ); 117 | }, 118 | ), 119 | secondaryActions: [ 120 | IconSlideAction( 121 | iconWidget: Image.asset( 122 | "images/favorite_cancel.png", 123 | width: 30, 124 | height: 30, 125 | ), 126 | caption: 'cancel favorite', 127 | color: Colors.red, 128 | // icon: Icons.delete, 129 | onTap: () => _cancelFavorite(index), 130 | ), 131 | ], 132 | ); 133 | } 134 | 135 | /// 请求数据 136 | _loadFavorites(int page) async { 137 | var result = await HttpClient.getInstance() 138 | .get(Api.FAVORITE_LIST, data: {"page": page}); 139 | return result; 140 | } 141 | 142 | /// 取消收藏 143 | _cancelFavorite(int index) async { 144 | Article article = articleList[index]; 145 | await HttpClient.getInstance().post( 146 | Api.UNCOLLECT_LIST + "${article.id}/json", 147 | data: {"originId": "-1"}); 148 | setState(() { 149 | articleList.removeAt(index); 150 | }); 151 | } 152 | 153 | /// 下拉刷新 154 | void _onRefresh() async { 155 | var result = await _loadFavorites(0); 156 | currentPage = 0; 157 | if (result != null) { 158 | ArticleResponse articleResponse = ArticleResponse.fromJson(result); 159 | List
articles = articleResponse.datas; 160 | setState(() { 161 | articleList.clear(); 162 | articleList.addAll(articles); 163 | }); 164 | } 165 | // 下拉刷新完成重置footer状态,否则此后无法上拉加载更多 166 | _refreshController.refreshCompleted(resetFooterState: true); 167 | } 168 | 169 | /// 上拉加载更多 170 | void _onLoadMore() async { 171 | var result = await _loadFavorites(currentPage); 172 | currentPage++; 173 | if (result != null) { 174 | ArticleResponse articleResponse = ArticleResponse.fromJson(result); 175 | List
articles = articleResponse.datas; 176 | if (articles.length < articleResponse.size) { 177 | _refreshController.loadNoData(); 178 | } else { 179 | _refreshController.loadComplete(); 180 | } 181 | setState(() { 182 | articleList.addAll(articles); 183 | }); 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /lib/pages/image_preview_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:photo_view/photo_view.dart'; 4 | import 'package:photo_view/photo_view_gallery.dart'; 5 | import 'package:wanandroid_flutter/utils/screen_utils.dart'; 6 | 7 | class ImagePreviewPage extends StatefulWidget { 8 | final List imageUrls; 9 | final int currentIndex; 10 | 11 | ImagePreviewPage(this.imageUrls, this.currentIndex); 12 | 13 | @override 14 | State createState() { 15 | return ImagePreviewPageState(); 16 | } 17 | } 18 | 19 | class ImagePreviewPageState extends State { 20 | int currentIndex = 0; 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | currentIndex = widget.currentIndex; 26 | } 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return Scaffold( 31 | body: Stack( 32 | children: [ 33 | Positioned( 34 | top: 0, 35 | left: 0, 36 | bottom: 0, 37 | right: 0, 38 | child: Container( 39 | width: getScreenWidth(context), 40 | height: getScreenHeight(context), 41 | child: PhotoViewGallery.builder( 42 | scrollPhysics: const BouncingScrollPhysics(), 43 | builder: (BuildContext context, int index) { 44 | return PhotoViewGalleryPageOptions( 45 | onTapUp: ( 46 | BuildContext context, 47 | TapUpDetails details, 48 | PhotoViewControllerValue controllerValue, 49 | ) { 50 | _closePreview(); 51 | }, 52 | imageProvider: NetworkImage(widget.imageUrls[index]), 53 | // heroAttributes: widget.heroTag.isNotEmpty 54 | // ? PhotoViewHeroAttributes(tag: widget.heroTag) 55 | // : null, 56 | ); 57 | }, 58 | itemCount: widget.imageUrls.length, 59 | loadingChild: Container(), 60 | backgroundDecoration: null, 61 | pageController: PageController( 62 | initialPage: currentIndex, // 进入时默认显示的第几页 63 | ), 64 | enableRotation: true, 65 | onPageChanged: (index) { 66 | setState(() { 67 | currentIndex = index; 68 | }); 69 | }, 70 | ), 71 | ), 72 | ), 73 | Positioned( 74 | bottom: 20, 75 | width: getScreenWidth(context), 76 | child: Center( 77 | child: Text( 78 | "${currentIndex + 1} / ${widget.imageUrls.length}", 79 | style: TextStyle( 80 | fontSize: 18, 81 | color: Colors.white, 82 | ), 83 | ), 84 | ), 85 | ), 86 | ], 87 | ), 88 | ); 89 | } 90 | 91 | /// 抬起关闭预览 92 | void _closePreview() { 93 | Navigator.pop(context); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /lib/pages/login_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:fluttertoast/fluttertoast.dart'; 5 | import 'package:provider/provider.dart'; 6 | import 'package:wanandroid_flutter/http/api.dart'; 7 | import 'package:wanandroid_flutter/http/http.dart'; 8 | import 'package:wanandroid_flutter/manager/userinfo_manager.dart'; 9 | import 'package:wanandroid_flutter/models/user_info.dart'; 10 | import 'package:wanandroid_flutter/pages/register_page.dart'; 11 | import 'package:wanandroid_flutter/provider/app_theme.dart'; 12 | import 'package:wanandroid_flutter/widgets/beizier_path_painter.dart'; 13 | import 'package:wanandroid_flutter/widgets/gradient_appbar.dart'; 14 | import 'package:wanandroid_flutter/widgets/xtextfield.dart'; 15 | import "package:wanandroid_flutter/res/strings.dart"; 16 | import 'package:shared_preferences/shared_preferences.dart'; 17 | import 'package:wanandroid_flutter/provider/login_state.dart'; 18 | 19 | class LoginPage extends StatefulWidget { 20 | @override 21 | State createState() { 22 | return LoginPageState(); 23 | } 24 | } 25 | 26 | class LoginPageState extends State { 27 | double screenWidth = 0; 28 | bool isHidden = true; 29 | TextEditingController usernameController = new TextEditingController(); 30 | TextEditingController pwdController = new TextEditingController(); 31 | 32 | @override 33 | void initState() { 34 | super.initState(); 35 | loadCacheUsername().then((value) { 36 | usernameController.text = value; 37 | }); 38 | loadCachePassword().then((value) { 39 | pwdController.text = value; 40 | }); 41 | } 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | screenWidth = MediaQuery.of(context).size.width; 46 | return WillPopScope( 47 | onWillPop: () async { 48 | // 拦截返回键,避免页面先关闭,再关闭软键盘 49 | FocusScope.of(context).unfocus(); 50 | return Future.value(true); 51 | }, 52 | child: Scaffold( 53 | appBar: GradientAppBar( 54 | leading: GestureDetector( 55 | child: Icon( 56 | Icons.close, 57 | ), 58 | onTap: () { 59 | Navigator.pop(context); 60 | }, 61 | ), 62 | ), 63 | body: SingleChildScrollView( 64 | child: Column( 65 | children: [ 66 | Container( 67 | child: Consumer( 68 | builder: (BuildContext context, AppTheme appTheme, child) { 69 | return CustomPaint( 70 | size: Size(screenWidth, 150), 71 | painter: BezierPathPainter(appTheme.themeColor), 72 | ); 73 | }, 74 | ), 75 | ), 76 | Container( 77 | margin: EdgeInsets.only( 78 | top: 20, 79 | ), 80 | padding: EdgeInsets.only( 81 | left: 20, 82 | right: 20, 83 | ), 84 | // child: Text("ccc"), 85 | child: XTextField( 86 | usernameController, 87 | "用户名", 88 | prefixIcon: Icons.person, 89 | obscureText: false, 90 | suffixIcon: Icon( 91 | Icons.close, 92 | // color: Theme.of(context).textTheme.button.color, 93 | ), 94 | onChanged: (text) {}, 95 | ), 96 | ), 97 | Container( 98 | margin: EdgeInsets.only( 99 | top: 20, 100 | ), 101 | padding: EdgeInsets.only( 102 | left: 20, 103 | right: 20, 104 | ), 105 | child: XTextField( 106 | pwdController, 107 | "密 码", 108 | prefixIcon: Icons.lock, 109 | suffixIcon: Icon( 110 | Icons.close, 111 | color: Theme.of(context).textTheme.button.color, 112 | ), 113 | onChanged: (text) {}, 114 | ), 115 | ), 116 | Container( 117 | margin: EdgeInsets.only( 118 | top: 30, 119 | ), 120 | padding: EdgeInsets.only( 121 | left: 20, 122 | right: 20, 123 | ), 124 | child: Row( 125 | children: [ 126 | Expanded( 127 | flex: 1, 128 | child: Consumer( 129 | builder: 130 | (BuildContext context, AppTheme appTheme, child) { 131 | return MaterialButton( 132 | elevation: 0, 133 | onPressed: () { 134 | login(); 135 | }, 136 | shape: RoundedRectangleBorder( 137 | borderRadius: BorderRadius.all( 138 | Radius.circular(5), 139 | ), 140 | ), 141 | height: 46, 142 | color: appTheme.themeColor, 143 | child: Text( 144 | "登录", 145 | style: TextStyle( 146 | color: Colors.white, 147 | fontSize: 16, 148 | ), 149 | ), 150 | ); 151 | }, 152 | ), 153 | ), 154 | ], 155 | ), 156 | ), 157 | Container( 158 | padding: EdgeInsets.only( 159 | top: 10, 160 | ), 161 | margin: EdgeInsets.only( 162 | left: 20, 163 | ), 164 | alignment: Alignment.centerLeft, 165 | child: GestureDetector( 166 | onTap: () { 167 | Navigator.push( 168 | context, 169 | MaterialPageRoute( 170 | builder: (BuildContext context) { 171 | return RegisterPage(); 172 | }, 173 | ), 174 | ); 175 | }, 176 | child: Text( 177 | "注册账号?", 178 | style: TextStyle( 179 | color: Theme.of(context).textTheme.body1.color, 180 | decoration: TextDecoration.underline, 181 | fontSize: 16, 182 | ), 183 | ), 184 | ), 185 | ), 186 | ], 187 | ), 188 | ), 189 | ), 190 | ); 191 | } 192 | 193 | void login() async { 194 | String username = usernameController.text; 195 | String password = pwdController.text; 196 | if (username.isEmpty) { 197 | Fluttertoast.showToast(msg: "请输入用户名"); 198 | return; 199 | } 200 | if (password.isEmpty) { 201 | Fluttertoast.showToast(msg: "请输入密码"); 202 | return; 203 | } 204 | // 缓存用户名,密码至本地 205 | saveUsernamePassword(username, password); 206 | var result = await HttpClient.getInstance().post(Api.USER_LOGIN, 207 | data: {"username": username, "password": password}); 208 | Provider.of(context).updateLoginState(true); 209 | UserInfo userInfo = UserInfo.fromJson(result); 210 | print("userinfo = $userInfo"); 211 | if (userInfo != null) { 212 | Navigator.pop(context); 213 | UserInfoManager.getInstance().setUserInfo(userInfo); 214 | } 215 | } 216 | 217 | /// 缓存用户名,密码至本地 218 | void saveUsernamePassword(String username, String password) async { 219 | var sp = await SharedPreferences.getInstance(); 220 | sp.setString(Strings.USERNAME, username); 221 | sp.setString(Strings.PASSWORD, password); 222 | } 223 | 224 | /// 加载缓存中的用户名 225 | loadCacheUsername() async { 226 | var sp = await SharedPreferences.getInstance(); 227 | return sp.getString(Strings.USERNAME); 228 | } 229 | 230 | /// 加载缓存中的密码 231 | loadCachePassword() async { 232 | var sp = await SharedPreferences.getInstance(); 233 | return sp.getString(Strings.PASSWORD); 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /lib/pages/main_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | import 'package:wanandroid_flutter/pages/home_page.dart'; 4 | import 'package:wanandroid_flutter/pages/mine_page.dart'; 5 | import 'package:wanandroid_flutter/pages/project_page.dart'; 6 | import 'package:wanandroid_flutter/pages/system_page.dart'; 7 | import 'package:wanandroid_flutter/provider/app_theme.dart'; 8 | import 'package:shared_preferences/shared_preferences.dart'; 9 | import 'package:wanandroid_flutter/res/theme_colors.dart'; 10 | import 'package:wanandroid_flutter/provider/dark_mode.dart'; 11 | 12 | class MainPage extends StatefulWidget { 13 | MainPage({Key key, this.title}) : super(key: key); 14 | 15 | final String title; 16 | 17 | @override 18 | MainPageState createState() => MainPageState(); 19 | } 20 | 21 | class MainPageState extends State { 22 | int mCurrentIndex = 0; 23 | List pages = new List(); 24 | 25 | @override 26 | void initState() { 27 | super.initState(); 28 | queryThemeColor().then((index) { 29 | Provider.of(context).updateThemeColor(getThemeColors()[index]); 30 | }); 31 | queryDark().then((value) { 32 | Provider.of(context).setDark(value); 33 | }); 34 | pages 35 | ..add(HomePage()) 36 | ..add(ProjectPage()) 37 | ..add(SystemPage()) 38 | ..add(MinePage()); 39 | } 40 | 41 | @override 42 | Widget build(BuildContext context) { 43 | var appTheme = Provider.of(context); 44 | return Scaffold( 45 | body: IndexedStack( 46 | index: mCurrentIndex, 47 | children: pages, 48 | ), 49 | bottomNavigationBar: BottomNavigationBar( 50 | selectedIconTheme: IconThemeData( 51 | color: appTheme.themeColor, 52 | ), 53 | unselectedIconTheme: IconThemeData( 54 | // color: Colors.black54, 55 | ), 56 | selectedFontSize: 14, 57 | elevation: 50, 58 | unselectedFontSize: 14, 59 | selectedItemColor: appTheme.themeColor, 60 | unselectedItemColor: Color(0xff555555), 61 | showUnselectedLabels: true, 62 | currentIndex: mCurrentIndex, 63 | onTap: onNavigationItemSelected, 64 | items: [ 65 | createNavigationBarItem( 66 | context, 67 | "首页", 68 | Icon(Icons.home), 69 | ), 70 | createNavigationBarItem( 71 | context, 72 | "项目", 73 | Icon(Icons.store), 74 | ), 75 | createNavigationBarItem( 76 | context, 77 | "体系", 78 | Icon(Icons.apps), 79 | ), 80 | createNavigationBarItem( 81 | context, 82 | "我的", 83 | Icon(Icons.person), 84 | ), 85 | ], 86 | ), 87 | ); 88 | } 89 | 90 | createNavigationBarItem(BuildContext context, String content, Icon icon) { 91 | return BottomNavigationBarItem( 92 | title: new Text( 93 | content, 94 | style: TextStyle(fontSize: 12), 95 | ), 96 | icon: icon, 97 | backgroundColor: Theme.of(context).backgroundColor, 98 | ); 99 | } 100 | 101 | void onNavigationItemSelected(int value) { 102 | setState(() { 103 | this.mCurrentIndex = value; 104 | }); 105 | } 106 | 107 | /// 查询主题色 108 | queryThemeColor() async { 109 | SharedPreferences sp = await SharedPreferences.getInstance(); 110 | int themeColorIndex = sp.getInt("themeColorIndex") ?? 0; 111 | return themeColorIndex; 112 | } 113 | 114 | /// 查询暗黑模式 115 | queryDark() async { 116 | SharedPreferences sp = await SharedPreferences.getInstance(); 117 | bool isDark = sp.getBool("dark") ?? false; 118 | return isDark; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /lib/pages/meizi_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/widgets.dart'; 4 | import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; 5 | import 'package:pull_to_refresh/pull_to_refresh.dart'; 6 | import 'package:wanandroid_flutter/http/api.dart'; 7 | import 'package:wanandroid_flutter/http/http.dart'; 8 | import 'package:wanandroid_flutter/models/meizi.dart'; 9 | import 'package:wanandroid_flutter/pages/image_preview_page.dart'; 10 | import 'package:wanandroid_flutter/res/colors.dart'; 11 | import 'package:wanandroid_flutter/widgets/gradient_appbar.dart'; 12 | 13 | class MeiziPage extends StatefulWidget { 14 | @override 15 | State createState() { 16 | return MeiziPageState(); 17 | } 18 | } 19 | 20 | class MeiziPageState extends State { 21 | double screenWidth = 0; 22 | ScrollController mScroller; 23 | List meiziList = new List(); 24 | int pageSize = 20; 25 | int curPage = 1; 26 | RefreshController _refreshController = new RefreshController(); 27 | List heights = [260, 310, 200, 250, 290]; 28 | 29 | @override 30 | void initState() { 31 | super.initState(); 32 | mScroller = new ScrollController(); 33 | _loadRefresh(); 34 | mScroller.addListener(() {}); 35 | } 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | screenWidth = MediaQuery.of(context).size.width; 40 | return Scaffold( 41 | appBar: GradientAppBar( 42 | title: Text("妹子"), 43 | colors: [ 44 | Colours.appThemeColor, 45 | Color(0xfffa5650), 46 | ], 47 | ), 48 | body: Container( 49 | child: SmartRefresher( 50 | enablePullDown: true, 51 | enablePullUp: true, 52 | header: WaterDropHeader(), 53 | footer: CustomFooter( 54 | builder: (BuildContext context, LoadStatus mode) { 55 | Widget body; 56 | if (mode == LoadStatus.idle) { 57 | body = Text("上拉加载"); 58 | } else if (mode == LoadStatus.loading) { 59 | body = CupertinoActivityIndicator(); 60 | } else if (mode == LoadStatus.failed) { 61 | body = Text("加载失败!点击重试!"); 62 | } else if (mode == LoadStatus.canLoading) { 63 | body = Text("上拉加载更多"); 64 | } else { 65 | body = Text("没有更多数据了!"); 66 | } 67 | return Container( 68 | height: 55.0, 69 | child: Center(child: body), 70 | ); 71 | }, 72 | ), 73 | controller: _refreshController, 74 | onRefresh: _loadRefresh, 75 | onLoading: _loadMore, 76 | child: StaggeredGridView.countBuilder( 77 | // 滑动控制器 78 | controller: mScroller, 79 | // 滑动方向 80 | scrollDirection: Axis.vertical, 81 | // 纵轴方向被划分的个数 82 | crossAxisCount: 2, 83 | itemCount: meiziList.length, 84 | mainAxisSpacing: 10, 85 | crossAxisSpacing: 10, 86 | staggeredTileBuilder: (index) { 87 | return StaggeredTile.fit(1); 88 | }, 89 | itemBuilder: (BuildContext context, int index) { 90 | return GestureDetector( 91 | onTap: () { 92 | _onItemClick(meiziList, index); 93 | }, 94 | child: Container( 95 | //随机生成高度 96 | height: (heights[index % heights.length]).toDouble(), 97 | width: 20, 98 | child: FadeInImage( 99 | fit: BoxFit.cover, 100 | placeholder: AssetImage("images/placeholder.png"), 101 | image: NetworkImage(meiziList[index].url), 102 | ), 103 | ), 104 | ); 105 | }, 106 | ), 107 | ), 108 | ), 109 | ); 110 | } 111 | 112 | _loadMeizi(int pageSize, int page) async { 113 | var result = await HttpClient.getInstance() 114 | .get(Api.GANK_MEIZI, data: {"pageSize": pageSize, "page": page}); 115 | curPage = page + 1; 116 | return result; 117 | } 118 | 119 | @override 120 | void dispose() { 121 | super.dispose(); 122 | mScroller?.dispose(); 123 | } 124 | 125 | /// item 点击事件 126 | void _onItemClick(List meizis, int index) { 127 | List urls = new List(); 128 | meizis.forEach((meizi) { 129 | urls.add(meizi.url); 130 | }); 131 | Navigator.push( 132 | context, 133 | MaterialPageRoute( 134 | builder: (BuildContext context) => ImagePreviewPage(urls, index), 135 | ), 136 | ); 137 | } 138 | 139 | void _loadRefresh() async { 140 | var result = await _loadMeizi(pageSize, 0); 141 | setState(() { 142 | meiziList.clear(); 143 | meiziList.addAll(result.map((map) => Meizi.fromJson(map)).toList()); 144 | }); 145 | _refreshController.refreshCompleted(resetFooterState: true); 146 | } 147 | 148 | void _loadMore() async { 149 | var result = await _loadMeizi(pageSize, curPage); 150 | setState(() { 151 | meiziList.addAll(result.map((map) => Meizi.fromJson(map)).toList()); 152 | }); 153 | _refreshController.loadComplete(); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /lib/pages/project_list_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:pull_to_refresh/pull_to_refresh.dart'; 4 | import 'package:wanandroid_flutter/http/api.dart'; 5 | import 'package:wanandroid_flutter/http/http.dart'; 6 | import 'package:wanandroid_flutter/models/article.dart'; 7 | import 'package:wanandroid_flutter/models/project_article.dart'; 8 | import 'package:wanandroid_flutter/pages/webview_page.dart'; 9 | import 'package:wanandroid_flutter/utils/screen_utils.dart'; 10 | 11 | class ProjectListPage extends StatefulWidget { 12 | final int tabId; 13 | 14 | ProjectListPage(this.tabId, {Key key}) : super(key: key); 15 | 16 | @override 17 | State createState() { 18 | return ProjectListPageState(); 19 | } 20 | } 21 | 22 | class ProjectListPageState extends State 23 | with TickerProviderStateMixin, AutomaticKeepAliveClientMixin { 24 | int curPage = 0; 25 | int tabId = 0; 26 | List
articleList = new List(); 27 | double screenWidth = 0; 28 | RefreshController _refreshController = 29 | RefreshController(initialRefresh: false); 30 | 31 | @override 32 | void initState() { 33 | super.initState(); 34 | tabId = widget.tabId; 35 | _onRefresh(); 36 | } 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | super.build(context); 41 | screenWidth = getScreenWidth(context); 42 | return SmartRefresher( 43 | enablePullDown: true, 44 | enablePullUp: true, 45 | header: WaterDropHeader(), 46 | footer: CustomFooter( 47 | builder: (BuildContext context, LoadStatus mode) { 48 | Widget body; 49 | if (mode == LoadStatus.idle) { 50 | body = Text("上拉加载"); 51 | } else if (mode == LoadStatus.loading) { 52 | body = CupertinoActivityIndicator(); 53 | } else if (mode == LoadStatus.failed) { 54 | body = Text("加载失败!点击重试!"); 55 | } else if (mode == LoadStatus.canLoading) { 56 | body = Text("上拉加载更多"); 57 | } else { 58 | body = Text("没有更多数据了!"); 59 | } 60 | return Container( 61 | height: 55.0, 62 | child: Center(child: body), 63 | ); 64 | }, 65 | ), 66 | controller: _refreshController, 67 | onRefresh: _onRefresh, 68 | onLoading: _onLoadMore, 69 | child: ListView.separated( 70 | itemBuilder: (context, index) { 71 | return createProjectListItem(index); 72 | }, 73 | separatorBuilder: (context, index) { 74 | return Container( 75 | margin: EdgeInsets.only( 76 | left: 12, 77 | right: 12, 78 | ), 79 | color: Colors.black12, 80 | height: 0.5, 81 | ); 82 | }, 83 | itemCount: articleList.length, 84 | ), 85 | ); 86 | } 87 | 88 | /// ListView item 89 | createProjectListItem(int position) { 90 | return GestureDetector( 91 | behavior: HitTestBehavior.opaque, 92 | onTap: () { 93 | onProjectArticleClick(position); 94 | }, 95 | child: Container( 96 | padding: EdgeInsets.all(12), 97 | color: Theme.of(context).backgroundColor, 98 | child: Row( 99 | children: [ 100 | FadeInImage.assetNetwork( 101 | placeholder: "images/placeholder.png", 102 | width: 90, 103 | height: 66, 104 | image: articleList[position].envelopePic, 105 | fit: BoxFit.cover, 106 | ), 107 | Container( 108 | height: 66, 109 | width: screenWidth - 124, 110 | margin: EdgeInsets.only( 111 | left: 10, 112 | ), 113 | child: Stack( 114 | children: [ 115 | Positioned( 116 | child: Row( 117 | crossAxisAlignment: CrossAxisAlignment.start, 118 | children: [ 119 | Expanded( 120 | flex: 1, 121 | child: Container( 122 | child: Text( 123 | articleList[position].title, 124 | maxLines: 2, 125 | style: TextStyle( 126 | fontSize: 16, 127 | ), 128 | ), 129 | ), 130 | ), 131 | ], 132 | ), 133 | ), 134 | Positioned( 135 | bottom: 0, 136 | child: Row( 137 | children: [ 138 | Container( 139 | child: Text( 140 | articleList[position].author.isEmpty 141 | ? articleList[position].shareUser 142 | : articleList[position].author, 143 | style: Theme.of(context).textTheme.body2, 144 | ), 145 | ), 146 | Container( 147 | margin: EdgeInsets.only( 148 | left: 20, 149 | ), 150 | child: Text( 151 | articleList[position].niceDate, 152 | style: Theme.of(context).textTheme.body2, 153 | ), 154 | ), 155 | ], 156 | ), 157 | ), 158 | ], 159 | ), 160 | ), 161 | ], 162 | ), 163 | ), 164 | ); 165 | } 166 | 167 | void onProjectArticleClick(int position) { 168 | Navigator.push( 169 | context, 170 | MaterialPageRoute( 171 | builder: (BuildContext context) => WebViewPage( 172 | url: articleList[position].link, 173 | ), 174 | ), 175 | ); 176 | } 177 | 178 | @override 179 | bool get wantKeepAlive => true; 180 | 181 | /// 请求 tab 下的列表 182 | Future _loadProjectList(int tabId, int page) async { 183 | var result = await HttpClient.getInstance() 184 | .get(Api.PROJECT_LIST, data: {"page": page, "cid": tabId}); 185 | curPage = page + 1; 186 | ProjectArticle projectArticle = ProjectArticle.fromJson(result); 187 | return projectArticle; 188 | } 189 | 190 | /// 下拉刷新 191 | void _onRefresh() async { 192 | ProjectArticle projectArticle = await _loadProjectList(tabId, 0); 193 | setState(() { 194 | articleList.clear(); 195 | articleList.addAll(projectArticle.datas); 196 | }); 197 | // 下拉刷新完成重置footer状态,否则此后无法上拉加载更多 198 | _refreshController.refreshCompleted(resetFooterState: true); 199 | } 200 | 201 | /// 上拉加载更多 202 | void _onLoadMore() async { 203 | ProjectArticle projectArticle = await _loadProjectList(tabId, curPage); 204 | List
articles = projectArticle.datas; 205 | if (articles.length < projectArticle.size) { 206 | _refreshController.loadNoData(); 207 | } else { 208 | _refreshController.loadComplete(); 209 | } 210 | setState(() { 211 | articleList.addAll(articles); 212 | }); 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /lib/pages/project_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | import 'package:wanandroid_flutter/http/api.dart'; 4 | import 'package:wanandroid_flutter/http/http.dart'; 5 | import 'package:wanandroid_flutter/models/article.dart'; 6 | import 'package:wanandroid_flutter/models/project_tab.dart'; 7 | import 'package:wanandroid_flutter/pages/project_list_page.dart'; 8 | import 'package:wanandroid_flutter/provider/app_theme.dart'; 9 | 10 | class ProjectPage extends StatefulWidget { 11 | @override 12 | State createState() { 13 | return ProjectPageState(); 14 | } 15 | } 16 | 17 | class ProjectPageState extends State 18 | with TickerProviderStateMixin { 19 | TabController mController; 20 | List tabList = new List(); 21 | List
articleList = new List(); 22 | double screenWidth = 0; 23 | int curPage = 0; 24 | 25 | @override 26 | void initState() { 27 | super.initState(); 28 | loadTabs(curPage); 29 | mController = TabController( 30 | length: tabList.length, 31 | vsync: this, 32 | ); 33 | } 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | screenWidth = MediaQuery.of(context).size.width; 38 | var appTheme = Provider.of(context); 39 | return Scaffold( 40 | appBar: PreferredSize( 41 | child: Container( 42 | child: AppBar( 43 | titleSpacing: 0, 44 | title: Row( 45 | children: [ 46 | Expanded( 47 | child: TabBar( 48 | labelPadding: EdgeInsets.only( 49 | left: 16, 50 | right: 16, 51 | bottom: 2, 52 | ), 53 | // indicatorPadding: EdgeInsets.all(0), 54 | // 选中颜色 55 | labelColor: Colors.white, 56 | // 选中样式 57 | labelStyle: TextStyle(fontSize: 18), 58 | // 未选中颜色 59 | unselectedLabelColor: Colors.white70, 60 | // 未选中样式 61 | unselectedLabelStyle: TextStyle(fontSize: 16), 62 | // 是否可滑动 63 | isScrollable: true, 64 | controller: mController, 65 | // 指示器宽度 66 | indicatorSize: TabBarIndicatorSize.label, 67 | indicatorColor: Colors.white, 68 | // 相当于 indicator 高度 69 | indicatorWeight: 3, 70 | // tab 标签 71 | tabs: tabList.map((tab) { 72 | return Tab( 73 | child: Container( 74 | padding: EdgeInsets.all(0), 75 | child: Text(tab.name), 76 | ), 77 | ); 78 | }).toList(), 79 | ), 80 | ), 81 | ], 82 | ), 83 | backgroundColor: Colors.transparent, 84 | elevation: 0, 85 | ), 86 | decoration: BoxDecoration( 87 | gradient: LinearGradient( 88 | begin: Alignment.topLeft, 89 | end: Alignment.bottomRight, 90 | colors: [ 91 | appTheme.themeColor, 92 | appTheme.themeColor, 93 | ], 94 | ), 95 | ), 96 | ), 97 | preferredSize: Size( 98 | MediaQuery.of(context).size.width, 99 | 50, 100 | ), 101 | ), 102 | body: TabBarView( 103 | controller: mController, 104 | children: createTabPage(), 105 | ), 106 | ); 107 | } 108 | 109 | List createTabPage() { 110 | List widgets = new List(); 111 | for (var projectTab in tabList) { 112 | widgets.add(ProjectListPage(projectTab.id)); 113 | } 114 | return widgets; 115 | } 116 | 117 | @override 118 | void dispose() { 119 | super.dispose(); 120 | if (mounted) { 121 | mController.dispose(); 122 | } 123 | } 124 | 125 | /// 请求 tabs 126 | void loadTabs(int page) async { 127 | var result = await HttpClient.getInstance().get(Api.PROJECT_TABS); 128 | if (result is List) { 129 | if (mounted) { 130 | for (var value in result) { 131 | ProjectTab tab = ProjectTab.fromJson(value); 132 | tabList.add(tab); 133 | } 134 | setState(() { 135 | mController = TabController( 136 | length: tabList.length, 137 | vsync: this, 138 | ); 139 | }); 140 | } 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /lib/pages/register_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:fluttertoast/fluttertoast.dart'; 3 | import 'package:provider/provider.dart'; 4 | import 'package:wanandroid_flutter/http/api.dart'; 5 | import 'package:wanandroid_flutter/http/http.dart'; 6 | import 'package:wanandroid_flutter/provider/app_theme.dart'; 7 | import 'package:wanandroid_flutter/widgets/gradient_appbar.dart'; 8 | import 'package:wanandroid_flutter/widgets/xtextfield.dart'; 9 | 10 | class RegisterPage extends StatefulWidget { 11 | @override 12 | State createState() { 13 | return RegisterPageState(); 14 | } 15 | } 16 | 17 | class RegisterPageState extends State { 18 | double screenWidth = 0; 19 | TextEditingController usernameController = new TextEditingController(); 20 | TextEditingController pwdController = new TextEditingController(); 21 | TextEditingController repwdController = new TextEditingController(); 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | usernameController.addListener(() {}); 27 | } 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | screenWidth = MediaQuery.of(context).size.width; 32 | return WillPopScope( 33 | onWillPop: () async { 34 | // 拦截返回键,避免页面先关闭,再关闭软键盘 35 | FocusScope.of(context).unfocus(); 36 | return Future.value(true); 37 | }, 38 | child: Scaffold( 39 | backgroundColor: Theme.of(context).backgroundColor, 40 | appBar: GradientAppBar( 41 | title: Text("注册"), 42 | ), 43 | body: Container( 44 | padding: EdgeInsets.only( 45 | left: 16, 46 | right: 16, 47 | ), 48 | child: Column( 49 | children: [ 50 | Container( 51 | margin: EdgeInsets.only( 52 | top: 20, 53 | ), 54 | child: XTextField( 55 | usernameController, 56 | "用户名", 57 | prefixIcon: Icons.person, 58 | obscureText: false, 59 | suffixIcon: Icon( 60 | Icons.close, 61 | color: Theme.of(context).textTheme.button.color, 62 | ), 63 | onChanged: (text) {}, 64 | ), 65 | ), 66 | Container( 67 | margin: EdgeInsets.only( 68 | top: 16, 69 | ), 70 | child: XTextField( 71 | pwdController, 72 | "密码", 73 | prefixIcon: Icons.lock, 74 | suffixIcon: Icon( 75 | Icons.close, 76 | color: Theme.of(context).textTheme.button.color, 77 | ), 78 | onChanged: (text) {}, 79 | ), 80 | ), 81 | Container( 82 | margin: EdgeInsets.only( 83 | top: 16, 84 | ), 85 | child: XTextField( 86 | repwdController, 87 | "确认密码", 88 | prefixIcon: Icons.lock, 89 | suffixIcon: Icon( 90 | Icons.close, 91 | color: Theme.of(context).textTheme.button.color, 92 | ), 93 | onChanged: (text) {}, 94 | ), 95 | ), 96 | Container( 97 | margin: EdgeInsets.only( 98 | top: 30, 99 | ), 100 | child: Consumer( 101 | builder: (context, provider, child) { 102 | return MaterialButton( 103 | elevation: 0, 104 | onPressed: () { 105 | register(); 106 | }, 107 | shape: RoundedRectangleBorder( 108 | borderRadius: BorderRadius.all( 109 | Radius.circular(5), 110 | ), 111 | ), 112 | height: 46, 113 | minWidth: screenWidth, 114 | color: provider.themeColor, 115 | child: Text( 116 | "注册", 117 | style: TextStyle( 118 | fontSize: 16, 119 | color: Colors.white, 120 | ), 121 | ), 122 | ); 123 | }, 124 | ), 125 | ), 126 | ], 127 | ), 128 | ), 129 | ), 130 | ); 131 | } 132 | 133 | void register() async { 134 | String username = usernameController.text; 135 | String password = pwdController.text; 136 | String repassword = repwdController.text; 137 | if (username.isEmpty) { 138 | Fluttertoast.showToast(msg: "请输入用户名"); 139 | return; 140 | } 141 | if (password.isEmpty || repassword.isEmpty) { 142 | Fluttertoast.showToast(msg: "请输入密码"); 143 | return; 144 | } 145 | if (password != repassword) { 146 | Fluttertoast.showToast(msg: "两次密码不一致"); 147 | return; 148 | } 149 | await HttpClient.getInstance().post(Api.USER_REGISTER, data: { 150 | "username": username, 151 | "password": password, 152 | "repassword": repassword 153 | }); 154 | Fluttertoast.showToast(msg: "注册成功"); 155 | Navigator.of(context).pop(); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /lib/pages/search_history_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:wanandroid_flutter/http/api.dart'; 3 | import 'package:wanandroid_flutter/http/http.dart'; 4 | import 'package:wanandroid_flutter/models/hot_search.dart'; 5 | import 'package:wanandroid_flutter/pages/search_page.dart'; 6 | import 'package:wanandroid_flutter/widgets/header_list_view.dart'; 7 | 8 | class SearchHistoryPage extends StatefulWidget { 9 | SearchPage searchPage; 10 | 11 | SearchHistoryPage(this.searchPage); 12 | 13 | @override 14 | State createState() { 15 | return SearchHistoryPageState(); 16 | } 17 | } 18 | 19 | class SearchHistoryPageState extends State { 20 | double screenWidth = 0; 21 | List hotSearchList = new List(); 22 | List searchHistoryList = new List(); 23 | 24 | @override 25 | void initState() { 26 | super.initState(); 27 | searchHistoryList 28 | ..add("dcd") 29 | ..add("dcd") 30 | ..add("dcd") 31 | ..add("dcd") 32 | ..add("dcd") 33 | ..add("dcd"); 34 | loadHotSearch(); 35 | } 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | screenWidth = MediaQuery.of(context).size.width; 40 | return HeaderListView( 41 | searchHistoryList, 42 | headerList: [1], 43 | headerBuilder: (BuildContext context, int position) { 44 | return Container( 45 | color: Theme.of(context).backgroundColor, 46 | padding: EdgeInsets.fromLTRB(12, 12, 12, 0), 47 | child: Column( 48 | crossAxisAlignment: CrossAxisAlignment.start, 49 | children: [ 50 | Container( 51 | margin: EdgeInsets.only( 52 | bottom: 15, 53 | ), 54 | child: Text( 55 | "热门搜索", 56 | style: TextStyle( 57 | fontSize: 18, 58 | ), 59 | ), 60 | ), 61 | Wrap( 62 | spacing: 10, 63 | runSpacing: 0, 64 | crossAxisAlignment: WrapCrossAlignment.center, 65 | children: createWrapItems(), 66 | ), 67 | Container( 68 | margin: EdgeInsets.only( 69 | top: 16, 70 | ), 71 | child: Text( 72 | "搜索历史", 73 | style: TextStyle( 74 | fontSize: 18, 75 | ), 76 | ), 77 | ), 78 | ], 79 | ), 80 | ); 81 | }, 82 | itemBuilder: (BuildContext context, int position) { 83 | return Container( 84 | color: Theme.of(context).backgroundColor, 85 | height: 50, 86 | padding: EdgeInsets.only( 87 | left: 15, 88 | right: 15, 89 | ), 90 | child: Row( 91 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 92 | children: [ 93 | Text( 94 | "我爱你", 95 | style: TextStyle(fontSize: 16), 96 | ), 97 | Icon( 98 | Icons.close, 99 | color: Theme.of(context).textTheme.button.color, 100 | ), 101 | ], 102 | ), 103 | ); 104 | }, 105 | separatorBuilder: (context, index) { 106 | if (index > 0) { 107 | return Divider( 108 | indent: 12, 109 | endIndent: 12, 110 | height: 1, 111 | ); 112 | } else { 113 | return Text( 114 | "", 115 | style: TextStyle(fontSize: 0), 116 | ); 117 | } 118 | }, 119 | ); 120 | } 121 | 122 | void loadHotSearch() async { 123 | var result = await HttpClient.getInstance().get(Api.SEARCH_HOT); 124 | if (result is List) { 125 | setState(() { 126 | hotSearchList = result.map((map) => HotSearch.fromJson(map)).toList(); 127 | }); 128 | } 129 | } 130 | 131 | List createWrapItems() => 132 | List.generate(hotSearchList.length, (index) { 133 | return ActionChip( 134 | backgroundColor: Theme.of(context).textTheme.display1.color, 135 | padding: EdgeInsets.fromLTRB(5, 0, 5, 0), 136 | label: Text( 137 | hotSearchList[index].name, 138 | style: TextStyle( 139 | color: Theme.of(context).textTheme.body1.color, 140 | ), 141 | ), 142 | onPressed: () { 143 | onSearchHotClick(hotSearchList[index].name); 144 | }, 145 | ); 146 | }); 147 | 148 | /// 热门搜索项点击 149 | void onSearchHotClick(String name) { 150 | widget.searchPage.keyword = name; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /lib/pages/search_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:wanandroid_flutter/models/hot_search.dart'; 3 | import 'package:wanandroid_flutter/pages/search_history_page.dart'; 4 | import 'package:wanandroid_flutter/pages/search_result_page.dart'; 5 | import 'package:wanandroid_flutter/res/colors.dart'; 6 | import 'package:wanandroid_flutter/widgets/gradient_appbar.dart'; 7 | 8 | class SearchPage extends StatefulWidget { 9 | String keyword; 10 | 11 | SearchPage(this.keyword); 12 | 13 | @override 14 | State createState() { 15 | return SearchPageState(keyword); 16 | } 17 | } 18 | 19 | class SearchPageState extends State { 20 | double screenWidth = 0; 21 | List hotSearchList = new List(); 22 | TextEditingController searchController; 23 | bool showClose = false; 24 | SearchResultPage searchResultPage; 25 | String keyword; 26 | 27 | SearchPageState(this.keyword); 28 | 29 | @override 30 | void initState() { 31 | super.initState(); 32 | searchController = new TextEditingController(text: keyword); 33 | searchResultPage = new SearchResultPage(searchController.text); 34 | } 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | screenWidth = MediaQuery.of(context).size.width; 39 | return Scaffold( 40 | appBar: GradientAppBar( 41 | title: Container( 42 | margin: EdgeInsets.only( 43 | right: 5, 44 | left: 5, 45 | ), 46 | child: TextField( 47 | controller: searchController, 48 | cursorColor: Colors.white, 49 | onChanged: (String value) { 50 | setState(() { 51 | showClose = value.length > 0; 52 | }); 53 | }, 54 | decoration: InputDecoration( 55 | hintText: "输入关键字搜索", 56 | hintStyle: TextStyle( 57 | fontSize: 16, 58 | color: Colors.white38, 59 | ), 60 | focusedBorder: UnderlineInputBorder( 61 | borderSide: BorderSide( 62 | color: Colors.white, 63 | ), 64 | ), 65 | border: UnderlineInputBorder( 66 | borderSide: BorderSide( 67 | color: Colors.white, 68 | ), 69 | ), 70 | suffixIcon: GestureDetector( 71 | onTap: () { 72 | searchController.text = ""; 73 | }, 74 | child: Visibility( 75 | visible: showClose, 76 | child: Icon( 77 | Icons.close, 78 | color: Colors.white, 79 | ), 80 | ), 81 | ), 82 | ), 83 | textInputAction: TextInputAction.search, 84 | style: TextStyle( 85 | color: Colors.white, 86 | fontSize: 16, 87 | ), 88 | ), 89 | ), 90 | centerTitle: true, 91 | colors: [ 92 | Colours.appThemeColor, 93 | Color(0xfffa5650), 94 | ], 95 | ), 96 | body: (searchController.text == null || searchController.text.isEmpty) 97 | ? SearchHistoryPage(widget) 98 | : searchResultPage, 99 | ); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /lib/pages/search_result_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:wanandroid_flutter/http/api.dart'; 4 | import 'package:wanandroid_flutter/http/http.dart'; 5 | import 'package:wanandroid_flutter/models/article.dart'; 6 | import 'package:wanandroid_flutter/models/article_response.dart'; 7 | import 'package:wanandroid_flutter/pages/webview_page.dart'; 8 | import 'package:wanandroid_flutter/widgets/article_item.dart'; 9 | 10 | class SearchResultPage extends StatefulWidget { 11 | final String keyword; 12 | 13 | SearchResultPage(this.keyword, {Key key}) : super(key: key); 14 | 15 | @override 16 | State createState() { 17 | return new SearchResultPageState(); 18 | } 19 | } 20 | 21 | class SearchResultPageState extends State { 22 | int curPage = 0; 23 | List
articleList = new List(); 24 | 25 | @override 26 | void initState() { 27 | super.initState(); 28 | loadSearchResult(widget.keyword, curPage); 29 | } 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return ListView.separated( 34 | itemBuilder: (context, index) { 35 | var article = articleList[index]; 36 | return ArticleItem(article.title, article.niceDate, article.shareUser, 37 | () { 38 | Navigator.push( 39 | context, 40 | MaterialPageRoute( 41 | builder: (BuildContext context) => WebViewPage( 42 | url: article.link, 43 | ), 44 | ), 45 | ); 46 | }); 47 | }, 48 | separatorBuilder: (context, index) { 49 | return Divider( 50 | indent: 12, 51 | endIndent: 12, 52 | height: 0.5, 53 | ); 54 | }, 55 | itemCount: articleList.length, 56 | ); 57 | } 58 | 59 | void loadSearchResult(keyword, int page) async { 60 | var result = await HttpClient.getInstance().post( 61 | Api.ARTICLE_SEARCH + page.toString() + "/json", 62 | data: {"k": keyword}); 63 | print("res = $result"); 64 | if (result != null) { 65 | ArticleResponse articleResponse = ArticleResponse.fromJson(result); 66 | List
articles = articleResponse.datas; 67 | setState(() { 68 | articleList.addAll(articles); 69 | }); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/pages/settings_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | import 'package:shared_preferences/shared_preferences.dart'; 4 | import 'package:wanandroid_flutter/http/http.dart'; 5 | import 'package:wanandroid_flutter/pages/about_page.dart'; 6 | import 'package:wanandroid_flutter/pages/login_page.dart'; 7 | import 'package:wanandroid_flutter/provider/app_theme.dart'; 8 | import 'package:wanandroid_flutter/provider/login_state.dart'; 9 | import 'package:wanandroid_flutter/res/colors.dart'; 10 | import 'package:wanandroid_flutter/widgets/gradient_appbar.dart'; 11 | import 'package:wanandroid_flutter/widgets/section_item.dart'; 12 | import 'package:wanandroid_flutter/provider/dark_mode.dart'; 13 | 14 | class SettingsPage extends StatefulWidget { 15 | @override 16 | State createState() { 17 | return SettingsPageState(); 18 | } 19 | } 20 | 21 | class SettingsPageState extends State { 22 | double screenWidth = 0; 23 | bool isDarkMode = false; 24 | bool cookieExist = false; 25 | 26 | @override 27 | void initState() { 28 | super.initState(); 29 | } 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | screenWidth = MediaQuery.of(context).size.width; 34 | // isCookieExist().then((value) { 35 | // setState(() { 36 | // Provider.of(context).updateLoginState(value ?? false); 37 | // print("cookieExist 8888888888 = $cookieExist"); 38 | // }); 39 | // }); 40 | return Scaffold( 41 | appBar: GradientAppBar( 42 | title: Text("设置"), 43 | colors: [ 44 | Colours.appThemeColor, 45 | Color(0xfffa5650), 46 | ], 47 | ), 48 | body: Column( 49 | children: [ 50 | SectionItem( 51 | Icons.brightness_6, 52 | "夜间模式", 53 | margin: EdgeInsets.only( 54 | top: 20, 55 | ), 56 | right: Switch( 57 | activeColor: Provider.of(context).themeColor, 58 | value: Provider.of(context).isDark, 59 | onChanged: (value) { 60 | print("value = $value"); 61 | Provider.of(context).setDark(value); 62 | saveDarkMode(value); 63 | }), 64 | hasDivider: false, 65 | ), 66 | SectionItem( 67 | Icons.delete_forever, 68 | "清除缓存", 69 | callback: () {}, 70 | margin: EdgeInsets.only( 71 | top: 20, 72 | ), 73 | showMore: false, 74 | hasDivider: true, 75 | ), 76 | SectionItem(Icons.language, "语言设置", 77 | callback: () {}, 78 | hasDivider: true, 79 | right: Padding( 80 | padding: EdgeInsets.only( 81 | right: 8, 82 | ), 83 | child: Text( 84 | "中文", 85 | style: TextStyle(fontSize: 16), 86 | ), 87 | )), 88 | SectionItem( 89 | Icons.account_box, 90 | "关于", 91 | callback: () { 92 | Navigator.push( 93 | context, 94 | MaterialPageRoute( 95 | builder: (BuildContext context) { 96 | return AboutPage(); 97 | }, 98 | ), 99 | ); 100 | }, 101 | right: Icon( 102 | Icons.chevron_right, 103 | color: Theme.of(context).textTheme.button.color, 104 | size: 30, 105 | ), 106 | ), 107 | Container( 108 | child: Consumer( 109 | builder: (context, provider, child) { 110 | return MaterialButton( 111 | elevation: 0, 112 | onPressed: () { 113 | handleLogoutBtnClick(context, provider); 114 | }, 115 | textColor: Colors.red, 116 | child: Text( 117 | provider.loggedIn ? "退出登录" : "未登录, 点击登录", 118 | style: TextStyle( 119 | fontSize: 16, 120 | color: Colors.redAccent, 121 | ), 122 | ), 123 | minWidth: screenWidth, 124 | height: 52, 125 | color: Theme.of(context).backgroundColor, 126 | ); 127 | }, 128 | ), 129 | margin: EdgeInsets.only( 130 | top: 40, 131 | bottom: 40, 132 | ), 133 | ), 134 | ], 135 | ), 136 | ); 137 | } 138 | 139 | void handleLogoutBtnClick(BuildContext context, LoginState loginState) { 140 | /// 已经登录了,清除 cookie 141 | if (loginState.loggedIn) { 142 | removeUsernamePasswordCache(); 143 | loginState.updateLoginState(false); 144 | Navigator.pop(context); 145 | } else { 146 | /// 没有登录,跳到登录页面 147 | Navigator.push( 148 | context, 149 | MaterialPageRoute( 150 | builder: (BuildContext context) { 151 | return LoginPage(); 152 | }, 153 | ), 154 | ); 155 | } 156 | } 157 | 158 | void saveDarkMode(bool value) async { 159 | print("dark = $value"); 160 | SharedPreferences prefs = await SharedPreferences.getInstance(); 161 | prefs.setBool("dark", value); 162 | } 163 | } 164 | 165 | /// 清除 cookie 缓存 166 | void removeUsernamePasswordCache() async { 167 | HttpClient.getInstance().persistCookieJar.deleteAll(); 168 | } 169 | -------------------------------------------------------------------------------- /lib/pages/splash_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SplashPage extends StatefulWidget { 4 | @override 5 | State createState() { 6 | return SplashPageState(); 7 | } 8 | } 9 | 10 | class SplashPageState extends State { 11 | @override 12 | void initState() { 13 | super.initState(); 14 | var duration = new Duration(seconds: 3); 15 | new Future.delayed(duration, gotoMainPage); 16 | } 17 | 18 | void gotoMainPage() { 19 | Navigator.of(context).pushReplacementNamed('main'); 20 | } 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return Scaffold( 25 | body: Container( 26 | color: Colors.red, 27 | ), 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/pages/system_article_list_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:pull_to_refresh/pull_to_refresh.dart'; 4 | import 'package:wanandroid_flutter/http/api.dart'; 5 | import 'package:wanandroid_flutter/http/http.dart'; 6 | import 'package:wanandroid_flutter/models/article.dart'; 7 | import 'package:wanandroid_flutter/models/system_article.dart'; 8 | import 'package:wanandroid_flutter/pages/webview_page.dart'; 9 | import 'package:wanandroid_flutter/res/colors.dart'; 10 | import 'package:wanandroid_flutter/widgets/article_item.dart'; 11 | import 'package:wanandroid_flutter/widgets/gradient_appbar.dart'; 12 | 13 | class SystemArticleListPage extends StatefulWidget { 14 | final int cid; 15 | final String title; 16 | 17 | SystemArticleListPage(this.cid, this.title); 18 | 19 | @override 20 | State createState() { 21 | return SystemArticleListPageState(); 22 | } 23 | } 24 | 25 | class SystemArticleListPageState extends State { 26 | double screenWidth = 0; 27 | List
articleList = new List(); 28 | int curPage = 0; 29 | int cid = 0; 30 | RefreshController _refreshController = 31 | RefreshController(initialRefresh: false); 32 | 33 | @override 34 | void initState() { 35 | super.initState(); 36 | cid = widget.cid; 37 | _onRefresh(); 38 | } 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | return Scaffold( 43 | appBar: GradientAppBar( 44 | title: Text(widget.title), 45 | colors: [ 46 | Colours.appThemeColor, 47 | Color(0xfffa5650), 48 | ], 49 | ), 50 | body: SmartRefresher( 51 | enablePullDown: true, 52 | enablePullUp: true, 53 | header: WaterDropHeader(), 54 | footer: CustomFooter( 55 | builder: (BuildContext context, LoadStatus mode) { 56 | Widget body; 57 | if (mode == LoadStatus.idle) { 58 | body = Text("上拉加载"); 59 | } else if (mode == LoadStatus.loading) { 60 | body = CupertinoActivityIndicator(); 61 | } else if (mode == LoadStatus.failed) { 62 | body = Text("加载失败!点击重试!"); 63 | } else if (mode == LoadStatus.canLoading) { 64 | body = Text("上拉加载更多"); 65 | } else { 66 | body = Text("没有更多数据了!"); 67 | } 68 | return Container( 69 | height: 55.0, 70 | child: Center(child: body), 71 | ); 72 | }, 73 | ), 74 | controller: _refreshController, 75 | onRefresh: _onRefresh, 76 | onLoading: _onLoadMore, 77 | child: ListView.separated( 78 | itemBuilder: (context, index) { 79 | return getSystemSquareItem(index); 80 | }, 81 | separatorBuilder: (context, index) { 82 | return Container( 83 | margin: EdgeInsets.only( 84 | left: 12, 85 | right: 12, 86 | ), 87 | color: Colors.black12, 88 | height: 0.5, 89 | ); 90 | }, 91 | itemCount: articleList.length, 92 | ), 93 | ), 94 | ); 95 | } 96 | 97 | getSystemSquareItem(int index) { 98 | Article article = articleList[index]; 99 | return ArticleItem( 100 | article.title, 101 | article.author.isNotEmpty ? article.author : article.shareUser, 102 | article.niceDate, 103 | () { 104 | Navigator.push( 105 | context, 106 | MaterialPageRoute( 107 | builder: (BuildContext context) => WebViewPage( 108 | url: article.link, 109 | ), 110 | ), 111 | ); 112 | }, 113 | ); 114 | } 115 | 116 | void onItemClick(int index) { 117 | Navigator.push( 118 | context, 119 | MaterialPageRoute( 120 | builder: (BuildContext context) => WebViewPage( 121 | url: articleList[index].link, 122 | ), 123 | ), 124 | ); 125 | } 126 | 127 | /// 请求数据 128 | Future _loadArticleList(int page, int cid) async { 129 | var result = await HttpClient.getInstance() 130 | .get(Api.SYSTEM_ARTICLE_LIST, data: {"page": page, "cid": cid}); 131 | curPage = page + 1; 132 | SystemArticle squareArticle = SystemArticle.fromJson(result); 133 | return squareArticle; 134 | } 135 | 136 | /// 下拉刷新 137 | void _onRefresh() async { 138 | SystemArticle systemArticle = await _loadArticleList(0, cid); 139 | setState(() { 140 | articleList.clear(); 141 | articleList.addAll(systemArticle.datas); 142 | }); 143 | _refreshController.refreshCompleted(); 144 | } 145 | 146 | /// 上拉加载更多 147 | _onLoadMore() async { 148 | SystemArticle systemArticle = await _loadArticleList(curPage, cid); 149 | List
articles = systemArticle.datas; 150 | if (articles.length < systemArticle.size) { 151 | _refreshController.loadNoData(); 152 | } else { 153 | _refreshController.loadComplete(); 154 | } 155 | setState(() { 156 | articleList.addAll(articles); 157 | }); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /lib/pages/system_category_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | import 'package:wanandroid_flutter/http/api.dart'; 4 | import 'package:wanandroid_flutter/http/http.dart'; 5 | import 'package:wanandroid_flutter/models/system_category.dart'; 6 | import 'package:wanandroid_flutter/pages/system_article_list_page.dart'; 7 | import 'package:wanandroid_flutter/provider/app_theme.dart'; 8 | 9 | class SystemCategoryPage extends StatefulWidget { 10 | @override 11 | State createState() { 12 | return SystemCategoryPageState(); 13 | } 14 | } 15 | 16 | class SystemCategoryPageState extends State 17 | with AutomaticKeepAliveClientMixin { 18 | double screenWidth = 0; 19 | double leftMenuWidth = 100; 20 | double leftMenuRightMargin = 8; 21 | 22 | // 左侧 ListView 数据源 23 | List systemCategoryList = new List(); 24 | 25 | // 右侧 GridView 数据源 26 | List contentSystemList = new List(); 27 | int selectedIndex = 0; 28 | 29 | @override 30 | void initState() { 31 | super.initState(); 32 | loadSystemCategory(); 33 | } 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | super.build(context); 38 | screenWidth = MediaQuery.of(context).size.width; 39 | var themeColor = Provider.of(context).themeColor; 40 | return Container( 41 | child: Row( 42 | children: [ 43 | Container( 44 | margin: EdgeInsets.only( 45 | top: 8, 46 | bottom: 8, 47 | right: leftMenuRightMargin, 48 | ), 49 | decoration: ShapeDecoration( 50 | color: Theme.of(context).backgroundColor, 51 | shape: RoundedRectangleBorder( 52 | borderRadius: BorderRadius.only( 53 | topRight: Radius.circular(6), 54 | bottomRight: Radius.circular(6), 55 | ), 56 | ), 57 | ), 58 | width: leftMenuWidth, 59 | padding: EdgeInsets.only( 60 | right: 5, 61 | ), 62 | child: getSystemListView(themeColor), 63 | ), 64 | Column( 65 | children: [ 66 | Expanded( 67 | flex: 1, 68 | child: Container( 69 | width: screenWidth - leftMenuWidth - leftMenuRightMargin * 2, 70 | child: getGridView(), 71 | ), 72 | ), 73 | ], 74 | ), 75 | ], 76 | ), 77 | ); 78 | } 79 | 80 | /// 创建左侧 ListView 81 | getSystemListView(Color themeColor) { 82 | return ListView.separated( 83 | itemBuilder: (context, index) { 84 | return GestureDetector( 85 | onTap: () { 86 | print("ontap = $systemCategoryList"); 87 | setState(() { 88 | selectedIndex = index; 89 | contentSystemList = systemCategoryList[index].children; 90 | }); 91 | }, 92 | behavior: HitTestBehavior.opaque, 93 | child: Stack( 94 | children: [ 95 | Container( 96 | alignment: Alignment.centerLeft, 97 | padding: EdgeInsets.only( 98 | left: 16, 99 | ), 100 | height: 50, 101 | child: Text( 102 | systemCategoryList[index].name, 103 | maxLines: 1, 104 | style: TextStyle( 105 | fontSize: 16, 106 | color: (index == selectedIndex) 107 | ? themeColor 108 | : Theme.of(context).textTheme.body1.color, 109 | ), 110 | ), 111 | ), 112 | Positioned( 113 | left: 0, 114 | top: 13, 115 | child: Visibility( 116 | visible: selectedIndex == index, 117 | child: Container( 118 | height: 24, 119 | width: 6, 120 | color: themeColor, 121 | ), 122 | ), 123 | ), 124 | ], 125 | ), 126 | ); 127 | }, 128 | separatorBuilder: (context, index) { 129 | return Divider( 130 | indent: 12, 131 | height: 0.5, 132 | ); 133 | }, 134 | itemCount: systemCategoryList.length, 135 | ); 136 | } 137 | 138 | /// 创建右侧网格布局 139 | getGridView() { 140 | return GridView.custom( 141 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 142 | crossAxisCount: 2, 143 | mainAxisSpacing: 10, 144 | crossAxisSpacing: 10, 145 | childAspectRatio: 3, 146 | ), 147 | childrenDelegate: SliverChildBuilderDelegate( 148 | (BuildContext context, int index) { 149 | return createGridItem(index); 150 | }, 151 | childCount: contentSystemList.length, 152 | ), 153 | ); 154 | } 155 | 156 | createGridItem(int index) { 157 | return GestureDetector( 158 | onTap: () { 159 | Navigator.push( 160 | context, 161 | MaterialPageRoute( 162 | builder: (BuildContext context) => SystemArticleListPage( 163 | contentSystemList[index].id, contentSystemList[index].name), 164 | ), 165 | ); 166 | }, 167 | child: Container( 168 | alignment: Alignment.centerLeft, 169 | padding: EdgeInsets.only( 170 | left: 10, 171 | ), 172 | child: Text( 173 | contentSystemList[index].name, 174 | style: TextStyle( 175 | fontSize: 16, 176 | ), 177 | maxLines: 1, 178 | ), 179 | ), 180 | ); 181 | } 182 | 183 | void loadSystemCategory() async { 184 | var result = await HttpClient.getInstance().get(Api.SYSTEM_CATEGORY); 185 | if (result is List) { 186 | List list = 187 | result.map((map) => SystemCategory.fromJson(map)).toList(); 188 | contentSystemList = list[selectedIndex].children; 189 | setState(() { 190 | if (mounted) { 191 | systemCategoryList = list; 192 | } 193 | }); 194 | } 195 | } 196 | 197 | @override 198 | bool get wantKeepAlive => true; 199 | } 200 | -------------------------------------------------------------------------------- /lib/pages/system_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | import 'package:wanandroid_flutter/pages/search_page.dart'; 4 | import 'package:wanandroid_flutter/provider/app_theme.dart'; 5 | 6 | import 'system_category_page.dart'; 7 | import 'system_square_page.dart'; 8 | 9 | class SystemPage extends StatefulWidget { 10 | @override 11 | State createState() { 12 | return SystemPageState(); 13 | } 14 | } 15 | 16 | class SystemPageState extends State 17 | with SingleTickerProviderStateMixin { 18 | double screenWidth = 0; 19 | TabController mController; 20 | List list = new List(); 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | list..add("体系")..add("广场"); 26 | mController = TabController( 27 | length: list.length, 28 | vsync: this, 29 | ); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | screenWidth = MediaQuery.of(context).size.width; 35 | var appTheme = Provider.of(context); 36 | return Scaffold( 37 | appBar: PreferredSize( 38 | child: Container( 39 | child: AppBar( 40 | titleSpacing: 5, 41 | actions: [ 42 | IconButton( 43 | icon: Icon(Icons.search), 44 | onPressed: () { 45 | Navigator.push( 46 | context, 47 | MaterialPageRoute( 48 | builder: (BuildContext context) { 49 | return SearchPage(""); 50 | }, 51 | ), 52 | ); 53 | }, 54 | ), 55 | ], 56 | title: Row( 57 | children: [ 58 | Expanded( 59 | child: TabBar( 60 | labelPadding: EdgeInsets.only( 61 | left: 16, 62 | right: 16, 63 | bottom: 2, 64 | ), 65 | indicatorPadding: EdgeInsets.all(0), 66 | // 选中颜色 67 | labelColor: Colors.white, 68 | // 选中样式 69 | labelStyle: TextStyle(fontSize: 18), 70 | // 未选中颜色 71 | unselectedLabelColor: Colors.white70, 72 | // 未选中样式 73 | unselectedLabelStyle: TextStyle(fontSize: 16), 74 | // 是否可滑动 75 | isScrollable: true, 76 | controller: mController, 77 | // 指示器宽度 78 | indicatorSize: TabBarIndicatorSize.label, 79 | indicatorColor: Colors.white, 80 | // 相当于 indicator 高度 81 | indicatorWeight: 3, 82 | // tab 标签 83 | tabs: list.map((title) { 84 | return Tab( 85 | child: Container( 86 | padding: EdgeInsets.all(0), 87 | child: Text(title), 88 | ), 89 | ); 90 | }).toList(), 91 | ), 92 | ), 93 | ], 94 | ), 95 | backgroundColor: Colors.transparent, 96 | elevation: 0, 97 | ), 98 | decoration: BoxDecoration( 99 | gradient: LinearGradient( 100 | begin: Alignment.topLeft, 101 | end: Alignment.bottomRight, 102 | colors: [ 103 | appTheme.themeColor, 104 | appTheme.themeColor, 105 | ], 106 | ), 107 | ), 108 | ), 109 | preferredSize: Size( 110 | MediaQuery.of(context).size.width, 111 | 50, 112 | ), 113 | ), 114 | body: TabBarView( 115 | controller: mController, 116 | children: [ 117 | SystemCategoryPage(), 118 | SystemSquarePage(), 119 | ], 120 | ), 121 | ); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /lib/pages/system_square_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:pull_to_refresh/pull_to_refresh.dart'; 4 | import 'package:wanandroid_flutter/http/api.dart'; 5 | import 'package:wanandroid_flutter/http/http.dart'; 6 | import 'package:wanandroid_flutter/models/article.dart'; 7 | import 'package:wanandroid_flutter/models/system_article.dart'; 8 | import 'package:wanandroid_flutter/pages/webview_page.dart'; 9 | import 'package:wanandroid_flutter/widgets/article_item.dart'; 10 | 11 | class SystemSquarePage extends StatefulWidget { 12 | @override 13 | State createState() { 14 | return SystemSquarePageState(); 15 | } 16 | } 17 | 18 | class SystemSquarePageState extends State 19 | with AutomaticKeepAliveClientMixin { 20 | int curPage = 0; 21 | List
articleList = new List(); 22 | RefreshController _refreshController = 23 | RefreshController(initialRefresh: false); 24 | 25 | @override 26 | void initState() { 27 | super.initState(); 28 | // 请求数据 29 | _onRefresh(); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | super.build(context); 35 | return SmartRefresher( 36 | enablePullDown: true, 37 | enablePullUp: true, 38 | header: WaterDropHeader(), 39 | footer: CustomFooter( 40 | builder: (BuildContext context, LoadStatus mode) { 41 | Widget body; 42 | if (mode == LoadStatus.idle) { 43 | body = Text("上拉加载"); 44 | } else if (mode == LoadStatus.loading) { 45 | body = CupertinoActivityIndicator(); 46 | } else if (mode == LoadStatus.failed) { 47 | body = Text("加载失败!点击重试!"); 48 | } else if (mode == LoadStatus.canLoading) { 49 | body = Text("上拉加载更多"); 50 | } else { 51 | body = Text("没有更多数据了!"); 52 | } 53 | return Container( 54 | height: 55.0, 55 | child: Center(child: body), 56 | ); 57 | }, 58 | ), 59 | controller: _refreshController, 60 | onRefresh: _onRefresh, 61 | onLoading: _onLoadMore, 62 | child: ListView.separated( 63 | itemBuilder: (context, index) { 64 | return createSystemSquareItem(index); 65 | }, 66 | separatorBuilder: (context, index) { 67 | return Divider( 68 | indent: 12, 69 | endIndent: 12, 70 | height: 0.5, 71 | ); 72 | }, 73 | itemCount: articleList.length, 74 | ), 75 | ); 76 | } 77 | 78 | createSystemSquareItem(int index) { 79 | Article article = articleList[index]; 80 | return ArticleItem( 81 | article.title, 82 | article.niceDate, 83 | article.author.isNotEmpty ? article.author : article.shareUser, 84 | () { 85 | Navigator.push( 86 | context, 87 | MaterialPageRoute( 88 | builder: (BuildContext context) => WebViewPage( 89 | url: article.link, 90 | ), 91 | ), 92 | ); 93 | }, 94 | ); 95 | } 96 | 97 | void onItemClick(int index) { 98 | Navigator.push( 99 | context, 100 | MaterialPageRoute( 101 | builder: (BuildContext context) => WebViewPage( 102 | url: articleList[index].link, 103 | ), 104 | ), 105 | ); 106 | } 107 | 108 | @override 109 | bool get wantKeepAlive => true; 110 | 111 | /// 下拉刷新 112 | void _onRefresh() async { 113 | SystemArticle systemArticle = await _loadSystemSquare(0); 114 | curPage = 0; 115 | setState(() { 116 | articleList.clear(); 117 | articleList.addAll(systemArticle.datas); 118 | }); 119 | _refreshController.refreshCompleted(); 120 | } 121 | 122 | /// 上拉加载更多 123 | _onLoadMore() async { 124 | SystemArticle systemArticle = await _loadSystemSquare(curPage); 125 | List
articles = systemArticle.datas; 126 | setState(() { 127 | articleList.addAll(articles); 128 | }); 129 | // 可能出现返回列表数据<每页数据,因为有自见的文章被过滤掉了。 130 | _refreshController.loadComplete(); 131 | } 132 | 133 | Future _loadSystemSquare(int page) async { 134 | var result = await HttpClient.getInstance() 135 | .get(Api.SQUARE_ARTICLE, data: {"page": page}); 136 | curPage = page + 1; 137 | SystemArticle squareArticle = SystemArticle.fromJson(result); 138 | return squareArticle; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /lib/pages/webview_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:fluttertoast/fluttertoast.dart'; 4 | import 'package:provider/provider.dart'; 5 | import 'package:wanandroid_flutter/provider/app_theme.dart'; 6 | import 'package:wanandroid_flutter/widgets/gradient_appbar.dart'; 7 | import 'package:webview_flutter/webview_flutter.dart'; 8 | 9 | class WebViewPage extends StatefulWidget { 10 | final String url; 11 | final String title; 12 | 13 | WebViewPage({this.url, this.title}); 14 | 15 | @override 16 | State createState() { 17 | return WebViewPageState(); 18 | } 19 | } 20 | 21 | class WebViewPageState extends State { 22 | List> list = new List(); 23 | String loadUrl; 24 | 25 | @override 26 | void initState() { 27 | super.initState(); 28 | list 29 | ..add({"title": "收藏", "icon": Icons.favorite_border}) 30 | ..add({"title": "复制链接", "icon": Icons.link}) 31 | ..add({"title": "浏览器打开", "icon": Icons.open_in_browser}) 32 | ..add({"title": "微信分享", "icon": Icons.share}) 33 | ..add({"title": "刷新", "icon": Icons.refresh}); 34 | } 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | var appTheme = Provider.of(context); 39 | return Scaffold( 40 | backgroundColor: Colors.white, 41 | appBar: GradientAppBar( 42 | title: Text("详情"), 43 | colors: [ 44 | appTheme.themeColor, 45 | appTheme.themeColor, 46 | ], 47 | actions: [ 48 | GestureDetector( 49 | onTap: () { 50 | // 显示底部弹框 51 | showBottomSheet(context, appTheme.themeColor); 52 | }, 53 | child: Container( 54 | padding: EdgeInsets.only( 55 | right: 10, 56 | ), 57 | child: Icon(Icons.more_vert), 58 | ), 59 | ), 60 | ], 61 | ), 62 | body: WebView( 63 | initialUrl: widget.url, 64 | javascriptMode: JavascriptMode.unrestricted, 65 | onWebViewCreated: (WebViewController controller) { 66 | controller.canGoBack().then((res) { 67 | print(res); 68 | }); 69 | controller.currentUrl().then((url) { 70 | print(url); 71 | loadUrl = url; 72 | }); 73 | }, 74 | onPageFinished: (String url) { 75 | print("onPageFinished"); 76 | }, 77 | ), 78 | ); 79 | } 80 | 81 | showBottomSheet(BuildContext context, Color color) { 82 | showModalBottomSheet( 83 | context: context, 84 | builder: (BuildContext context) { 85 | return Container( 86 | height: 290, 87 | padding: EdgeInsets.all(15), 88 | alignment: Alignment.center, 89 | child: GridView.builder( 90 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 91 | crossAxisCount: 4, 92 | mainAxisSpacing: 10, 93 | crossAxisSpacing: 10, 94 | childAspectRatio: 0.8, 95 | ), 96 | itemBuilder: (BuildContext context, int index) { 97 | return GestureDetector( 98 | child: createBottomSheetItem( 99 | color, list[index]['title'], list[index]['icon']), 100 | onTap: () { 101 | Navigator.pop(context); 102 | handleBottomSheetItemClick(context, index); 103 | }, 104 | ); 105 | }, 106 | itemCount: list.length, 107 | ), 108 | ); 109 | }, 110 | ); 111 | } 112 | 113 | createBottomSheetItem(Color color, String title, IconData icon) { 114 | return Column( 115 | children: [ 116 | Container( 117 | margin: EdgeInsets.only( 118 | top: 10, 119 | bottom: 10, 120 | ), 121 | padding: EdgeInsets.all(16), 122 | decoration: BoxDecoration( 123 | color: Theme.of(context).backgroundColor, 124 | borderRadius: BorderRadius.circular(20), 125 | ), 126 | child: Icon( 127 | icon, 128 | color: color, 129 | size: 32, 130 | ), 131 | ), 132 | Text( 133 | title, 134 | maxLines: 1, 135 | style: TextStyle( 136 | fontSize: 14, 137 | ), 138 | ), 139 | ], 140 | ); 141 | } 142 | 143 | void handleBottomSheetItemClick(context, index) { 144 | switch (index) { 145 | case 0: 146 | addArticleFavorite(); 147 | break; 148 | case 1: 149 | copyLink(); 150 | break; 151 | case 2: 152 | openByBrowser(); 153 | break; 154 | case 3: 155 | shareWeChat(); 156 | break; 157 | case 4: 158 | refresh(); 159 | break; 160 | } 161 | } 162 | 163 | /// 添加收藏 164 | void addArticleFavorite() {} 165 | 166 | /// 复制链接 167 | void copyLink() { 168 | ClipboardData data = new ClipboardData(text: loadUrl); 169 | Clipboard.setData(data); 170 | Fluttertoast.showToast(msg: "复制成功"); 171 | } 172 | 173 | /// 从浏览器打开 174 | void openByBrowser() async { 175 | // if (await canLaunch(loadUrl)) { 176 | // await launch(loadUrl); 177 | // } else { 178 | // throw 'Could not launch $loadUrl'; 179 | // } 180 | } 181 | 182 | /// 分享到微信 183 | void shareWeChat() {} 184 | 185 | /// 刷新 186 | void refresh() {} 187 | } 188 | -------------------------------------------------------------------------------- /lib/provider/app_theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AppTheme with ChangeNotifier { 4 | /// 主题颜色 5 | Color _themeColor; 6 | 7 | get themeColor => _themeColor; 8 | 9 | /// 修改主题颜色 10 | void updateThemeColor(Color color) { 11 | this._themeColor = color; 12 | notifyListeners(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/provider/dark_mode.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class DarkMode with ChangeNotifier { 4 | /// 夜间模式 5 | bool _isDark = false; 6 | 7 | void setDark(isDark) { 8 | _isDark = isDark; 9 | notifyListeners(); 10 | } 11 | 12 | get isDark => _isDark; 13 | } 14 | -------------------------------------------------------------------------------- /lib/provider/login_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class LoginState with ChangeNotifier { 4 | /// 是否已登录 5 | bool _loggedin = false; 6 | 7 | get loggedIn => _loggedin; 8 | 9 | /// 更新登录状态 10 | void updateLoginState(bool logged) { 11 | _loggedin = logged; 12 | notifyListeners(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/res/colors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Colours { 4 | static const Color appThemeColor = Color(0xffc54945); 5 | 6 | /// -------- light mode color ----------- /// 7 | static const Color appText = Color(0xff222222); 8 | static const Color appSubText = Color(0xff999999); 9 | static const Color appBackground = Color(0xfff5f5f5); 10 | static const Color appForeground = Color(0xffffffff); 11 | static const Color appDivider = Color(0xffcecece); 12 | static const Color appActionClip = Color(0xffefefef); 13 | 14 | /// -------- dark mode color ----------- /// 15 | static const Color darkAppBackground = Color(0xff191919); 16 | static const Color darkAppForeground = Color(0xff232323); 17 | 18 | static const Color darkAppText = Color(0xff8f8f8f); 19 | static const Color darkAppSubText = Color(0xff606060); 20 | static const Color darkAppDivider = Color(0xff666666); 21 | static const Color darkDialogBackground = Color(0xff303030); 22 | static const Color darkAppActionClip = Color(0xff323232); 23 | } 24 | -------------------------------------------------------------------------------- /lib/res/strings.dart: -------------------------------------------------------------------------------- 1 | class Strings { 2 | static const String USERNAME = "username"; 3 | static const String PASSWORD = "password"; 4 | } 5 | -------------------------------------------------------------------------------- /lib/res/theme_colors.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | List getThemeColors() { 4 | List themeColors = new List(); 5 | themeColors 6 | ..add(Color(0xffc54945)) // 7 | ..add(Color(0xfffc5e38)) // 8 | ..add(Color(0xfffd742d)) // 9 | ..add(Color(0xfff6b816)) // 10 | ..add(Color(0xffcae053)) // 11 | ..add(Color(0xff81c842)) // 12 | ..add(Color(0xff5cc095)) // 13 | ..add(Color(0xff1e88e5)) // 14 | ..add(Color(0xff5978e9)) // 15 | ..add(Color(0xff7668f6)) // 16 | ..add(Color(0xffa674e6)) // 17 | ..add(Color(0xffd477e6)) // 18 | ..add(Color(0xffec7ec5)) // 19 | ..add(Color(0xffed698b)) // 20 | ..add(Color(0xfff19fb4)) // 21 | ..add(Color(0xff323638)); 22 | return themeColors; 23 | } 24 | -------------------------------------------------------------------------------- /lib/utils/file_utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:path_provider/path_provider.dart'; 4 | 5 | /// 获取本地缓存路径 6 | Future getCachePath() async { 7 | var tempDir = await getTemporaryDirectory(); 8 | return tempDir.path; 9 | } 10 | 11 | /// 获取 cookie 缓存路径 12 | Future getCookiePath() async { 13 | String cachePath = await getCachePath(); 14 | return "$cachePath/cookie"; 15 | } 16 | 17 | Future isCookieExist() async { 18 | String cookiePath = await getCookiePath(); 19 | print("cookiePaht = $cookiePath"); 20 | Directory dir = new Directory(cookiePath); 21 | if (dir != null) { 22 | List list = dir.listSync(); 23 | print("list.size = ${list.length}"); 24 | return list != null && list.length > 0; 25 | } 26 | return false; 27 | } 28 | -------------------------------------------------------------------------------- /lib/utils/keyboard_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// 显示软键盘 4 | /// __focusNode 为 TextField 的 FocusNode 属性 5 | void showKeyboard(BuildContext context, FocusNode _focusNode) { 6 | if (MediaQuery.of(context).viewInsets.bottom == 0) { 7 | final focusScope = FocusScope.of(context); 8 | focusScope.requestFocus(FocusNode()); 9 | Future.delayed(Duration.zero, () => focusScope.requestFocus(_focusNode)); 10 | } 11 | } 12 | 13 | /// 隐藏软键盘 14 | void hideKeyboard(BuildContext context) { 15 | FocusScope.of(context).unfocus(); 16 | } 17 | -------------------------------------------------------------------------------- /lib/utils/screen_utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | double getStatusBarHeight() { 6 | return MediaQueryData.fromWindow(window).padding.top; 7 | } 8 | 9 | double getScreenWidth(BuildContext context) { 10 | return MediaQuery.of(context).size.width; 11 | } 12 | 13 | double getScreenHeight(BuildContext context) { 14 | return MediaQuery.of(context).size.height; 15 | } 16 | -------------------------------------------------------------------------------- /lib/widgets/article_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ArticleItem extends StatefulWidget { 4 | final String title; 5 | final String niceDate; 6 | final String shareUser; 7 | final GestureTapCallback callback; 8 | 9 | ArticleItem(this.title, this.niceDate, this.shareUser, this.callback); 10 | 11 | @override 12 | State createState() { 13 | return new ArticleItemState(); 14 | } 15 | } 16 | 17 | class ArticleItemState extends State { 18 | @override 19 | Widget build(BuildContext context) { 20 | return GestureDetector( 21 | behavior: HitTestBehavior.opaque, 22 | onTap: widget.callback, 23 | child: Container( 24 | color: Theme.of(context).backgroundColor, 25 | alignment: Alignment.centerLeft, 26 | padding: EdgeInsets.all(12), 27 | child: Column( 28 | mainAxisAlignment: MainAxisAlignment.center, 29 | crossAxisAlignment: CrossAxisAlignment.start, 30 | children: [ 31 | Text( 32 | widget.title, 33 | style: TextStyle( 34 | fontSize: 16, 35 | ), 36 | ), 37 | Container( 38 | margin: EdgeInsets.only( 39 | top: 10, 40 | ), 41 | child: Row( 42 | children: [ 43 | Container( 44 | child: Text( 45 | widget.niceDate, 46 | style: Theme.of(context).textTheme.body2, 47 | ), 48 | ), 49 | Container( 50 | margin: EdgeInsets.only( 51 | left: 20, 52 | ), 53 | child: Text( 54 | widget.shareUser, 55 | style: Theme.of(context).textTheme.body2, 56 | ), 57 | ), 58 | ], 59 | ), 60 | ), 61 | ], 62 | ), 63 | ), 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/widgets/beizier_path_painter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class BezierPathPainter extends CustomPainter { 4 | Path path = new Path(); 5 | double bigRadius = 30; 6 | double smallRadius = 20; 7 | Color color; 8 | Paint mPaint; 9 | 10 | BezierPathPainter(this.color) { 11 | mPaint = new Paint() 12 | ..color = color 13 | ..style = PaintingStyle.fill 14 | ..isAntiAlias = true; 15 | } 16 | 17 | @override 18 | void paint(Canvas canvas, Size size) { 19 | print("ppppp"); 20 | mPaint.color = color; 21 | canvas.save(); 22 | canvas.translate(size.width / 2, size.height / 2); 23 | 24 | path.moveTo(-size.width / 2, -size.height / 2); 25 | path.lineTo(-size.width / 2, size.height * 2 / 8); 26 | 27 | path.quadraticBezierTo( 28 | 0, size.height / 2, size.width / 2, size.height * 2 / 8); 29 | 30 | // path.cubicTo(size.width / 4, 0, size.width * 3 / 4, size.height, size.width, 31 | // size.height / 2); 32 | path.lineTo(size.width / 2, -size.height / 2); 33 | canvas.drawPath(path, mPaint); 34 | 35 | mPaint.color = Colors.white38; 36 | canvas.drawCircle( 37 | new Offset(size.width / 2 - 2 * bigRadius, -size.height / 5), 38 | bigRadius, 39 | mPaint); 40 | canvas.drawCircle( 41 | new Offset(size.width / 2 - 2 * bigRadius - 30, -size.height / 5 + 10), 42 | smallRadius, 43 | mPaint); 44 | canvas.drawCircle( 45 | new Offset(-size.width / 2 + 3 * smallRadius, 46 | -size.height / 2 + 2 * smallRadius), 47 | smallRadius, 48 | mPaint); 49 | 50 | canvas.restore(); 51 | } 52 | 53 | @override 54 | bool shouldRepaint(CustomPainter oldDelegate) { 55 | return false; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/widgets/bezier_clipper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class BezierClipper extends CustomClipper { 4 | @override 5 | Path getClip(Size size) { 6 | Path path = Path(); 7 | path.moveTo(0, 0); 8 | path.lineTo(0, size.height / 2); 9 | path.cubicTo(size.width / 4, 0, size.width * 3 / 4, size.height, size.width, 10 | size.height / 2); 11 | path.lineTo(size.width, 0); 12 | return path; 13 | } 14 | 15 | @override 16 | bool shouldReclip(CustomClipper oldClipper) { 17 | return false; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/widgets/circle_degree_ring.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | class CircleDegreeRing extends CustomPainter { 7 | Paint mPaint = new Paint() 8 | ..color = Colors.white54 9 | ..isAntiAlias = true 10 | ..style = PaintingStyle.stroke 11 | ..strokeWidth = 2; 12 | 13 | var tipString = const ["加油", "厉害", "很棒"]; 14 | 15 | double startAngle = 5 * pi / 6; 16 | double sweepAngle = 4 * pi / 3; // 240度 17 | double percent; 18 | double gap = 2; 19 | double centerRingWidth = 16; 20 | TextPainter textPainter; 21 | int curValue; 22 | int maxValue; 23 | 24 | Offset pointOffset; 25 | 26 | CircleDegreeRing(this.curValue, this.maxValue) { 27 | percent = curValue / maxValue > 1 ? 1 : curValue / maxValue; 28 | } 29 | 30 | @override 31 | void paint(Canvas canvas, Size size) { 32 | // 绘制半径 = (宽高最小值 - strokeWidth) / 2 33 | double radius = min(size.width, size.height) / 2 - mPaint.strokeWidth / 2; 34 | canvas.save(); 35 | // 平移到中心 36 | canvas.translate(size.width / 2, size.height / 2); 37 | 38 | // 绘制圆点 39 | mPaint.style = PaintingStyle.fill; 40 | mPaint.color = Colors.white24; 41 | pointOffset = new Offset( 42 | cos(1 / 2 * pi + sweepAngle / 2 - sweepAngle * percent) * radius, 43 | -sin(1 / 2 * pi + sweepAngle / 2 - sweepAngle * percent) * radius); 44 | canvas.drawCircle(pointOffset, 12, mPaint); 45 | mPaint.color = Colors.white38; 46 | canvas.drawCircle(pointOffset, 8, mPaint); 47 | mPaint.color = Colors.white; 48 | canvas.drawCircle(pointOffset, 3, mPaint); 49 | 50 | // 已达到的刻度 51 | mPaint.color = Colors.white; 52 | mPaint.style = PaintingStyle.stroke; 53 | mPaint.strokeWidth = 3; 54 | canvas.drawArc(Rect.fromCircle(center: Offset(0, 0), radius: radius), 55 | startAngle, sweepAngle * percent, false, mPaint); 56 | // 未达到的刻度 57 | mPaint.strokeWidth = 2; 58 | mPaint.color = Colors.white54; 59 | canvas.drawArc( 60 | Rect.fromCircle(center: Offset(0, 0), radius: radius), 61 | startAngle + sweepAngle * percent, 62 | sweepAngle - sweepAngle * percent, 63 | false, 64 | mPaint); 65 | 66 | // 绘制中间圆环 67 | // 以达到刻度 68 | mPaint.strokeWidth = centerRingWidth; 69 | mPaint.color = Colors.white54; 70 | canvas.drawArc( 71 | Rect.fromCircle(center: Offset(0, 0), radius: radius - centerRingWidth), 72 | startAngle, 73 | sweepAngle, 74 | false, 75 | mPaint); 76 | mPaint.strokeWidth = 2; 77 | canvas.drawArc( 78 | Rect.fromCircle( 79 | center: Offset(0, 0), radius: radius - centerRingWidth * 3 / 2 - 4), 80 | startAngle, 81 | sweepAngle, 82 | false, 83 | mPaint); 84 | 85 | // 绘制中间分数 86 | textPainter = new TextPainter( 87 | text: TextSpan( 88 | children: [ 89 | TextSpan( 90 | text: curValue.toString(), 91 | style: TextStyle( 92 | color: Colors.white, 93 | fontSize: 34, 94 | ), 95 | ), 96 | TextSpan( 97 | text: "分", 98 | style: TextStyle( 99 | color: Colors.white, 100 | fontSize: 20, 101 | ), 102 | ), 103 | ], 104 | ), 105 | textDirection: TextDirection.ltr, 106 | textAlign: TextAlign.center, 107 | maxLines: 1, 108 | ); 109 | textPainter.layout(); 110 | textPainter.paint( 111 | canvas, Offset(-textPainter.width / 2, -textPainter.height / 2)); 112 | 113 | // 绘制中间分数提示语 114 | textPainter = new TextPainter( 115 | text: TextSpan( 116 | children: [ 117 | TextSpan( 118 | text: getTipString(curValue), 119 | style: TextStyle( 120 | color: Colors.white70, 121 | fontSize: 22, 122 | ), 123 | ), 124 | ], 125 | ), 126 | textDirection: TextDirection.ltr, 127 | textAlign: TextAlign.center, 128 | maxLines: 1, 129 | ); 130 | textPainter.layout(); 131 | textPainter.paint( 132 | canvas, Offset(-textPainter.width / 2, textPainter.height / 2)); 133 | 134 | // 绘制刻度线 135 | canvas.rotate(pi * 4 / 3); 136 | double degreeCount = 8; 137 | mPaint.strokeWidth = 2; 138 | for (int i = 0; i <= degreeCount; i++) { 139 | // 刻度线 140 | canvas.drawLine( 141 | Offset(0, -radius + centerRingWidth / 2), 142 | Offset(0, -radius + centerRingWidth / 2 + centerRingWidth / 2), 143 | mPaint); 144 | // 绘制可短文字 145 | String text = (i * 5).toString(); 146 | Path path = new Path(); 147 | canvas.drawPath(path, mPaint); 148 | textPainter = new TextPainter( 149 | text: TextSpan( 150 | children: [ 151 | TextSpan( 152 | text: text, 153 | style: TextStyle( 154 | color: Colors.white70, 155 | fontSize: 12, 156 | ), 157 | ), 158 | ], 159 | ), 160 | textDirection: TextDirection.ltr, 161 | textAlign: TextAlign.center, 162 | maxLines: 1, 163 | ); 164 | textPainter.layout(); 165 | textPainter.paint(canvas, new Offset(0, -radius + centerRingWidth * 2)); 166 | canvas.rotate(sweepAngle / degreeCount); 167 | } 168 | canvas.restore(); 169 | } 170 | 171 | @override 172 | bool shouldRepaint(CustomPainter oldDelegate) { 173 | return true; 174 | } 175 | 176 | String getTipString(int coinCount) { 177 | if (coinCount >= 4000) { 178 | return "棒极"; 179 | } else if (coinCount >= 3000) { 180 | return "厉害"; 181 | } 182 | return "加油"; 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /lib/widgets/content_empty.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class EmptyWidget extends StatelessWidget { 5 | @override 6 | Widget build(BuildContext context) { 7 | return Column( 8 | children: [ 9 | Icon( 10 | Icons.hourglass_empty, 11 | ), 12 | Text( 13 | "无内容", 14 | style: TextStyle( 15 | color: Colors.white70, 16 | fontSize: 20, 17 | ), 18 | ), 19 | ], 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/widgets/gradient_appbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:provider/provider.dart'; 4 | import 'package:wanandroid_flutter/provider/app_theme.dart'; 5 | 6 | class GradientAppBar extends StatefulWidget implements PreferredSizeWidget { 7 | final Widget leading; 8 | final double height; 9 | final List colors; 10 | final Widget title; 11 | final bool centerTitle; 12 | final List actions; 13 | final Brightness brightness; 14 | 15 | GradientAppBar({ 16 | Key key, 17 | this.leading, 18 | this.height = 50, 19 | this.colors = const [Colors.red, Colors.redAccent], 20 | this.title, 21 | this.centerTitle = false, 22 | this.actions, 23 | this.brightness = Brightness.dark, 24 | }) : super(key: key); 25 | 26 | @override 27 | State createState() { 28 | return GradientAppBarState(); 29 | } 30 | 31 | @override 32 | Size get preferredSize => Size.fromHeight(height); 33 | } 34 | 35 | class GradientAppBarState extends State { 36 | @override 37 | Widget build(BuildContext context) { 38 | return PreferredSize( 39 | preferredSize: widget.preferredSize, 40 | child: Stack( 41 | children: [ 42 | Consumer( 43 | builder: (context, appTheme, child) { 44 | return Container( 45 | child: AppBar( 46 | brightness: widget.brightness, 47 | leading: widget.leading, 48 | titleSpacing: 0, 49 | title: widget.title, 50 | backgroundColor: Colors.transparent, 51 | elevation: 0, 52 | centerTitle: widget.centerTitle, 53 | actions: widget.actions, 54 | ), 55 | decoration: BoxDecoration( 56 | gradient: LinearGradient( 57 | begin: Alignment.topLeft, 58 | end: Alignment.bottomRight, 59 | colors: [ 60 | appTheme.themeColor, 61 | appTheme.themeColor, 62 | ], 63 | ), 64 | ), 65 | ); 66 | }, 67 | ), 68 | ], 69 | ), 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/widgets/header_list_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:wanandroid_flutter/utils/screen_utils.dart'; 3 | 4 | typedef HeaderBuilder = Function(BuildContext context, int position); 5 | typedef ItemBuilder = Function(BuildContext context, int position); 6 | 7 | class HeaderListView extends StatefulWidget { 8 | final List itemList; 9 | final List headerList; 10 | final HeaderBuilder headerBuilder; 11 | final ItemBuilder itemBuilder; 12 | final IndexedWidgetBuilder separatorBuilder; 13 | final ScrollController controller; 14 | 15 | HeaderListView(this.itemList, 16 | {Key key, 17 | this.headerList, 18 | this.headerBuilder, 19 | this.itemBuilder, 20 | this.separatorBuilder, 21 | this.controller}) 22 | : super(key: key); 23 | 24 | @override 25 | State createState() { 26 | return HeaderListViewState(); 27 | } 28 | } 29 | 30 | class HeaderListViewState extends State { 31 | @override 32 | Widget build(BuildContext context) { 33 | return SizedBox( 34 | height: getScreenHeight(context), 35 | child: ListView.separated( 36 | itemBuilder: (BuildContext context, int position) { 37 | return buildItemWidget(context, position); 38 | }, 39 | separatorBuilder: widget.separatorBuilder, 40 | itemCount: getItemCount(), 41 | controller: widget.controller, 42 | ), 43 | ); 44 | } 45 | 46 | getItemCount() { 47 | return getHeaderCount() + 48 | (widget.itemList != null ? widget.itemList.length : 0); 49 | } 50 | 51 | getHeaderCount() { 52 | return widget.headerList != null ? widget.headerList.length : 0; 53 | } 54 | 55 | Widget buildItemWidget(BuildContext context, int position) { 56 | if (position < getHeaderCount()) { 57 | return headerWidget(context, position); 58 | } else { 59 | return itemWidget(context, position - getHeaderCount()); 60 | } 61 | } 62 | 63 | Widget headerWidget(BuildContext context, int position) { 64 | if (widget.headerBuilder != null) { 65 | return widget.headerBuilder(context, position); 66 | } 67 | return GestureDetector( 68 | child: Container( 69 | height: 0, 70 | width: 0, 71 | ), 72 | ); 73 | } 74 | 75 | Widget itemWidget(BuildContext context, int position) { 76 | if (widget.itemBuilder != null) { 77 | return widget.itemBuilder(context, position); 78 | } 79 | return GestureDetector( 80 | child: Container( 81 | width: 0, 82 | height: 0, 83 | ), 84 | ); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /lib/widgets/item_creator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:wanandroid_flutter/res/colors.dart'; 4 | 5 | class ItemCreator { 6 | static createItem(BuildContext context, IconData icon, String text, 7 | GestureTapCallback callback, 8 | {EdgeInsetsGeometry margin, 9 | Widget right = const Icon( 10 | Icons.chevron_right, 11 | color: Colors.black45, 12 | size: 30, 13 | ), 14 | bool hasDivider = false, 15 | bool showMore = true}) { 16 | return GestureDetector( 17 | onTap: callback, 18 | child: Container( 19 | color: Theme.of(context).backgroundColor, 20 | alignment: Alignment.centerLeft, 21 | margin: margin, 22 | padding: EdgeInsets.only( 23 | left: 15, 24 | ), 25 | child: Column( 26 | children: [ 27 | Stack( 28 | children: [ 29 | Container( 30 | height: 52, 31 | ), 32 | Positioned( 33 | height: 52, 34 | child: Container( 35 | alignment: Alignment.center, 36 | child: Visibility( 37 | visible: showMore, 38 | child: right, 39 | ), 40 | ), 41 | right: 5, 42 | ), 43 | Positioned( 44 | child: Icon( 45 | icon, 46 | color: Colours.appThemeColor, 47 | size: 22, 48 | ), 49 | top: 16, 50 | ), 51 | Positioned( 52 | child: Text( 53 | text, 54 | style: TextStyle( 55 | fontSize: 16, 56 | ), 57 | ), 58 | left: 36, 59 | top: 14, 60 | ), 61 | ], 62 | ), 63 | Visibility( 64 | visible: hasDivider, 65 | child: Divider( 66 | indent: 30, 67 | height: 1, 68 | ), 69 | ), 70 | ], 71 | ), 72 | ), 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/widgets/link_text.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/gestures.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:wanandroid_flutter/pages/webview_page.dart'; 4 | 5 | class LinkText extends StatefulWidget { 6 | final String content; 7 | final String url; 8 | final TextStyle contentTextStyle; 9 | 10 | LinkText(this.content, this.url, this.contentTextStyle); 11 | 12 | @override 13 | State createState() { 14 | return LinkTextState(); 15 | } 16 | } 17 | 18 | class LinkTextState extends State { 19 | @override 20 | Widget build(BuildContext context) { 21 | return Container( 22 | padding: EdgeInsets.only( 23 | top: 5, 24 | bottom: 5, 25 | ), 26 | child: RichText( 27 | text: TextSpan( 28 | children: [ 29 | TextSpan( 30 | text: widget.content, 31 | style: widget.contentTextStyle, 32 | recognizer: TapGestureRecognizer() 33 | ..onTap = () { 34 | Navigator.push( 35 | context, 36 | MaterialPageRoute( 37 | builder: (BuildContext context) => WebViewPage( 38 | url: widget.url, 39 | ), 40 | ), 41 | ); 42 | }, 43 | ), 44 | ], 45 | ), 46 | ), 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/widgets/painter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class MyPointer extends CustomPainter { 4 | Paint mPaint = new Paint() 5 | ..strokeWidth = 2 6 | ..color = Colors.red 7 | ..isAntiAlias = true; 8 | 9 | Paint bgPaint = new Paint() 10 | ..strokeWidth = 2 11 | ..color = Colors.white30 12 | ..isAntiAlias = true; 13 | 14 | @override 15 | void paint(Canvas canvas, Size size) { 16 | canvas.translate(size.width / 2, size.height / 2); 17 | canvas.saveLayer( 18 | Rect.fromCircle( 19 | center: Offset(0, 0), 20 | radius: size.width / 2, 21 | ), 22 | mPaint); 23 | canvas.drawCircle(Offset(0, 0), size.width / 2, mPaint); 24 | mPaint 25 | ..color = Colors.cyan 26 | ..strokeWidth = 2; 27 | // 横线 28 | canvas.drawLine( 29 | Offset(-size.width / 2, 0), Offset(size.width / 2, 0), mPaint); 30 | // 竖线 31 | canvas.drawLine( 32 | Offset(0, -size.height / 2), Offset(0, size.height / 2), mPaint); 33 | 34 | // ui.ParagraphBuilder pb = new ui.ParagraphBuilder(ui.ParagraphStyle( 35 | // textAlign: TextAlign.left, 36 | // )) 37 | // ..pushStyle(ui.TextStyle( 38 | // color: Colors.white, 39 | // fontSize: 36, 40 | // fontWeight: FontWeight.normal, 41 | // textBaseline: TextBaseline.alphabetic, 42 | // )) 43 | // ..addText("0099") 44 | // ..pushStyle(ui.TextStyle( 45 | // fontSize: 22, 46 | // textBaseline: TextBaseline.alphabetic, 47 | // )) 48 | // ..addText("分"); 49 | // 50 | // ui.ParagraphConstraints pc = ui.ParagraphConstraints(width: 120); 51 | // ui.Paragraph paragraph = pb.build()..layout(pc); 52 | // var textHeight = paragraph.height; 53 | // canvas.drawParagraph(paragraph, new Offset(-pc.width / 2, -textHeight / 2)); 54 | 55 | TextPainter textPainter = new TextPainter( 56 | text: TextSpan( 57 | children: [ 58 | TextSpan( 59 | text: "中国我爱你", 60 | style: TextStyle( 61 | color: Colors.green, 62 | fontSize: 30, 63 | ), 64 | ), 65 | TextSpan( 66 | text: " 分", 67 | style: TextStyle( 68 | color: Colors.green, 69 | fontSize: 20, 70 | ), 71 | ), 72 | ], 73 | ), 74 | textDirection: TextDirection.ltr, 75 | textAlign: TextAlign.center, 76 | maxLines: 1, 77 | ); 78 | textPainter.layout(); 79 | textPainter.paint( 80 | canvas, Offset(-textPainter.width / 2, -textPainter.height / 2)); 81 | canvas.restore(); 82 | } 83 | 84 | @override 85 | bool shouldRepaint(CustomPainter oldDelegate) { 86 | return false; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /lib/widgets/section_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:provider/provider.dart'; 4 | import 'package:wanandroid_flutter/provider/app_theme.dart'; 5 | 6 | class SectionItem extends StatefulWidget { 7 | final IconData icon; 8 | final String title; 9 | final GestureTapCallback callback; 10 | final EdgeInsetsGeometry margin; 11 | final bool showMore; 12 | final bool hasDivider; 13 | final Widget right; 14 | 15 | SectionItem( 16 | this.icon, 17 | this.title, { 18 | this.callback, 19 | this.margin, 20 | this.showMore = true, 21 | this.hasDivider = false, 22 | this.right = const Icon( 23 | Icons.chevron_right, 24 | color: Colors.black45, 25 | size: 30, 26 | ), 27 | }); 28 | 29 | @override 30 | State createState() { 31 | return new SectionItemState(); 32 | } 33 | } 34 | 35 | class SectionItemState extends State { 36 | @override 37 | Widget build(BuildContext context) { 38 | return GestureDetector( 39 | onTap: widget.callback, 40 | child: Container( 41 | color: Theme.of(context).backgroundColor, 42 | alignment: Alignment.centerLeft, 43 | margin: widget.margin, 44 | padding: EdgeInsets.only( 45 | left: 15, 46 | ), 47 | child: Column( 48 | children: [ 49 | Stack( 50 | children: [ 51 | Container( 52 | height: 52, 53 | ), 54 | Positioned( 55 | height: 52, 56 | child: Container( 57 | alignment: Alignment.center, 58 | child: Visibility( 59 | visible: widget.showMore, 60 | child: widget.right, 61 | ), 62 | ), 63 | right: 5, 64 | ), 65 | Positioned( 66 | child: Consumer( 67 | builder: (context, provider, child) { 68 | return Icon( 69 | widget.icon, 70 | color: provider.themeColor, 71 | size: 22, 72 | ); 73 | }, 74 | ), 75 | top: 16, 76 | ), 77 | Positioned( 78 | child: Text( 79 | widget.title, 80 | style: TextStyle( 81 | fontSize: 16, 82 | ), 83 | ), 84 | left: 36, 85 | top: 14, 86 | ), 87 | ], 88 | ), 89 | Visibility( 90 | visible: widget.hasDivider, 91 | child: Divider( 92 | indent: 30, 93 | height: 1, 94 | ), 95 | ), 96 | ], 97 | ), 98 | ), 99 | ); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /lib/widgets/xtextfield.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | 4 | import '../provider/app_theme.dart'; 5 | 6 | class XTextField extends StatefulWidget { 7 | final TextEditingController controller; 8 | final Color prefixIconColor; 9 | final String hintText; 10 | final IconData prefixIcon; 11 | final Widget suffixIcon; 12 | final bool obscureText; 13 | final ValueChanged onChanged; 14 | 15 | XTextField( 16 | this.controller, 17 | this.hintText, { 18 | this.prefixIcon, 19 | this.prefixIconColor, 20 | this.obscureText = true, 21 | this.suffixIcon, 22 | this.onChanged, 23 | }) : super(); 24 | 25 | @override 26 | State createState() { 27 | return XTextFieldState(); 28 | } 29 | } 30 | 31 | class XTextFieldState extends State { 32 | bool hasClearIcon = false; 33 | FocusNode focusNode = new FocusNode(); 34 | bool hasFocus = false; 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | focusNode.addListener(() { 39 | setState(() { 40 | hasFocus = focusNode.hasFocus; 41 | }); 42 | }); 43 | return TextField( 44 | controller: widget.controller, 45 | obscureText: widget.obscureText, 46 | focusNode: focusNode, 47 | onChanged: (text) { 48 | widget.onChanged(text); 49 | setState(() { 50 | hasClearIcon = text.isNotEmpty; 51 | }); 52 | }, 53 | //是否是密码 54 | style: TextStyle( 55 | fontSize: 16, 56 | color: Theme.of(context).textTheme.body1.color, 57 | ), 58 | decoration: InputDecoration( 59 | hintText: widget.hintText, 60 | hintStyle: TextStyle( 61 | color: Theme.of(context).textTheme.body1.color, 62 | ), 63 | prefixIcon: Consumer( 64 | builder: (context, appTheme, child) { 65 | return Icon( 66 | widget.prefixIcon, 67 | color: hasFocus 68 | ? appTheme.themeColor 69 | : Theme.of(context).textTheme.body2.color, 70 | ); 71 | }, 72 | ), 73 | suffixIcon: GestureDetector( 74 | onTap: () { 75 | widget.controller.clear(); 76 | setState(() { 77 | hasClearIcon = false; 78 | }); 79 | }, 80 | child: hasClearIcon && hasFocus ? widget.suffixIcon : Text(""), 81 | ), 82 | ), 83 | ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /png/about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/png/about.png -------------------------------------------------------------------------------- /png/article_detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/png/article_detail.png -------------------------------------------------------------------------------- /png/dark_mine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/png/dark_mine.png -------------------------------------------------------------------------------- /png/dark_project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/png/dark_project.png -------------------------------------------------------------------------------- /png/dark_setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/png/dark_setting.png -------------------------------------------------------------------------------- /png/favorite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/png/favorite.png -------------------------------------------------------------------------------- /png/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/png/home.png -------------------------------------------------------------------------------- /png/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/png/login.png -------------------------------------------------------------------------------- /png/mine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/png/mine.png -------------------------------------------------------------------------------- /png/my_points.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/png/my_points.png -------------------------------------------------------------------------------- /png/project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/png/project.png -------------------------------------------------------------------------------- /png/register.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/png/register.png -------------------------------------------------------------------------------- /png/setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/png/setting.png -------------------------------------------------------------------------------- /png/system.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/png/system.png -------------------------------------------------------------------------------- /png/theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/png/theme.png -------------------------------------------------------------------------------- /png/theme_green_mine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/png/theme_green_mine.png -------------------------------------------------------------------------------- /png/theme_green_project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/png/theme_green_project.png -------------------------------------------------------------------------------- /png/theme_green_system.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xing16/WanAndroid-Flutter/74f719deeee28260b5e71070370f884e3a4e8e49/png/theme_green_system.png -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: wanandroid_flutter 2 | description: A new Flutter project. 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 | # In Android, build-name is used as versionName while build-number used as versionCode. 10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 12 | # Read more about iOS versioning at 13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 14 | version: 1.0.0+1 15 | 16 | environment: 17 | sdk: ">=2.1.0 <3.0.0" 18 | 19 | dependencies: 20 | flutter: 21 | sdk: flutter 22 | flutter_localizations: 23 | sdk: flutter 24 | 25 | # The following adds the Cupertino Icons font to your application. 26 | # Use with the CupertinoIcons class for iOS style icons. 27 | cupertino_icons: 0.1.3 28 | flutter_swiper: 1.1.6 29 | dio: 3.0.3 30 | webview_flutter: 0.3.15+1 31 | flutter_staggered_grid_view: 0.3.0 32 | json_annotation: 3.0.0 33 | fluttertoast: 3.1.3 34 | cookie_jar: 1.0.1 35 | pull_to_refresh: 1.5.7 36 | provider: 3.2.0 37 | shared_preferences: 0.4.2 38 | flutter_easyrefresh: 2.0.8 39 | photo_view: 0.9.0 40 | path_provider: 1.5.1 41 | dio_cookie_manager: 1.0.0 42 | flutter_slidable: 0.5.4 43 | 44 | # url_launcher: 5.2.3 45 | 46 | 47 | dev_dependencies: 48 | flutter_test: 49 | sdk: flutter 50 | #这两个是dev的,不要放到上面去了哦 51 | build_runner: 1.7.0 52 | json_serializable: 3.2.3 53 | 54 | 55 | # For information on the generic Dart part of this file, see the 56 | # following page: https://dart.dev/tools/pub/pubspec 57 | 58 | # The following section is specific to Flutter. 59 | flutter: 60 | 61 | # The following line ensures that the Material Icons font is 62 | # included with your application, so that you can use the icons in 63 | # the material Icons class. 64 | uses-material-design: true 65 | 66 | # To add assets to your application, add an assets section, like this: 67 | assets: 68 | - images/avatar.jpeg 69 | - images/android_logo.jpg 70 | - images/avatar_def.png 71 | - images/trophy.png 72 | - images/placeholder.png 73 | - images/favorite_cancel.png 74 | 75 | # An image asset can refer to one or more resolution-specific "variants", see 76 | # https://flutter.dev/assets-and-images/#resolution-aware. 77 | 78 | # For details regarding adding assets from package dependencies, see 79 | # https://flutter.dev/assets-and-images/#from-packages 80 | 81 | # To add custom fonts to your application, add a fonts section here, 82 | # in this "flutter" section. Each entry in this list should have a 83 | # "family" key with the font family name, and a "fonts" key with a 84 | # list giving the asset and other descriptors for the font. For 85 | # example: 86 | # fonts: 87 | # - family: Schyler 88 | # fonts: 89 | # - asset: fonts/Schyler-Regular.ttf 90 | # - asset: fonts/Schyler-Italic.ttf 91 | # style: italic 92 | # - family: Trajan Pro 93 | # fonts: 94 | # - asset: fonts/TrajanPro.ttf 95 | # - asset: fonts/TrajanPro_Bold.ttf 96 | # weight: 700 97 | # 98 | # For details regarding fonts from package dependencies, 99 | # see https://flutter.dev/custom-fonts/#from-packages 100 | --------------------------------------------------------------------------------