├── .gitignore ├── .metadata ├── LICENSE ├── README.md ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── fuusy │ │ │ │ └── flutter_project │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_flutter.png │ │ │ ├── ic_launcher.png │ │ │ └── launch_image.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_flutter.png │ │ │ ├── ic_launcher.png │ │ │ └── launch_image.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_flutter.png │ │ │ ├── ic_launcher.png │ │ │ └── launch_image.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_flutter.png │ │ │ ├── ic_launcher.png │ │ │ └── launch_image.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_flutter.png │ │ │ ├── ic_launcher.png │ │ │ └── launch_image.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── settings_aar.gradle ├── images ├── default_avatar.png ├── ic_about.png ├── ic_favorite.png ├── ic_first.png ├── ic_flutter.png ├── ic_login.png ├── ic_mine_bg.png ├── ic_one.png ├── ic_rank.png ├── ic_second.png ├── ic_setting.png ├── ic_three.png ├── ic_un_favorite.png ├── ic_zan.png ├── icon_big.png ├── icon_daily.png ├── icon_framework.png ├── icon_iv.png ├── icon_jetpack.png ├── icon_kotlin.png ├── icon_op.png ├── icon_open.png └── launch_image.png ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── 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 ├── base │ └── base_refresh_load_state.dart ├── db │ └── sp_cache.dart ├── global.dart ├── http │ ├── core │ │ ├── dio_adapter.dart │ │ ├── f_error.dart │ │ ├── f_net.dart │ │ ├── f_net_adapter.dart │ │ └── f_net_state.dart │ ├── dao │ │ ├── home_dao.dart │ │ ├── login_dao.dart │ │ ├── person_dao.dart │ │ └── project_dao.dart │ └── request │ │ ├── banner_request.dart │ │ ├── base_request.dart │ │ ├── categoty_request.dart │ │ ├── coin_rank_request.dart │ │ ├── collect_request.dart │ │ ├── home_article_request.dart │ │ ├── login_request.dart │ │ ├── logout_request.dart │ │ ├── my_collect_request.dart │ │ ├── project_request.dart │ │ ├── project_tab_request.dart │ │ ├── register_request.dart │ │ ├── search_request.dart │ │ ├── uncollect_request.dart │ │ └── user_coin_request.dart ├── main.dart ├── model │ ├── category │ │ └── category_model.dart │ ├── home │ │ ├── banner_model.dart │ │ ├── home_article_model.dart │ │ └── search_hot_model.dart │ ├── mine │ │ ├── coin_model.dart │ │ ├── collect_model.dart │ │ ├── mine_item_model.dart │ │ └── user.dart │ ├── owner.dart │ ├── project │ │ ├── project_model.dart │ │ └── project_tab_model.dart │ └── video_model.dart ├── navigator │ ├── bottom_navigator.dart │ └── f_navigatior.dart ├── page │ ├── about_page.dart │ ├── article_page.dart │ ├── category_page.dart │ ├── coin_rank_page.dart │ ├── home_page.dart │ ├── login_page.dart │ ├── mine_page.dart │ ├── my_collect_page.dart │ ├── project_page.dart │ ├── project_top_tab_page.dart │ ├── register_page.dart │ ├── search_delegate.dart │ ├── setting_page.dart │ ├── video_detail_page.dart │ └── webview_page.dart ├── provider │ ├── provider_manager.dart │ ├── theme_provider.dart │ └── user_provider.dart ├── utils │ ├── cache_util.dart │ ├── color.dart │ ├── constants.dart │ ├── string_util.dart │ ├── toast_util.dart │ └── view_util.dart └── widget │ ├── app_toolbar.dart │ ├── article_item.dart │ ├── blur_view.dart │ ├── card_view.dart │ ├── f_banner.dart │ ├── login_button.dart │ ├── login_input.dart │ ├── navigation_bar.dart │ └── setting_item.dart ├── pubspec.lock ├── pubspec.yaml ├── screenshots ├── 1.jpg ├── 2.jpg ├── 3.jpg ├── 4.jpg ├── 5.jpg ├── 6.jpg ├── 7.png └── 8.jpg └── test └── widget_test.dart /.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 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | -------------------------------------------------------------------------------- /.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: b22742018b3edf16c6cadd7b76d9db5e7f9064b5 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 fuusy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flutter-ReadAndroid 2 | 3 | 4 | 5 | 这个项目还是以[玩AndroidAPI](https://www.wanandroid.com/blog/show/2)为基础数据进行构建,目前完成首页、项目分类、文章体系筛选、个人中心、登录、注册、收藏页等功能。 6 | 7 | **Flutter版**项目地址:[**fuusy/flutter_read**](https://github.com/fuusy/flutter_read) 8 | 9 | 另外, 10 | 11 | **Jetpack版**请转地址 [**fuusy/component-jetpack-mvvm**](https://github.com/fuusy/component-jetpack-mvvm) 12 | 13 | ## 项目预览 14 | 使用浏览器扫描二维码可下载【轻阅】体验。 15 | 16 | ![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/16456d6ce1524a22b788a0e890df2efc~tplv-k3u1fbpfcp-watermark.image?) 17 | 18 | 预览图 19 | 20 | | 首页 | 登录 | 21 | | --- | --- | 22 | | ![0a864f2b8cbae3f356353734a4afbf2.jpg](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fe76225613b041a2b6d07a0d89a00d5b~tplv-k3u1fbpfcp-watermark.image?) |![5c1e8035c71efb7198042d009f4e065.jpg](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3b8225595c17473eb0f20bcaeec5f829~tplv-k3u1fbpfcp-watermark.image?) | 23 | 24 | | 文章体系 | 个人中心 | 25 | | --- | --- | 26 | | ![微信图片_20211212110232.jpg](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/099077f3635f4374b6468718ff59404c~tplv-k3u1fbpfcp-watermark.image?) | ![b37963dc7dd59223b55324c97c057e2.jpg](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/dc471f921fa3481d8387e86c243ed87c~tplv-k3u1fbpfcp-watermark.image?) | 27 | 28 | | 项目分类 | 列表 | 29 | | --- | --- | 30 | |![811ac58fe71515e911ec3b265db76de.jpg](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/476ed032962f451eb93529bc46becb00~tplv-k3u1fbpfcp-watermark.image?) | ![5922e3467b927e02b04cea55dd160d1.jpg](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fd9a61c41002403d8a73d3cb98f4b62c~tplv-k3u1fbpfcp-watermark.image?) | 31 | 32 | | 排行榜 | 关于 | 33 | | --- | --- | 34 | |![rank.jpg](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/304de645fd6040b6b6224763cf14ed73~tplv-k3u1fbpfcp-watermark.image?) || 35 | 36 | ## 功能点 37 | - **空安全**:基于2.0进行空安全适配; 38 | - **网络**:封装了Http请求并适配Dio网络库; 39 | - **状态管理**:使用provider进行统一处理; 40 | - **页面跳转**:基于RouterDelegate封装页面跳转组件FRouter; 41 | - **UI组件**:封装toolbar、article_item、navigationBar、banner等UI组件; 42 | - **下拉、上滑加载**:封装下拉刷新、上滑加载更多组件BaseRefreshLoadStateState; 43 | ...... 44 | 45 | ## THANKS 46 | [玩AndroidAPI](https://www.wanandroid.com/blog/show/2) 47 | 48 | 49 | MIT License 50 | 51 | Copyright (c) 2021 fuusy 52 | 53 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | 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 30 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | defaultConfig { 36 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 37 | applicationId "com.fuusy.flutter_project" 38 | minSdkVersion 17 39 | targetSdkVersion 30 40 | versionCode flutterVersionCode.toInteger() 41 | versionName flutterVersionName 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 59 | } 60 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 18 | 22 | 23 | 26 | 31 | 34 | 35 | 36 | 37 | 38 | 39 | 41 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/fuusy/flutter_project/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.fuusy.flutter_project 2 | 3 | import android.graphics.Color 4 | import android.os.Build 5 | import android.os.Bundle 6 | import io.flutter.embedding.android.FlutterActivity 7 | 8 | class MainActivity : FlutterActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 12 | val window = activity.window 13 | //设置状态栏为透明色, 14 | window.statusBarColor = Color.TRANSPARENT 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_flutter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/android/app/src/main/res/mipmap-hdpi/ic_flutter.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/android/app/src/main/res/mipmap-hdpi/launch_image.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_flutter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/android/app/src/main/res/mipmap-mdpi/ic_flutter.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/android/app/src/main/res/mipmap-mdpi/launch_image.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_flutter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/android/app/src/main/res/mipmap-xhdpi/ic_flutter.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/android/app/src/main/res/mipmap-xhdpi/launch_image.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_flutter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/android/app/src/main/res/mipmap-xxhdpi/ic_flutter.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/android/app/src/main/res/mipmap-xxhdpi/launch_image.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_flutter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/android/app/src/main/res/mipmap-xxxhdpi/ic_flutter.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/android/app/src/main/res/mipmap-xxxhdpi/launch_image.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 17 | 20 | 21 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /android/settings_aar.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /images/default_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/images/default_avatar.png -------------------------------------------------------------------------------- /images/ic_about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/images/ic_about.png -------------------------------------------------------------------------------- /images/ic_favorite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/images/ic_favorite.png -------------------------------------------------------------------------------- /images/ic_first.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/images/ic_first.png -------------------------------------------------------------------------------- /images/ic_flutter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/images/ic_flutter.png -------------------------------------------------------------------------------- /images/ic_login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/images/ic_login.png -------------------------------------------------------------------------------- /images/ic_mine_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/images/ic_mine_bg.png -------------------------------------------------------------------------------- /images/ic_one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/images/ic_one.png -------------------------------------------------------------------------------- /images/ic_rank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/images/ic_rank.png -------------------------------------------------------------------------------- /images/ic_second.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/images/ic_second.png -------------------------------------------------------------------------------- /images/ic_setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/images/ic_setting.png -------------------------------------------------------------------------------- /images/ic_three.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/images/ic_three.png -------------------------------------------------------------------------------- /images/ic_un_favorite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/images/ic_un_favorite.png -------------------------------------------------------------------------------- /images/ic_zan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/images/ic_zan.png -------------------------------------------------------------------------------- /images/icon_big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/images/icon_big.png -------------------------------------------------------------------------------- /images/icon_daily.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/images/icon_daily.png -------------------------------------------------------------------------------- /images/icon_framework.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/images/icon_framework.png -------------------------------------------------------------------------------- /images/icon_iv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/images/icon_iv.png -------------------------------------------------------------------------------- /images/icon_jetpack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/images/icon_jetpack.png -------------------------------------------------------------------------------- /images/icon_kotlin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/images/icon_kotlin.png -------------------------------------------------------------------------------- /images/icon_op.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/images/icon_op.png -------------------------------------------------------------------------------- /images/icon_open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/images/icon_open.png -------------------------------------------------------------------------------- /images/launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/images/launch_image.png -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/ephemeral/ 22 | Flutter/app.flx 23 | Flutter/app.zip 24 | Flutter/flutter_assets/ 25 | Flutter/flutter_export_environment.sh 26 | ServiceDefinitions.json 27 | Runner/GeneratedPluginRegistrant.* 28 | 29 | # Exceptions to above rules. 30 | !default.mode1v3 31 | !default.mode2v3 32 | !default.pbxuser 33 | !default.perspectivev3 34 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "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/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/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/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/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/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/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/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/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/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/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/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/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/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/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/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/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/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/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/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/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/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/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/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/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/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/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/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/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/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/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/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/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 | flutter_project 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" 2 | -------------------------------------------------------------------------------- /lib/base/base_refresh_load_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_project/http/core/f_error.dart'; 3 | import 'package:flutter_project/http/core/f_net_state.dart'; 4 | import 'package:flutter_project/utils/color.dart'; 5 | 6 | ///下拉刷新,上拉加载更多框架 7 | ///[M]json请求结果实体类 8 | ///[L]列表数据实体类 9 | ///[T]StatefulWidget,例如首页page 10 | abstract class BaseRefreshLoadStateState 11 | extends FNetState with AutomaticKeepAliveClientMixin { 12 | List dataList = []; 13 | int pagePos = 0; 14 | bool loading = false; 15 | ScrollController scrollController = ScrollController(); 16 | 17 | get child; 18 | 19 | @override 20 | void initState() { 21 | super.initState(); 22 | //上拉加载更多 23 | scrollController.addListener(() { 24 | var distance = scrollController.position.maxScrollExtent - 25 | scrollController.position.pixels; 26 | //距离下方300,且足一屏才加载更多 27 | if (distance < 300 && 28 | !loading && 29 | scrollController.position.maxScrollExtent != 0) { 30 | loadData(isLoadMore: true); 31 | } 32 | }); 33 | loadData(); 34 | } 35 | 36 | @override 37 | void dispose() { 38 | super.dispose(); 39 | scrollController.dispose(); 40 | } 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | return RefreshIndicator( 45 | child: child, 46 | onRefresh: loadData, 47 | color: primary, 48 | ); 49 | } 50 | 51 | Future loadData({isLoadMore = false}) async { 52 | if (loading) { 53 | return; 54 | } 55 | loading = true; 56 | if (!isLoadMore) { 57 | pagePos = 0; 58 | } 59 | 60 | var curPage = pagePos + (isLoadMore ? 1 : 0); 61 | try { 62 | var result = await getData(curPage); 63 | setState(() { 64 | if (isLoadMore) { 65 | dataList = [...dataList, ...parseList(result)]; 66 | if (parseList(result).length != 0) { 67 | pagePos++; 68 | } 69 | } else { 70 | dataList = parseList(result); 71 | } 72 | }); 73 | Future.delayed(Duration(milliseconds: 1000), () { 74 | loading = false; 75 | }); 76 | } on FNetError catch (e) { 77 | loading = false; 78 | print(e); 79 | } 80 | } 81 | 82 | Future getData(int curPage); 83 | 84 | List parseList(M result); 85 | 86 | @override 87 | bool get wantKeepAlive => true; 88 | } 89 | -------------------------------------------------------------------------------- /lib/db/sp_cache.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter_project/model/mine/user.dart'; 4 | import 'package:shared_preferences/shared_preferences.dart'; 5 | 6 | /// 缓存基类 7 | class SpCache { 8 | static User user = User(); 9 | SharedPreferences? sp; 10 | 11 | SpCache._() { 12 | init(); 13 | } 14 | 15 | static SpCache? _instance; 16 | 17 | SpCache._pre(SharedPreferences preferences) { 18 | this.sp = preferences; 19 | } 20 | 21 | static Future preInit() async { 22 | if (_instance == null) { 23 | var prefs = await SharedPreferences.getInstance(); 24 | _instance = SpCache._pre(prefs); 25 | } 26 | 27 | return _instance; 28 | } 29 | 30 | static SpCache? getInstance() { 31 | if (_instance == null) { 32 | _instance = SpCache._(); 33 | } 34 | return _instance; 35 | } 36 | 37 | void init() async { 38 | if (sp == null) { 39 | sp = await SharedPreferences.getInstance(); 40 | } 41 | } 42 | 43 | setString(String key, String value) { 44 | sp?.setString(key, value); 45 | } 46 | 47 | Object? get(String key) { 48 | return sp?.get(key) ?? null; 49 | } 50 | 51 | ///保存一个对象 52 | void _putObject(String key, Object value) { 53 | sp!.setString(key, json.encode(value)); 54 | } 55 | 56 | ///保存登录后的用户信息 57 | ///已登录 58 | void saveUser(User user) { 59 | _putObject("key_user", user); 60 | sp!.setBool("key_is_login", true); 61 | } 62 | 63 | ///退出登录后,删除登录信息 64 | void clearLoginInfo(){ 65 | sp?.remove("key_is_login"); 66 | sp?.remove("key_user"); 67 | } 68 | 69 | ///判断是否已经登录 70 | bool isLogin() { 71 | return sp!.getBool("key_is_login") ?? false; 72 | } 73 | 74 | ///获取User数据 75 | User getUser() { 76 | Map? userMap = getObject("key_user"); 77 | if(userMap!=null){ 78 | var user = User.fromJsonMap(userMap); 79 | return user; 80 | } 81 | return User(); 82 | } 83 | 84 | Map? getObject(String key) { 85 | String? _data = sp?.getString(key); 86 | return (_data == null || _data.isEmpty) ? null : json.decode(_data); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /lib/global.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter_project/http/core/dio_adapter.dart'; 3 | 4 | class Global { 5 | 6 | 7 | static Future preInit() async{ 8 | await DioAdapter.initCookJar(); 9 | } 10 | } -------------------------------------------------------------------------------- /lib/http/core/dio_adapter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 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:flutter_project/http/request/base_request.dart'; 7 | import 'package:flutter_project/http/core/f_error.dart'; 8 | import 'package:flutter_project/http/core/f_net_adapter.dart'; 9 | import 'package:path_provider/path_provider.dart'; 10 | 11 | ///适配Dio网络请求 12 | ///如果有其他网络库,则可以继承FNetAdapter重写。 13 | class DioAdapter extends FNetAdapter { 14 | static PersistCookieJar? _cookieJar; 15 | 16 | static Future initCookJar() async{ 17 | Directory? directory = await getApplicationDocumentsDirectory(); 18 | _cookieJar = new PersistCookieJar(storage: FileStorage(directory.path)); 19 | } 20 | 21 | @override 22 | Future> send(BaseRequest request) async { 23 | Response? response ; 24 | var option = Options(headers: request.header); 25 | var error; 26 | 27 | try { 28 | var dio = Dio(); 29 | if(_cookieJar==null){ 30 | Directory? directory = await getApplicationDocumentsDirectory(); 31 | _cookieJar = new PersistCookieJar(storage: FileStorage(directory.path)); 32 | } 33 | dio.interceptors.add(CookieManager(_cookieJar!)); 34 | 35 | if (request.httpMethod() == HttpMethod.GET) { 36 | print("${request.url()}"); 37 | response = (await dio.get(request.url(), options: option)) ; 38 | } else if (request.httpMethod() == HttpMethod.POST) { 39 | print("${request.url()} ${request.params} $option"); 40 | response = 41 | (await dio.post(request.url(), data: request.params, options: option)) ; 42 | } else if (request.httpMethod() == HttpMethod.DELETE) { 43 | response = (await dio.delete( 44 | request.url(), data: request.params, options: option)) ; 45 | } 46 | } on DioError catch (e) { 47 | error = e; 48 | response = e.response; 49 | } 50 | 51 | if (error != null) { 52 | throw FNetError(response?.statusCode ??-1, error.toString(),data: await buildRes(response,request)); 53 | } 54 | 55 | return buildRes(response, request); 56 | } 57 | 58 | ///BaseNetResponse 59 | Future> buildRes( 60 | Response? response, BaseRequest request) { 61 | return Future.value(BaseNetResponse( 62 | //?.防止response为空 63 | data: response?.data, 64 | request: request, 65 | errorCode: response?.statusCode, 66 | errorMsg: response?.statusMessage, 67 | extra: response)); 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /lib/http/core/f_error.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | ///需要登录 4 | class NeedLogin extends FNetError { 5 | NeedLogin({int code: 401, String message: '请先登录'}) : super(code, message); 6 | } 7 | 8 | ///需要授权的异常 9 | class NeedAuth extends FNetError { 10 | NeedAuth(String message, {int code: 403, dynamic data}) 11 | : super(code, message); 12 | } 13 | 14 | class FNetError implements Exception { 15 | final int? code; 16 | final String message; 17 | final dynamic data; 18 | 19 | FNetError(this.code, this.message, {this.data}); 20 | } 21 | -------------------------------------------------------------------------------- /lib/http/core/f_net.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter_project/http/request/base_request.dart'; 3 | import 'package:flutter_project/http/core/dio_adapter.dart'; 4 | import 'package:flutter_project/http/core/f_error.dart'; 5 | import 'package:flutter_project/http/core/f_net_adapter.dart'; 6 | 7 | class FNet { 8 | FNet._(); 9 | 10 | static FNet? _instance; 11 | 12 | static FNet getInstance() { 13 | if (_instance == null) { 14 | _instance = FNet._(); 15 | } 16 | return _instance!; 17 | } 18 | 19 | Future fire(BaseRequest request) async { 20 | BaseNetResponse? response; 21 | var error; 22 | try { 23 | response = await send(request); 24 | } on FNetError catch(e){ 25 | error = e; 26 | response = e.data; 27 | printLog(e.message); 28 | }catch(e){ 29 | //其他异常 30 | printLog(e); 31 | } 32 | 33 | if(response==null){ 34 | printLog(error); 35 | } 36 | var result = response?.data; 37 | print(result); 38 | var status = response?.errorCode; 39 | printLog("status = $status"); 40 | switch(status){ 41 | case 200: 42 | return result; 43 | case 401: 44 | throw NeedLogin(); 45 | case 403: 46 | throw NeedAuth(result.toString(),data: result); 47 | default: 48 | throw FNetError(status ?? -1, result.toString(),data: result); 49 | } 50 | 51 | } 52 | 53 | 54 | Future> send(BaseRequest request) async { 55 | FNetAdapter adapter = DioAdapter(); 56 | return adapter.send(request); 57 | } 58 | 59 | void printLog(log) { 60 | print("${log}"); 61 | } 62 | } -------------------------------------------------------------------------------- /lib/http/core/f_net_adapter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter_project/http/request/base_request.dart'; 4 | 5 | 6 | 7 | ///网络请求抽象类 8 | abstract class FNetAdapter{ 9 | Future> send(BaseRequest request); 10 | } 11 | 12 | ///基本数据类型 13 | class BaseNetResponse { 14 | BaseNetResponse( 15 | {this.data, 16 | this.request, 17 | this.errorCode, 18 | this.errorMsg, 19 | this.extra}); 20 | 21 | T? data; 22 | BaseRequest? request; 23 | int? errorCode; 24 | String? errorMsg; 25 | dynamic extra; 26 | 27 | @override 28 | String toString() { 29 | if (data is Map) { 30 | return json.encode(data); 31 | } 32 | return data.toString(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/http/core/f_net_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | ///主要解决页面销毁时,调用setState 4 | abstract class FNetState extends State { 5 | @override 6 | void setState(VoidCallback fn) { 7 | if (mounted) { 8 | super.setState(fn); 9 | } else { 10 | print('页面已销毁'); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/http/dao/home_dao.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_project/http/core/f_net.dart'; 2 | import 'package:flutter_project/http/request/banner_request.dart'; 3 | import 'package:flutter_project/http/request/collect_request.dart'; 4 | import 'package:flutter_project/http/request/home_article_request.dart'; 5 | import 'package:flutter_project/http/request/search_request.dart'; 6 | import 'package:flutter_project/model/home/banner_model.dart'; 7 | import 'package:flutter_project/model/home/home_article_model.dart'; 8 | 9 | class HomeDao { 10 | ///首页banner请求 11 | static getBanner() async { 12 | BannerRequest request = new BannerRequest(); 13 | var result = await FNet.getInstance().fire(request); 14 | List list = result['data']; 15 | List bannerList = list.map((e) { 16 | return BannerModel.fromMap(e); 17 | }).toList(); 18 | return bannerList; 19 | } 20 | 21 | ///从0开始 22 | ///请求首页文章列表 23 | static getHomeArticle(int pageIndex) async{ 24 | HomeArticleRequest request = new HomeArticleRequest(); 25 | request.pathParams = '$pageIndex/json'; 26 | var result = await FNet.getInstance().fire(request); 27 | return HomeArticleModel.fromJson(result['data']); 28 | } 29 | 30 | ///根据cid请求文章 31 | static getArticleByCid(int pageIndex,int cid) async{ 32 | HomeArticleRequest request = new HomeArticleRequest(); 33 | request.pathParams = "$pageIndex/json"; 34 | request.add("cid",cid); 35 | var result = await FNet.getInstance().fire(request); 36 | return HomeArticleModel.fromJson(result['data']); 37 | } 38 | 39 | ///获取搜素结果 40 | static getSearchResult(int pageIndex,String query) async{ 41 | SearchRequest request = SearchRequest(); 42 | request.pathParams = '/$pageIndex/json'; 43 | request.add("k", query); 44 | var result = await FNet.getInstance().fire(request); 45 | print("搜索结果 $result"); 46 | return HomeArticleModel.fromJson(result['data']); 47 | } 48 | 49 | ///收藏文章 50 | static collectArticle(int id) async{ 51 | CollectRequest request = CollectRequest(); 52 | request.pathParams = "/$id/json"; 53 | var result = await FNet.getInstance().fire(request); 54 | print("收藏: $result"); 55 | return result; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/http/dao/login_dao.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | import 'package:flutter_project/db/sp_cache.dart'; 4 | import 'package:flutter_project/http/request/base_request.dart'; 5 | import 'package:flutter_project/http/core/f_net.dart'; 6 | import 'package:flutter_project/http/request/login_request.dart'; 7 | import 'package:flutter_project/http/request/logout_request.dart'; 8 | import 'package:flutter_project/http/request/register_request.dart'; 9 | 10 | class LoginDao { 11 | static const LOGIN_TOKEN = "LOGIN_TOKEN"; 12 | 13 | ///登录 14 | static login(String userName, String passWord) { 15 | return _send(userName, passWord); 16 | } 17 | 18 | ///注册 19 | static registration(String userName, String passWord, String rePassWord) { 20 | return _send(userName, passWord, rePassWord: rePassWord); 21 | } 22 | 23 | ///退出登录 24 | static logout() async { 25 | LogoutRequest request = new LogoutRequest(); 26 | await FNet.getInstance().fire(request); 27 | 28 | } 29 | 30 | static _send(String userName, String passWord, {rePassWord}) async { 31 | BaseRequest request; 32 | if (rePassWord != null) { 33 | request = RegisterRequest(); 34 | } else { 35 | request = LoginRequest(); 36 | } 37 | request 38 | .add("username", userName) 39 | .add("password", passWord) 40 | .add("repassword", rePassWord ?? ''); 41 | var result = await FNet.getInstance().fire(request); 42 | print("result = $result"); 43 | 44 | if (result['code'] == 0 && result['data'] != null) { 45 | SpCache.getInstance()?.setString(LOGIN_TOKEN, result['data']); 46 | } 47 | return result; 48 | } 49 | 50 | static getToken() { 51 | return SpCache.getInstance()?.get(LOGIN_TOKEN); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/http/dao/person_dao.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter_project/http/core/f_net.dart'; 3 | import 'package:flutter_project/http/request/coin_rank_request.dart'; 4 | import 'package:flutter_project/http/request/my_collect_request.dart'; 5 | import 'package:flutter_project/http/request/user_coin_request.dart'; 6 | import 'package:flutter_project/model/mine/coin_model.dart'; 7 | import 'package:flutter_project/model/mine/collect_model.dart'; 8 | 9 | class PersonDao{ 10 | 11 | 12 | ///获取个人积分,需要登录 13 | static getUserCoin() async{ 14 | UserCoinRequest request = UserCoinRequest(); 15 | var result = await FNet.getInstance().fire(request); 16 | print("个人积分 $result"); 17 | } 18 | 19 | ///我的收藏列表,需登录 20 | static getMyCollectList(int pageIndex) async{ 21 | MyCollectRequest request = MyCollectRequest(); 22 | request.pathParams="$pageIndex/json"; 23 | var result = await FNet.getInstance().fire(request); 24 | return CollectModel.fromJson(result['data']); 25 | } 26 | 27 | ///积分排行榜 28 | static getCoinRank() async{ 29 | CoinRankRequest request = CoinRankRequest(); 30 | var result = await FNet.getInstance().fire(request); 31 | return CoinModel.fromJson(result['data']); 32 | } 33 | } -------------------------------------------------------------------------------- /lib/http/dao/project_dao.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter_project/http/core/f_net.dart'; 4 | import 'package:flutter_project/http/request/categoty_request.dart'; 5 | import 'package:flutter_project/http/request/project_request.dart'; 6 | import 'package:flutter_project/http/request/project_tab_request.dart'; 7 | import 'package:flutter_project/model/category/category_model.dart'; 8 | import 'package:flutter_project/model/project/project_model.dart'; 9 | import 'package:flutter_project/model/project/project_tab_model.dart'; 10 | 11 | class ProjectDao { 12 | 13 | 14 | ///项目模块 上方tab请求 15 | static getTab() async{ 16 | ProjectTabRequest request = ProjectTabRequest(); 17 | var result = await FNet.getInstance().fire(request); 18 | List tabList = result['data']; 19 | List list = tabList.map((e) { 20 | return new ProjectTabModel.fromJson(e); 21 | }).toList(); 22 | 23 | return list; 24 | } 25 | 26 | ///根据tab的cid分页请求项目模块数据 27 | static getProjectFromTab(int pagePos, int cid) async{ 28 | ProjectRequest request = ProjectRequest(); 29 | request.pathParams="$pagePos/json"; 30 | request.add("cid", cid); 31 | var result = await FNet.getInstance().fire(request); 32 | return ProjectModel.fromJson(result['data']); 33 | } 34 | 35 | ///获取文章体系 36 | static getCategory() async { 37 | CategoryRequest request = CategoryRequest(); 38 | var result = await FNet.getInstance().fire(request); 39 | List list = result['data']; 40 | List categoryList = list.map((e) { 41 | return CategoryModel.fromJson(e); 42 | }).toList(); 43 | return categoryList; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/http/request/banner_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_project/http/request/base_request.dart'; 2 | 3 | ///首页banner请求 4 | ///https://www.wanandroid.com/banner/json 5 | class BannerRequest extends BaseRequest { 6 | @override 7 | HttpMethod httpMethod() { 8 | return HttpMethod.GET; 9 | } 10 | 11 | @override 12 | bool needLogin() { 13 | return false; 14 | } 15 | 16 | @override 17 | String path() { 18 | return "banner/json"; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/http/request/base_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_project/http/dao/login_dao.dart'; 2 | import 'package:flutter_project/main.dart'; 3 | 4 | enum HttpMethod { GET, POST, DELETE } 5 | 6 | ///每个接口请求,继承该抽象类, 7 | abstract class BaseRequest{ 8 | var pathParams; 9 | var useHttps = true; 10 | 11 | //域名 12 | String authority() { 13 | return "www.wanandroid.com"; 14 | } 15 | 16 | HttpMethod httpMethod(); 17 | 18 | String path(); 19 | 20 | String url() { 21 | Uri uri; 22 | var pathStr = path(); 23 | 24 | if (pathParams != null) { 25 | if (path().endsWith("/")) { 26 | pathStr = "${path()}$pathParams"; 27 | } else { 28 | pathStr = "${path()}/$pathParams"; 29 | } 30 | } 31 | 32 | if (useHttps) { 33 | uri = Uri.https(authority(), pathStr, params); 34 | } else { 35 | uri = Uri.http(authority(), pathStr, params); 36 | } 37 | 38 | if(needLogin()){ 39 | //需要登录,请求头添加token 40 | //addHeader(LoginDao.LOGIN_TOKEN, LoginDao.getToken()); 41 | } 42 | return uri.toString(); 43 | } 44 | 45 | //是否需要登录 46 | bool needLogin(); 47 | 48 | Map params = Map(); 49 | 50 | BaseRequest add(String k, Object v) { 51 | params[k] = v.toString(); 52 | return this; 53 | } 54 | 55 | //添加header 56 | Map header = {}; 57 | 58 | BaseRequest addHeader(String k, Object v) { 59 | header[k] = v.toString(); 60 | return this; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/http/request/categoty_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_project/http/request/base_request.dart'; 2 | 3 | class CategoryRequest extends BaseRequest{ 4 | @override 5 | HttpMethod httpMethod() { 6 | return HttpMethod.GET; 7 | } 8 | 9 | @override 10 | bool needLogin() { 11 | return false; 12 | } 13 | 14 | @override 15 | String path() { 16 | return "tree/json"; 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /lib/http/request/coin_rank_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_project/http/request/base_request.dart'; 2 | 3 | class CoinRankRequest extends BaseRequest{ 4 | @override 5 | HttpMethod httpMethod() { 6 | return HttpMethod.GET; 7 | } 8 | 9 | @override 10 | bool needLogin() { 11 | return false; 12 | } 13 | 14 | @override 15 | String path() { 16 | return "coin/rank/1/json"; 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /lib/http/request/collect_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_project/http/request/base_request.dart'; 2 | 3 | class CollectRequest extends BaseRequest{ 4 | @override 5 | HttpMethod httpMethod() { 6 | return HttpMethod.POST; 7 | 8 | } 9 | 10 | @override 11 | bool needLogin() { 12 | return true; 13 | } 14 | 15 | @override 16 | String path() { 17 | return "lg/collect"; 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /lib/http/request/home_article_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_project/http/request/base_request.dart'; 2 | 3 | ///首页文章列表 4 | ///https://www.wanandroid.com/article/list/0/json 5 | class HomeArticleRequest extends BaseRequest{ 6 | @override 7 | HttpMethod httpMethod() { 8 | return HttpMethod.GET; 9 | } 10 | 11 | @override 12 | bool needLogin() { 13 | return false; 14 | } 15 | 16 | @override 17 | String path() { 18 | return 'article/list/'; 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /lib/http/request/login_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_project/http/request/base_request.dart'; 2 | 3 | class LoginRequest extends BaseRequest { 4 | @override 5 | HttpMethod httpMethod() { 6 | return HttpMethod.POST; 7 | } 8 | 9 | @override 10 | bool needLogin() { 11 | return false; 12 | } 13 | 14 | @override 15 | String path() { 16 | return "/user/login"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/http/request/logout_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_project/http/request/base_request.dart'; 2 | 3 | class LogoutRequest extends BaseRequest{ 4 | @override 5 | HttpMethod httpMethod() { 6 | return HttpMethod.GET; 7 | } 8 | 9 | @override 10 | bool needLogin() { 11 | return true; 12 | } 13 | 14 | @override 15 | String path() { 16 | return "user/logout/json"; 17 | 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /lib/http/request/my_collect_request.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter_project/http/request/base_request.dart'; 3 | 4 | class MyCollectRequest extends BaseRequest{ 5 | @override 6 | HttpMethod httpMethod() { 7 | return HttpMethod.GET; 8 | } 9 | 10 | @override 11 | bool needLogin() { 12 | return true; 13 | } 14 | 15 | @override 16 | String path() { 17 | return "lg/collect/list"; 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /lib/http/request/project_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_project/http/request/base_request.dart'; 2 | 3 | class ProjectRequest extends BaseRequest{ 4 | @override 5 | HttpMethod httpMethod() { 6 | return HttpMethod.GET; 7 | } 8 | 9 | @override 10 | bool needLogin() { 11 | return false; 12 | } 13 | 14 | @override 15 | String path() { 16 | return "project/list/"; 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /lib/http/request/project_tab_request.dart: -------------------------------------------------------------------------------- 1 | import 'base_request.dart'; 2 | 3 | class ProjectTabRequest extends BaseRequest { 4 | @override 5 | HttpMethod httpMethod() { 6 | return HttpMethod.GET; 7 | } 8 | 9 | @override 10 | bool needLogin() { 11 | return false; 12 | } 13 | 14 | @override 15 | String path() { 16 | return "project/tree/json"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/http/request/register_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_project/http/request/base_request.dart'; 2 | 3 | class RegisterRequest extends BaseRequest { 4 | @override 5 | HttpMethod httpMethod() { 6 | return HttpMethod.POST; 7 | } 8 | 9 | @override 10 | bool needLogin() { 11 | return false; 12 | } 13 | 14 | @override 15 | String path() { 16 | return "/user/register"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/http/request/search_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_project/http/request/base_request.dart'; 2 | 3 | class SearchRequest extends BaseRequest{ 4 | @override 5 | HttpMethod httpMethod() { 6 | return HttpMethod.POST; 7 | } 8 | 9 | @override 10 | bool needLogin() { 11 | return false; 12 | } 13 | 14 | @override 15 | String path() { 16 | return "article/query"; 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /lib/http/request/uncollect_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_project/http/request/base_request.dart'; 2 | 3 | ///取消收藏 4 | class UnCollectRequest extends BaseRequest{ 5 | @override 6 | HttpMethod httpMethod() { 7 | return HttpMethod.POST; 8 | } 9 | 10 | @override 11 | bool needLogin() { 12 | return true; 13 | } 14 | 15 | @override 16 | String path() { 17 | return "lg/uncollect_originId"; 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /lib/http/request/user_coin_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_project/http/request/base_request.dart'; 2 | 3 | ///个人积分 4 | class UserCoinRequest extends BaseRequest{ 5 | @override 6 | HttpMethod httpMethod() { 7 | return HttpMethod.GET; 8 | } 9 | 10 | @override 11 | bool needLogin() { 12 | return false; 13 | } 14 | 15 | @override 16 | String path() { 17 | return "lg/coin/userinfo/json"; 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_project/db/sp_cache.dart'; 3 | import 'package:flutter_project/global.dart'; 4 | import 'package:flutter_project/navigator/bottom_navigator.dart'; 5 | import 'package:flutter_project/navigator/f_navigatior.dart'; 6 | import 'package:flutter_project/page/about_page.dart'; 7 | import 'package:flutter_project/page/article_page.dart'; 8 | import 'package:flutter_project/page/coin_rank_page.dart'; 9 | import 'package:flutter_project/page/login_page.dart'; 10 | import 'package:flutter_project/page/my_collect_page.dart'; 11 | import 'package:flutter_project/page/register_page.dart'; 12 | import 'package:flutter_project/page/setting_page.dart'; 13 | import 'package:flutter_project/page/video_detail_page.dart'; 14 | import 'package:flutter_project/page/webview_page.dart'; 15 | import 'package:flutter_project/provider/provider_manager.dart'; 16 | import 'package:flutter_project/provider/theme_provider.dart'; 17 | import 'package:flutter_project/utils/toast_util.dart'; 18 | import 'package:provider/provider.dart'; 19 | 20 | import 'http/core/dio_adapter.dart'; 21 | import 'model/video_model.dart'; 22 | 23 | void main() { 24 | runApp(FApp()); 25 | } 26 | 27 | class FApp extends StatefulWidget { 28 | const FApp({Key? key}) : super(key: key); 29 | 30 | @override 31 | _FAppState createState() => _FAppState(); 32 | } 33 | 34 | class _FAppState extends State { 35 | FRouteDelegate _routeDelegate = FRouteDelegate(); 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | DioAdapter.initCookJar(); 40 | return FutureBuilder( 41 | future: SpCache.preInit(), 42 | builder: (BuildContext context, AsyncSnapshot snap) { 43 | var widget = snap.connectionState == ConnectionState.done 44 | ? Router( 45 | routerDelegate: _routeDelegate, 46 | ) 47 | : Scaffold( 48 | body: Center( 49 | child: CircularProgressIndicator(), 50 | ), 51 | ); 52 | 53 | return MultiProvider( 54 | providers: topProviders, 55 | child: Consumer(builder: (BuildContext context, 56 | ThemeProvider themeProvider, Widget? child) { 57 | return MaterialApp( 58 | home: widget, 59 | theme: themeProvider.getTheme(), 60 | darkTheme: themeProvider.getTheme(isDarkMode: true), 61 | themeMode: themeProvider.getThemeMode(), 62 | ); 63 | }), 64 | ); 65 | }); 66 | } 67 | } 68 | 69 | class FRouteDelegate extends RouterDelegate 70 | with ChangeNotifier, PopNavigatorRouterDelegateMixin { 71 | final GlobalKey navigatorKey; 72 | 73 | FRouteDelegate() : navigatorKey = GlobalKey() { 74 | //跳转listener 75 | FRouter.getInstance()!.registerRouteJumpListener( 76 | RouteIntentListener(onJumpTo: (RouteStatus status, {Map? args}) { 77 | _routeStatus = status; 78 | switch (status) { 79 | case RouteStatus.detail: 80 | this.videoModel = args!['videoModel']; 81 | break; 82 | case RouteStatus.webview: 83 | this.articleUrl = args!['article_path']; 84 | this.articleTitle = args['article_title']; 85 | break; 86 | case RouteStatus.article: 87 | this.cid = args!['article_cid']; 88 | this.articleTitle = args['article_title']; 89 | break; 90 | } 91 | 92 | notifyListeners(); 93 | })); 94 | } 95 | 96 | List pages = []; 97 | VideoModel? videoModel; 98 | String? articleUrl; 99 | String? articleTitle; 100 | int cid = 0; 101 | 102 | RoutePath? path; 103 | RouteStatus _routeStatus = RouteStatus.home; 104 | 105 | RouteStatus get routeStatus { 106 | if ((_routeStatus == RouteStatus.collect) && 107 | !SpCache.getInstance()!.isLogin()) { 108 | showToast("请先登录"); 109 | return _routeStatus = RouteStatus.login; 110 | } else if (videoModel != null) { 111 | return _routeStatus = RouteStatus.detail; 112 | } else { 113 | return _routeStatus; 114 | } 115 | } 116 | 117 | @override 118 | Widget build(BuildContext context) { 119 | var index = getPageIndex(pages, routeStatus); 120 | List tempPages = pages; 121 | if (index != -1) { 122 | tempPages = tempPages.sublist(0, index); 123 | } 124 | 125 | //根据状态,创建各个page 126 | var page; 127 | switch (routeStatus) { 128 | case RouteStatus.home: 129 | pages.clear(); 130 | page = pageWrap(BottomNavigator()); 131 | break; 132 | case RouteStatus.login: 133 | page = pageWrap(LoginPage()); 134 | break; 135 | case RouteStatus.register: 136 | page = pageWrap(RegisterPage()); 137 | break; 138 | case RouteStatus.detail: 139 | page = pageWrap(VideoDetailPage(videoModel!)); 140 | break; 141 | case RouteStatus.webview: 142 | page = pageWrap(WebViewPage(url: articleUrl, title: articleTitle)); 143 | break; 144 | case RouteStatus.article: 145 | page = pageWrap(ArticlePage( 146 | cid: cid, 147 | title: articleTitle, 148 | )); 149 | break; 150 | case RouteStatus.collect: 151 | page = pageWrap(MyCollectPage()); 152 | break; 153 | case RouteStatus.about: 154 | page = pageWrap(AboutPage()); 155 | break; 156 | case RouteStatus.setting: 157 | page = pageWrap(SettingPage()); 158 | break; 159 | case RouteStatus.coinRank: 160 | page = pageWrap(CoinRankPage()); 161 | break; 162 | } 163 | 164 | tempPages = [...tempPages, page]; 165 | pages = tempPages; 166 | 167 | //进入页面 通知路由变化 168 | FRouter.getInstance()?.notify(tempPages, pages); 169 | 170 | return WillPopScope( 171 | child: Navigator( 172 | key: navigatorKey, 173 | pages: pages, 174 | onPopPage: (route, result) { 175 | if (route.settings is MaterialPage) { 176 | if ((route.settings as MaterialPage).child is LoginPage) { 177 | // if (LoginDao.getToken() == null) { 178 | // showToast('请先登录'); 179 | // return false; 180 | // } 181 | } 182 | } 183 | 184 | if (!route.didPop(result)) { 185 | return false; 186 | } 187 | 188 | //退出时,通知页面变化 189 | var tempPages = [...pages]; 190 | pages.removeLast(); 191 | FRouter.getInstance()?.notify(pages, tempPages); 192 | return true; 193 | }, 194 | ), 195 | onWillPop: () async => !await navigatorKey.currentState!.maybePop()); 196 | } 197 | 198 | @override 199 | Future setNewRoutePath(RoutePath configuration) async {} 200 | } 201 | 202 | pageWrap(Widget child) { 203 | return MaterialPage(key: ValueKey(child.hashCode), child: child); 204 | } 205 | 206 | ///页面路径 207 | class RoutePath { 208 | final String? location; 209 | 210 | RoutePath.home() : location = "/"; 211 | 212 | RoutePath.detail() : location = "/detail"; 213 | } 214 | -------------------------------------------------------------------------------- /lib/model/category/category_model.dart: -------------------------------------------------------------------------------- 1 | 2 | class CategoryModel { 3 | List? children; 4 | int? courseId; 5 | int? id; 6 | String? name; 7 | int? order; 8 | int? parentChapterId; 9 | bool? userControlSetTop; 10 | int? visible; 11 | 12 | CategoryModel( 13 | {this.children, 14 | this.courseId, 15 | this.id, 16 | this.name, 17 | this.order, 18 | this.parentChapterId, 19 | this.userControlSetTop, 20 | this.visible}); 21 | 22 | CategoryModel.fromJson(Map json) { 23 | if (json['children'] != null) { 24 | children = new List.empty(growable: true); 25 | json['children'].forEach((v) { 26 | children?.add(new Children.fromJson(v)); 27 | }); 28 | } 29 | courseId = json['courseId']; 30 | id = json['id']; 31 | name = json['name']; 32 | order = json['order']; 33 | parentChapterId = json['parentChapterId']; 34 | userControlSetTop = json['userControlSetTop']; 35 | visible = json['visible']; 36 | } 37 | 38 | Map toJson() { 39 | final Map data = new Map(); 40 | if (this.children != null) { 41 | data['children'] = this.children?.map((v) => v.toJson()).toList(); 42 | } 43 | data['courseId'] = this.courseId; 44 | data['id'] = this.id; 45 | data['name'] = this.name; 46 | data['order'] = this.order; 47 | data['parentChapterId'] = this.parentChapterId; 48 | data['userControlSetTop'] = this.userControlSetTop; 49 | data['visible'] = this.visible; 50 | return data; 51 | } 52 | } 53 | 54 | class Children { 55 | List? children; 56 | int? courseId; 57 | int? id; 58 | String? name; 59 | int? order; 60 | int? parentChapterId; 61 | bool? userControlSetTop; 62 | int? visible; 63 | 64 | Children( 65 | {this.children, 66 | this.courseId, 67 | this.id, 68 | this.name, 69 | this.order, 70 | this.parentChapterId, 71 | this.userControlSetTop, 72 | this.visible}); 73 | 74 | Children.fromJson(Map json) { 75 | if (json['children'] != null) { 76 | children = new List.empty(growable: true); 77 | json['children'].forEach((v) { 78 | //children?.add(new Null.fromJson(v)); 79 | }); 80 | } 81 | courseId = json['courseId']; 82 | id = json['id']; 83 | name = json['name']; 84 | order = json['order']; 85 | parentChapterId = json['parentChapterId']; 86 | userControlSetTop = json['userControlSetTop']; 87 | visible = json['visible']; 88 | } 89 | 90 | Map toJson() { 91 | final Map data = new Map(); 92 | if (this.children != null) { 93 | //data['children'] = this.children?.map((v) => v.toJson()).toList(); 94 | } 95 | data['courseId'] = this.courseId; 96 | data['id'] = this.id; 97 | data['name'] = this.name; 98 | data['order'] = this.order; 99 | data['parentChapterId'] = this.parentChapterId; 100 | data['userControlSetTop'] = this.userControlSetTop; 101 | data['visible'] = this.visible; 102 | return data; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /lib/model/home/banner_model.dart: -------------------------------------------------------------------------------- 1 | /// data : [{"desc":"一起来做个App吧","id":10,"imagePath":"https://www.wanandroid.com/blogimgs/50c115c2-cf6c-4802-aa7b-a4334de444cd.png","isVisible":1,"order":1,"title":"一起来做个App吧","type":0,"url":"https://www.wanandroid.com/blog/show/2"},{"desc":"","id":6,"imagePath":"https://www.wanandroid.com/blogimgs/62c1bd68-b5f3-4a3c-a649-7ca8c7dfabe6.png","isVisible":1,"order":1,"title":"我们新增了一个常用导航Tab~","type":1,"url":"https://www.wanandroid.com/navi"},{"desc":"","id":20,"imagePath":"https://www.wanandroid.com/blogimgs/90c6cc12-742e-4c9f-b318-b912f163b8d0.png","isVisible":1,"order":2,"title":"flutter 中文社区 ","type":1,"url":"https://flutter.cn/"}] 2 | /// errorCode : 0 3 | /// errorMsg : "" 4 | 5 | class BannerModel { 6 | late String desc; 7 | late int id; 8 | late String imagePath; 9 | late int isVisible; 10 | late int order; 11 | late String title; 12 | late int type; 13 | late String url; 14 | 15 | static BannerModel fromMap(Map map) { 16 | 17 | BannerModel dataBean = BannerModel(); 18 | dataBean.desc = map['desc']; 19 | dataBean.id = map['id']; 20 | dataBean.imagePath = map['imagePath']; 21 | dataBean.isVisible = map['isVisible']; 22 | dataBean.order = map['order']; 23 | dataBean.title = map['title']; 24 | dataBean.type = map['type']; 25 | dataBean.url = map['url']; 26 | return dataBean; 27 | } 28 | 29 | Map toJson() => { 30 | "desc": desc, 31 | "id": id, 32 | "imagePath": imagePath, 33 | "isVisible": isVisible, 34 | "order": order, 35 | "title": title, 36 | "type": type, 37 | "url": url, 38 | }; 39 | } -------------------------------------------------------------------------------- /lib/model/home/home_article_model.dart: -------------------------------------------------------------------------------- 1 | class HomeArticleModel { 2 | int? curPage; 3 | List? datas; 4 | int? offset; 5 | bool? over; 6 | int? pageCount; 7 | int? size; 8 | int? total; 9 | 10 | HomeArticleModel( 11 | {this.curPage, 12 | this.datas, 13 | this.offset, 14 | this.over, 15 | this.pageCount, 16 | this.size, 17 | this.total}); 18 | 19 | HomeArticleModel.fromJson(Map json) { 20 | curPage = json['curPage']; 21 | if (json['datas'] != null) { 22 | datas = new List.empty(growable: true); 23 | json['datas'].forEach((v) { 24 | datas!.add(new ArticleInfo.fromJson(v)); 25 | }); 26 | } 27 | offset = json['offset']; 28 | over = json['over']; 29 | pageCount = json['pageCount']; 30 | size = json['size']; 31 | total = json['total']; 32 | } 33 | 34 | Map toJson() { 35 | final Map data = new Map(); 36 | data['curPage'] = this.curPage; 37 | if (this.datas != null) { 38 | data['datas'] = this.datas!.map((v) => v.toJson()).toList(); 39 | } 40 | data['offset'] = this.offset; 41 | data['over'] = this.over; 42 | data['pageCount'] = this.pageCount; 43 | data['size'] = this.size; 44 | data['total'] = this.total; 45 | return data; 46 | } 47 | } 48 | 49 | class ArticleInfo { 50 | String? apkLink; 51 | int? audit; 52 | String? author; 53 | bool? canEdit; 54 | int? chapterId; 55 | String? chapterName; 56 | bool? collect; 57 | int? courseId; 58 | String? desc; 59 | String? descMd; 60 | String? envelopePic; 61 | bool? fresh; 62 | String? host; 63 | int? id; 64 | String? link; 65 | String? niceDate; 66 | String? niceShareDate; 67 | String? origin; 68 | String? prefix; 69 | String? projectLink; 70 | int? publishTime; 71 | int? realSuperChapterId; 72 | int? selfVisible; 73 | int? shareDate; 74 | String? shareUser; 75 | int? superChapterId; 76 | String? superChapterName; 77 | List? tags; 78 | String? title; 79 | int? type; 80 | int? userId; 81 | int? visible; 82 | int? zan; 83 | 84 | ArticleInfo( 85 | {this.apkLink, 86 | this.audit, 87 | this.author, 88 | this.canEdit, 89 | this.chapterId, 90 | this.chapterName, 91 | this.collect, 92 | this.courseId, 93 | this.desc, 94 | this.descMd, 95 | this.envelopePic, 96 | this.fresh, 97 | this.host, 98 | this.id, 99 | this.link, 100 | this.niceDate, 101 | this.niceShareDate, 102 | this.origin, 103 | this.prefix, 104 | this.projectLink, 105 | this.publishTime, 106 | this.realSuperChapterId, 107 | this.selfVisible, 108 | this.shareDate, 109 | this.shareUser, 110 | this.superChapterId, 111 | this.superChapterName, 112 | this.tags, 113 | this.title, 114 | this.type, 115 | this.userId, 116 | this.visible, 117 | this.zan}); 118 | 119 | ArticleInfo.fromJson(Map json) { 120 | apkLink = json['apkLink']; 121 | audit = json['audit']; 122 | author = json['author']; 123 | canEdit = json['canEdit']; 124 | chapterId = json['chapterId']; 125 | chapterName = json['chapterName']; 126 | collect = json['collect']; 127 | courseId = json['courseId']; 128 | desc = json['desc']; 129 | descMd = json['descMd']; 130 | envelopePic = json['envelopePic']; 131 | fresh = json['fresh']; 132 | host = json['host']; 133 | id = json['id']; 134 | link = json['link']; 135 | niceDate = json['niceDate']; 136 | niceShareDate = json['niceShareDate']; 137 | origin = json['origin']; 138 | prefix = json['prefix']; 139 | projectLink = json['projectLink']; 140 | publishTime = json['publishTime']; 141 | realSuperChapterId = json['realSuperChapterId']; 142 | selfVisible = json['selfVisible']; 143 | shareDate = json['shareDate']; 144 | shareUser = json['shareUser']; 145 | superChapterId = json['superChapterId']; 146 | superChapterName = json['superChapterName']; 147 | if (json['tags'] != null) { 148 | tags = new List.empty(growable: true); 149 | json['tags'].forEach((v) { 150 | //tags!.add(new Null.fromJson(v)); 151 | }); 152 | } 153 | title = json['title']; 154 | type = json['type']; 155 | userId = json['userId']; 156 | visible = json['visible']; 157 | zan = json['zan']; 158 | } 159 | 160 | Map toJson() { 161 | final Map data = new Map(); 162 | data['apkLink'] = this.apkLink; 163 | data['audit'] = this.audit; 164 | data['author'] = this.author; 165 | data['canEdit'] = this.canEdit; 166 | data['chapterId'] = this.chapterId; 167 | data['chapterName'] = this.chapterName; 168 | data['collect'] = this.collect; 169 | data['courseId'] = this.courseId; 170 | data['desc'] = this.desc; 171 | data['descMd'] = this.descMd; 172 | data['envelopePic'] = this.envelopePic; 173 | data['fresh'] = this.fresh; 174 | data['host'] = this.host; 175 | data['id'] = this.id; 176 | data['link'] = this.link; 177 | data['niceDate'] = this.niceDate; 178 | data['niceShareDate'] = this.niceShareDate; 179 | data['origin'] = this.origin; 180 | data['prefix'] = this.prefix; 181 | data['projectLink'] = this.projectLink; 182 | data['publishTime'] = this.publishTime; 183 | data['realSuperChapterId'] = this.realSuperChapterId; 184 | data['selfVisible'] = this.selfVisible; 185 | data['shareDate'] = this.shareDate; 186 | data['shareUser'] = this.shareUser; 187 | data['superChapterId'] = this.superChapterId; 188 | data['superChapterName'] = this.superChapterName; 189 | if (this.tags != null) { 190 | //data['tags'] = this.tags.map((v) => v!.toJson()).toList(); 191 | } 192 | data['title'] = this.title; 193 | data['type'] = this.type; 194 | data['userId'] = this.userId; 195 | data['visible'] = this.visible; 196 | data['zan'] = this.zan; 197 | return data; 198 | } 199 | } -------------------------------------------------------------------------------- /lib/model/home/search_hot_model.dart: -------------------------------------------------------------------------------- 1 | 2 | ///搜索热词model 3 | class SearchHotModel { 4 | int? id; 5 | String? link; 6 | String? name; 7 | int? order; 8 | int? visible; 9 | 10 | SearchHotModel({this.id, this.link, this.name, this.order, this.visible}); 11 | 12 | SearchHotModel.fromJson(Map json) { 13 | id = json['id']; 14 | link = json['link']; 15 | name = json['name']; 16 | order = json['order']; 17 | visible = json['visible']; 18 | } 19 | 20 | Map toJson() { 21 | final Map data = new Map(); 22 | data['id'] = this.id; 23 | data['link'] = this.link; 24 | data['name'] = this.name; 25 | data['order'] = this.order; 26 | data['visible'] = this.visible; 27 | return data; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/model/mine/coin_model.dart: -------------------------------------------------------------------------------- 1 | 2 | class CoinModel { 3 | int? curPage; 4 | List? datas; 5 | int? offset; 6 | bool? over; 7 | int? pageCount; 8 | int? size; 9 | int? total; 10 | 11 | CoinModel( 12 | {this.curPage, 13 | this.datas, 14 | this.offset, 15 | this.over, 16 | this.pageCount, 17 | this.size, 18 | this.total}); 19 | 20 | CoinModel.fromJson(Map json) { 21 | curPage = json['curPage']; 22 | if (json['datas'] != null) { 23 | datas = new List.empty(growable: true); 24 | json['datas'].forEach((v) { 25 | datas?.add(new Coin.fromJsonMap(v)); 26 | }); 27 | } 28 | offset = json['offset']; 29 | over = json['over']; 30 | pageCount = json['pageCount']; 31 | size = json['size']; 32 | total = json['total']; 33 | } 34 | 35 | Map toJson() { 36 | final Map data = new Map(); 37 | data['curPage'] = this.curPage; 38 | if (this.datas != null) { 39 | data['datas'] = this.datas?.map((v) => v.toJson()).toList(); 40 | } 41 | data['offset'] = this.offset; 42 | data['over'] = this.over; 43 | data['pageCount'] = this.pageCount; 44 | data['size'] = this.size; 45 | data['total'] = this.total; 46 | return data; 47 | } 48 | } 49 | 50 | class Coin { 51 | int? coinCount; 52 | int? level; 53 | String? nickname; 54 | String? rank; 55 | int? userId; 56 | String? username; 57 | 58 | Coin.fromJsonMap( dynamic map): 59 | coinCount = map["coinCount"], 60 | level = map["level"], 61 | nickname = map["nickname"], 62 | rank = map["rank"], 63 | userId = map["userId"], 64 | username = map["username"]; 65 | 66 | Map toJson() { 67 | final Map data = new Map(); 68 | data['coinCount'] = coinCount; 69 | data['level'] = level; 70 | data['nickname'] = nickname; 71 | data['rank'] = rank; 72 | data['userId'] = userId; 73 | data['username'] = username; 74 | return data; 75 | } 76 | } -------------------------------------------------------------------------------- /lib/model/mine/collect_model.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | /// curPage : 1 4 | /// datas : [{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"

今天意外在崩溃上报平台发现一个异常为UndeclaredThrowableException,看名字就比较好奇,大家可以搜索下,尝试回答:

\r\n
    \r\n
  1. 什么时候会抛出此异常?
  2. \r\n
  3. 为什么[1]中重新封装为此异常抛出,这么设计的原因是?
  4. \r\n
","envelopePic":"","id":228235,"link":"https://www.wanandroid.com/wenda/show/20514","niceDate":"2021-11-19 09:16","origin":"","originId":20514,"publishTime":1637284614000,"title":"每日一问 UndeclaredThrowableException 是什么异常?","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"

问题如题:

\r\n

ViewGroup 的 measureChild 方法和 measureChildWithMargins 方法的区别是什么,如何在实际开发中决定选择使用哪一个?

\r\n

来源:可以从这里提问,欢迎大家踊跃提问~

","envelopePic":"","id":222612,"link":"https://wanandroid.com/wenda/show/20130","niceDate":"2021-10-13 14:05","origin":"","originId":20130,"publishTime":1634105137000,"title":"【大家提问】 | ViewGroup 的 measureChild 方法和 measureChildWithMargins 方法的区别是什么?","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":360,"chapterName":"小编发布","courseId":13,"desc":"

这是一个收集建议、功能的帖子。

\r\n

如果你有:

\r\n
    \r\n
  1. 想要添加的功能;
  2. \r\n
  3. 觉得目前需要改进的地方;
  4. \r\n
\r\n

欢迎提出,会在评估后安排更新~

","envelopePic":"","id":222380,"link":"https://www.wanandroid.com/wenda/show/20087","niceDate":"2021-10-12 15:35","origin":"","originId":20087,"publishTime":1634024135000,"title":"给 wanandroid 提个意见吧!","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"

Gson大家一定不陌生,在很多项目中都大规模使用。

\r\n

例如常见的:

\r\n
网络请求\r\n    ->返回Json数据\r\n    ->Gson解析为对象\r\n    ->渲染页面\r\n

很多时候,历史项目包含很多Gson解析对象在UI线程的操作,或者说即使在子线程其实也会影响页面展现速度。

\r\n

大家都了解Gson对于对象的解析,如果不单独的配置TypeAdapter,那么其实内部是充满反射的。

\r\n

问题来了:

\r\n

有没有什么低侵入的方案可以尽可能去除反射操作,从而提升运行效率?描述思路即可。

","envelopePic":"","id":216415,"link":"https://wanandroid.com/wenda/show/19623","niceDate":"2021-08-31 18:51","origin":"","originId":19623,"publishTime":1630407076000,"title":"每日一问 | Gson中序列化对象的操作有低侵入的优化方案吗?","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"

了解应用启动相关代码的同学一定知道:

\r\n

我们的应用启动时,每个进程会对应一个ActivityThread对象,而Application对象在正常情况下也是每个进程只有一个?

\r\n

但是如果你看ActivityThread的源码,你会发现:

\r\n
public final class ActivityThread {\r\n    final ArrayList<Application> mAllApplications\r\n                = new ArrayList<Application>();\r\n    ...\r\n}\r\n

源码直达

\r\n

问题来了:

\r\n
    \r\n
  1. 什么情况下一个ActivityThread对象,会对应多个Application对象,即mAllApplications.size() > 1;
  2. \r\n
  3. 如果找到了1的情况,支持这个目的是?
  4. \r\n
\r\n
\r\n

本问题归因为好奇,硬说使用场景在一些插件化中会尝试构造Application会调用这个,但是这个肯定不是google的本意。

\r\n
","envelopePic":"","id":215620,"link":"https://wanandroid.com/wenda/show/19550","niceDate":"2021-08-27 09:00","origin":"","originId":19550,"publishTime":1630026023000,"title":"每日一问 | 好奇ActivityThread中为什么会有一个 Application的集合?","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"

今天我们来讨论下 Jetpack 中的 ViewModel:

\r\n

大家都知道 ViewModel 有一个特点就是能够在 Activity 发生重建时做数据的恢复。

\r\n

我们就针对这个「重建」与「恢复」问一些问题:

\r\n
    \r\n
  1. ViewModel 在 Activity 发生旋转等配置发生变化所导致的重建,能恢复数据吗?
  2. \r\n
  3. 如果 1 能,尝试从源码角度分析,数据存在哪?怎么存储的?怎么读取的?
  4. \r\n
  5. 当 Activity 切换到后台,被系统杀死(进程存活),此时回到 Activity 导致的重建,ViewModel 的数据能恢复吗?为什么?
  6. \r\n
","envelopePic":"","id":207249,"link":"https://www.wanandroid.com/wenda/show/18930","niceDate":"2021-07-15 09:06","origin":"","originId":18930,"publishTime":1626311178000,"title":"每日一问 | ViewModel 在什么情况下的「销毁重建」能够对数据进行无缝恢复?","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"

关于 Activity 重建,我们探究几个问题:

\r\n
    \r\n
  1. 当前 app 正在前台运行,不在栈顶的 Activity 有可能会因为系统资源,例如内存等不足回收吗?
  2. \r\n
  3. 当 app 处于后台运行,app 进程未被杀死,其内部的 Activity 会被回收吗?
  4. \r\n
  5. 当 app 处于后台运行,app 的进程会被杀死吗?
  6. \r\n
\r\n

如果有能力,建议解释过程中可以配合源码,不一定要全部答出来~

","envelopePic":"","id":207248,"link":"https://www.wanandroid.com/wenda/show/18965","niceDate":"2021-07-15 09:05","origin":"","originId":18965,"publishTime":1626311143000,"title":"每日一问 | 关于 Activity 重建,值得探究的几个问题","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"

欢迎分享:


1. 你觉得不错的学习习惯;

2. 正在使用的不错的 App

","envelopePic":"","id":204923,"link":"https://www.wanandroid.com/wenda/show/8483","niceDate":"2021-07-03 21:55","origin":"","originId":8483,"publishTime":1625320512000,"title":"每日一问 你有什么好的学习习惯 或者 不错的 app 推荐给大家?","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"

最近很多同学烦恼,Flutter和Kotlin不知道该重点学习哪个。

如果是你要做选择,你会怎么选?

答题格式:

我会选Kotlin,因为...

","envelopePic":"","id":204922,"link":"https://wanandroid.com/wenda/show/8435","niceDate":"2021-07-03 21:54","origin":"","originId":8435,"publishTime":1625320490000,"title":"讨论 | Flutter Kotlin 如果二选一学习,你会怎么选?","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"

可以从常见出问题场景、检测方案等方面回答。

","envelopePic":"","id":204921,"link":"https://www.wanandroid.com/wenda/show/8206","niceDate":"2021-07-03 21:54","origin":"","originId":8206,"publishTime":1625320446000,"title":"每日一问 | Android 中关于内存泄露有哪些注意点?","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"

这个问题其实不算一个太好的问题,但是也能考察事件的分发流程,搞清楚 Window,Activity,DecorView 在事件分发环节的调用流程。

","envelopePic":"","id":204920,"link":"https://www.wanandroid.com/wenda/show/11363","niceDate":"2021-07-03 21:53","origin":"","originId":11363,"publishTime":1625320401000,"title":"每日一问 为什么 Dialog 默认弹出后 Activity 就无法响应用户事件了?","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"

以后偶尔会出现这种作业,感兴趣的可以做一下。

\r\n
\r\n

该效果来自app: 莫比健身

\r\n
\r\n

\"F45BE934CF17DDDA5FF8A25A0E6F9E22.gif\"

\r\n

注意看小船。

\r\n

大家感兴趣可以实现一下,留言区可以贴:

\r\n
    \r\n
  1. 实现思路讨论;
  2. \r\n
  3. 或者自己实现的博客 ;
  4. \r\n
  5. 或者自己实现的开源项目;
  6. \r\n
","envelopePic":"","id":204919,"link":"https://wanandroid.com/wenda/show/12773","niceDate":"2021-07-03 21:51","origin":"","originId":12773,"publishTime":1625320266000,"title":"一期讨论 | 有趣的效果 小船儿游而游","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"

之前我在公众号写了一篇:这些年“崛起”的Android技术博主们,也有很多朋友自荐、推荐。

\r\n

于是我就开了个帖子收集吧,大家直接留言即可。

\r\n

所有收录博客会展现在:

\r\n

wanandroid 导航 优秀的博客一栏。

\r\n

希望与大家共建内容生态。

","envelopePic":"","id":204917,"link":"https://wanandroid.com/wenda/show/13347","niceDate":"2021-07-03 21:49","origin":"","originId":13347,"publishTime":1625320190000,"title":"博客收集 | 欢迎推荐优秀博主","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"

谈到 RecyclerView,相信不少同学,张口都能说出它的几级缓存机制:

\r\n

例如:

\r\n
    \r\n
  • 一级缓存:mAttachedScrap 和 mChangedScrap
  • \r\n
  • 二级缓存:mCachedViews
  • \r\n
  • 三级缓存:ViewCacheExtension
  • \r\n
  • 四级缓存:RecycledViewPool
  • \r\n
\r\n

然后说怎么用,就是先从 1 级找,然后 2 级...然后4 级,找不到 create ViewHolder。

\r\n

那么,有没有思考过,其实上面几级缓存都属于“内存缓存",那么这么分级肯定有一定区别。

\r\n

问题来了:

\r\n
    \r\n
  1. 每一级缓存具体作用是什么?
  2. \r\n
  3. 分别在什么场景下会用到哪些缓存呢?
  4. \r\n
","envelopePic":"","id":204916,"link":"https://www.wanandroid.com/wenda/show/14222","niceDate":"2021-07-03 21:47","origin":"","originId":14222,"publishTime":1625320079000,"title":"每日一问 | RecyclerView的多级缓存机制,每级缓存到底起到什么样的作用?","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"

View 的三大流程:测量、布局、绘制,我想大家应该都烂熟于心。

\r\n

而在绘制阶段,ViewGroup 不光要绘制自身,还需循环绘制其一众子 View,这个绘制策略默认为顺序绘制,即 [0 ~ childCount)。

\r\n

这个默认的策略,有办法调整吗?

\r\n

例如修改成 (childCount ~ 0],或是修成某个 View 最后绘制。同时又有什么场景需要我们做这样的修改?

\r\n

问题来了:

\r\n
    \r\n
  1. 这个默认的策略,有办法调整吗?
  2. \r\n
  3. 修改了之后,事件分发需要特殊处理吗?还是需要特殊处理。
  4. \r\n
","envelopePic":"","id":204915,"link":"https://www.wanandroid.com/wenda/show/14409","niceDate":"2021-07-03 21:46","origin":"","originId":14409,"publishTime":1625320009000,"title":"每日一问| View 绘制的一个细节,如何修改 View 绘制的顺序?","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"

之前写代码,需要在一些特殊时机做一些事情,例如释放内存等,特殊时机包含:

\r\n
    \r\n
  1. 应用退出(用户back 退出,没有任何 Activity 了,但进程还存活的情况)
  2. \r\n
  3. 应用 Home 按键置于后台
  4. \r\n
\r\n

问题来了,怎么方便的判断这两种时机呢?

\r\n

注意:需要考虑屏幕旋转异常情况。

","envelopePic":"","id":204914,"link":"https://wanandroid.com/wenda/show/14774","niceDate":"2021-07-03 21:46","origin":"","originId":14774,"publishTime":1625319989000,"title":"每日一问 | 如何判断应用退出,或者到后台了?","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"

最近实在是太忙了,抽空更新一问。

\r\n

想到一个非常有意思的问题:

\r\n

如果 app 启动了一个 Activity,那么在这个 Activity 展示的情况下,问题来了:

\r\n

1.上述场景背后至少有多少个线程?
2.每个线程具体的作用是什么?

","envelopePic":"","id":204913,"link":"https://www.wanandroid.com/wenda/show/15188","niceDate":"2021-07-03 21:41","origin":"","originId":15188,"publishTime":1625319685000,"title":"每日一问 | 启动了Activity 的 app 至少有几个线程?","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"

记得mipmap刚出来的时候,出现过很多言论,XXX类型图片放mipmap更好。

\r\n

如今的观念基本停留在,仅将app icon放置到mipmap,其他的图片都放到drawable。

\r\n

那么我们想想:

\r\n
    \r\n
  1. google 为啥要搞个mipmap,或者mipmap有什么特殊的能力?
  2. \r\n
  3. 从源码上能做出相关分析吗?
  4. \r\n
","envelopePic":"","id":204912,"link":"https://wanandroid.com/wenda/show/17666","niceDate":"2021-07-03 21:38","origin":"","originId":17666,"publishTime":1625319531000,"title":"每日一问 | mipmap vs drawable,傻傻分不清楚?","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"

很多时候我们在自定义 View 的需要做动画的时候,我们可以依赖属性动画的回调周期性修改 自定义的属性值,然后调用 invalidate 方法实现。

\r\n

不过我还见过一个比较野的路子,它在 onDraw 里面直接修改属性,然后调用 invalidate() 方法。

\r\n

运行起来好像也没问题。

\r\n

那么问题来了:

\r\n
    \r\n
  1. 在 onDraw 里面调用 修改绘制相关属性(例如画圆,修改半径) invalidate() ,这种与属性动画的回调调用 invalidate()源码分析有什么区别?
  2. \r\n
  3. 在 onDraw 里面调用 invalidate() 会存在什么问题?
  4. \r\n
","envelopePic":"","id":204911,"link":"https://www.wanandroid.com/wenda/show/17629","niceDate":"2021-07-03 21:35","origin":"","originId":17629,"publishTime":1625319310000,"title":"每日一问 | onDraw 里面调用 invalidate 做动画,有什么问题?","userId":31008,"visible":0,"zan":0},{"author":"xiaoyang","chapterId":440,"chapterName":"官方","courseId":13,"desc":"

大家应该都清楚app上内存是非常宝贵的资源,而Bitmap几乎是app里面占据内存最大的一个部分。

\r\n

不少同学也清楚,Bitmap占据的内存计算为:

\r\n
宽 * 高 * 单个像素所需字节数\r\n

今天有个很常规,但是你可能没有太关注的问题:

\r\n
    \r\n
  1. Bitmap所占用的内存,是app的哪部分的内存?或者说app运行时可使用Java内存为512M,Bitmap占据的内存可以超过512M吗?
  2. \r\n
  3. 问题1中所描述的,需要区分Android版本吗(5.0以下不考虑)?
  4. \r\n
  5. 问题1,问题2如果都搞清楚,经常在一些blog看到这样的代码:设置fresco图片缓存空间为Java内存的白分比,例如1/4,合适吗?
  6. \r\n
","envelopePic":"","id":204910,"link":"https://wanandroid.com/wenda/show/17874","niceDate":"2021-07-03 21:34","origin":"","originId":17874,"publishTime":1625319256000,"title":"每日一问 | 听说你做过内存优化 之 Bitmap内存占用到底在哪?","userId":31008,"visible":0,"zan":0}] 5 | /// offset : 0 6 | /// over : false 7 | /// pageCount : 2 8 | /// size : 20 9 | /// total : 25 10 | 11 | class CollectModel { 12 | int? curPage; 13 | List
? datas; 14 | int? offset; 15 | bool? over; 16 | int? pageCount; 17 | int? size; 18 | int? total; 19 | 20 | CollectModel( 21 | {this.curPage, 22 | this.datas, 23 | this.offset, 24 | this.over, 25 | this.pageCount, 26 | this.size, 27 | this.total}); 28 | 29 | CollectModel.fromJson(Map json) { 30 | curPage = json['curPage']; 31 | if (json['datas'] != null) { 32 | datas = new List
.empty(growable: true); 33 | json['datas'].forEach((v) { 34 | datas?.add(new Article.fromJson(v)); 35 | }); 36 | } 37 | offset = json['offset']; 38 | over = json['over']; 39 | pageCount = json['pageCount']; 40 | size = json['size']; 41 | total = json['total']; 42 | } 43 | 44 | Map toJson() { 45 | final Map data = new Map(); 46 | data['curPage'] = this.curPage; 47 | if (this.datas != null) { 48 | data['datas'] = this.datas?.map((v) => v.toJson()).toList(); 49 | } 50 | data['offset'] = this.offset; 51 | data['over'] = this.over; 52 | data['pageCount'] = this.pageCount; 53 | data['size'] = this.size; 54 | data['total'] = this.total; 55 | return data; 56 | } 57 | } 58 | 59 | class Article { 60 | String? author; 61 | int? chapterId; 62 | String? chapterName; 63 | int? courseId; 64 | String? desc; 65 | String? envelopePic; 66 | int? id; 67 | String? link; 68 | String? niceDate; 69 | String? origin; 70 | int? originId; 71 | int? publishTime; 72 | String? title; 73 | int? userId; 74 | int? visible; 75 | int? zan; 76 | 77 | Article( 78 | {this.author, 79 | this.chapterId, 80 | this.chapterName, 81 | this.courseId, 82 | this.desc, 83 | this.envelopePic, 84 | this.id, 85 | this.link, 86 | this.niceDate, 87 | this.origin, 88 | this.originId, 89 | this.publishTime, 90 | this.title, 91 | this.userId, 92 | this.visible, 93 | this.zan}); 94 | 95 | Article.fromJson(Map json) { 96 | author = json['author']; 97 | chapterId = json['chapterId']; 98 | chapterName = json['chapterName']; 99 | courseId = json['courseId']; 100 | desc = json['desc']; 101 | envelopePic = json['envelopePic']; 102 | id = json['id']; 103 | link = json['link']; 104 | niceDate = json['niceDate']; 105 | origin = json['origin']; 106 | originId = json['originId']; 107 | publishTime = json['publishTime']; 108 | title = json['title']; 109 | userId = json['userId']; 110 | visible = json['visible']; 111 | zan = json['zan']; 112 | } 113 | 114 | Map toJson() { 115 | final Map data = new Map(); 116 | data['author'] = this.author; 117 | data['chapterId'] = this.chapterId; 118 | data['chapterName'] = this.chapterName; 119 | data['courseId'] = this.courseId; 120 | data['desc'] = this.desc; 121 | data['envelopePic'] = this.envelopePic; 122 | data['id'] = this.id; 123 | data['link'] = this.link; 124 | data['niceDate'] = this.niceDate; 125 | data['origin'] = this.origin; 126 | data['originId'] = this.originId; 127 | data['publishTime'] = this.publishTime; 128 | data['title'] = this.title; 129 | data['userId'] = this.userId; 130 | data['visible'] = this.visible; 131 | data['zan'] = this.zan; 132 | return data; 133 | }} -------------------------------------------------------------------------------- /lib/model/mine/mine_item_model.dart: -------------------------------------------------------------------------------- 1 | class MineItemModel{ 2 | String? iconUrl; 3 | String? title; 4 | 5 | MineItemModel(this.iconUrl, this.title); 6 | } -------------------------------------------------------------------------------- /lib/model/mine/user.dart: -------------------------------------------------------------------------------- 1 | 2 | class User { 3 | 4 | bool? admin; 5 | List? chapterTops; 6 | List? collectIds; 7 | String? email; 8 | String? icon; 9 | int? id; 10 | String? nickname; 11 | String? password; 12 | String? token; 13 | int? type; 14 | String? username; 15 | 16 | 17 | User( 18 | {this.admin, 19 | this.chapterTops, 20 | this.collectIds, 21 | this.email, 22 | this.icon, 23 | this.id, 24 | this.nickname, 25 | this.password, 26 | this.token, 27 | this.type, 28 | this.username}); 29 | 30 | User.fromJsonMap( dynamic map): 31 | admin = map["admin"], 32 | chapterTops = map["chapterTops"], 33 | collectIds = map["collectIds"], 34 | email = map["email"], 35 | icon = map["icon"], 36 | id = map["id"], 37 | nickname = map["nickname"], 38 | password = map["password"], 39 | token = map["token"], 40 | type = map["type"], 41 | username = map["username"]; 42 | 43 | Map toJson() { 44 | final Map data = new Map(); 45 | data['admin'] = admin; 46 | data['chapterTops'] = chapterTops; 47 | data['collectIds'] = collectIds; 48 | data['email'] = email; 49 | data['icon'] = icon; 50 | data['id'] = id; 51 | data['nickname'] = nickname; 52 | data['password'] = password; 53 | data['token'] = token; 54 | data['type'] = type; 55 | data['username'] = username; 56 | return data; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/model/owner.dart: -------------------------------------------------------------------------------- 1 | class Owner { 2 | String? name; 3 | String? face; 4 | int? fans; 5 | 6 | Owner({this.name, this.face, this.fans}); 7 | 8 | Owner.fromJson(Map json) { 9 | name = json['name']; 10 | face = json['face']; 11 | fans = json['fans']; 12 | } 13 | 14 | Map toJson() { 15 | final Map data = Map(); 16 | data['name'] = this.name; 17 | data['face'] = this.face; 18 | data['fans'] = this.fans; 19 | return data; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/model/project/project_model.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /// apkLink : "" 5 | /// audit : 1 6 | /// author : "manqianzhuang" 7 | /// canEdit : false 8 | /// chapterId : 294 9 | /// chapterName : "完整项目" 10 | /// collect : false 11 | /// courseId : 13 12 | /// desc : "项目使用Android官方的Jetpack Compose完成,遵循MVVM架构思路,以下为本项目用到的框架:\r\njetpack compose, viewModel, retrofit, okhttp, coroutine/flow, paging3,\r\nroom, accompanist, hilt, gson, glide/picasso, navigation." 13 | /// descMd : "" 14 | /// envelopePic : "https://www.wanandroid.com/resources/image/pc/default_project_img.jpg" 15 | /// fresh : false 16 | /// host : "" 17 | /// id : 20275 18 | /// link : "https://www.wanandroid.com/blog/show/3090" 19 | /// niceDate : "2021-10-24 22:51" 20 | /// niceShareDate : "2021-10-24 22:51" 21 | /// origin : "" 22 | /// prefix : "" 23 | /// projectLink : "https://github.com/manqianzhuang/HamApp.git" 24 | /// publishTime : 1635087073000 25 | /// realSuperChapterId : 293 26 | /// selfVisible : 0 27 | /// shareDate : 1635087073000 28 | /// shareUser : "" 29 | /// superChapterId : 294 30 | /// superChapterName : "开源项目主Tab" 31 | /// tags : [{"name":"项目","url":"/project/list/1?cid=294"}] 32 | /// title : "用Jetpack Compose做一个完成度较高的WanAndroid app" 33 | /// type : 0 34 | /// userId : -1 35 | /// visible : 1 36 | /// zan : 0 37 | 38 | class ProjectModel { 39 | int? curPage; 40 | List? datas; 41 | int? offset; 42 | bool? over; 43 | int? pageCount; 44 | int? size; 45 | int? total; 46 | 47 | ProjectModel( 48 | {this.curPage, 49 | this.datas, 50 | this.offset, 51 | this.over, 52 | this.pageCount, 53 | this.size, 54 | this.total}); 55 | 56 | ProjectModel.fromJson(Map json) { 57 | curPage = json['curPage']; 58 | print("fromJson111 = ${json['datas']}"); 59 | if (json['datas'] != null) { 60 | datas = new List.empty(growable: true); 61 | json['datas'].forEach((v) { 62 | datas?.add(new ProjectInfo.fromJson(v)); 63 | }); 64 | } 65 | print("fromJson = ${datas}"); 66 | offset = json['offset']; 67 | over = json['over']; 68 | pageCount = json['pageCount']; 69 | size = json['size']; 70 | total = json['total']; 71 | } 72 | 73 | Map toJson() { 74 | final Map data = new Map(); 75 | data['curPage'] = this.curPage; 76 | if (this.datas != null) { 77 | data['datas'] = this.datas?.map((v) => v.toJson()).toList(); 78 | } 79 | data['offset'] = this.offset; 80 | data['over'] = this.over; 81 | data['pageCount'] = this.pageCount; 82 | data['size'] = this.size; 83 | data['total'] = this.total; 84 | return data; 85 | } 86 | } 87 | 88 | class ProjectInfo { 89 | String? apkLink; 90 | int? audit; 91 | String? author; 92 | bool? canEdit; 93 | int? chapterId; 94 | String? chapterName; 95 | bool? collect; 96 | int? courseId; 97 | String? desc; 98 | String? descMd; 99 | String? envelopePic; 100 | bool? fresh; 101 | String? host; 102 | int? id; 103 | String? link; 104 | String? niceDate; 105 | String? niceShareDate; 106 | String? origin; 107 | String? prefix; 108 | String? projectLink; 109 | int? publishTime; 110 | int? realSuperChapterId; 111 | int? selfVisible; 112 | int? shareDate; 113 | String? shareUser; 114 | int? superChapterId; 115 | String? superChapterName; 116 | List? tags; 117 | String? title; 118 | int? type; 119 | int? userId; 120 | int? visible; 121 | int? zan; 122 | 123 | ProjectInfo( 124 | {this.apkLink, 125 | this.audit, 126 | this.author, 127 | this.canEdit, 128 | this.chapterId, 129 | this.chapterName, 130 | this.collect, 131 | this.courseId, 132 | this.desc, 133 | this.descMd, 134 | this.envelopePic, 135 | this.fresh, 136 | this.host, 137 | this.id, 138 | this.link, 139 | this.niceDate, 140 | this.niceShareDate, 141 | this.origin, 142 | this.prefix, 143 | this.projectLink, 144 | this.publishTime, 145 | this.realSuperChapterId, 146 | this.selfVisible, 147 | this.shareDate, 148 | this.shareUser, 149 | this.superChapterId, 150 | this.superChapterName, 151 | this.tags, 152 | this.title, 153 | this.type, 154 | this.userId, 155 | this.visible, 156 | this.zan}); 157 | 158 | ProjectInfo.fromJson(Map json) { 159 | apkLink = json['apkLink']; 160 | audit = json['audit']; 161 | author = json['author']; 162 | canEdit = json['canEdit']; 163 | chapterId = json['chapterId']; 164 | chapterName = json['chapterName']; 165 | collect = json['collect']; 166 | courseId = json['courseId']; 167 | desc = json['desc']; 168 | descMd = json['descMd']; 169 | envelopePic = json['envelopePic']; 170 | fresh = json['fresh']; 171 | host = json['host']; 172 | id = json['id']; 173 | link = json['link']; 174 | niceDate = json['niceDate']; 175 | niceShareDate = json['niceShareDate']; 176 | origin = json['origin']; 177 | prefix = json['prefix']; 178 | projectLink = json['projectLink']; 179 | publishTime = json['publishTime']; 180 | realSuperChapterId = json['realSuperChapterId']; 181 | selfVisible = json['selfVisible']; 182 | shareDate = json['shareDate']; 183 | shareUser = json['shareUser']; 184 | superChapterId = json['superChapterId']; 185 | superChapterName = json['superChapterName']; 186 | if (json['tags'] != null) { 187 | tags = new List.empty(growable: true); 188 | json['tags'].forEach((v) { 189 | tags?.add(new Tags.fromJson(v)); 190 | }); 191 | } 192 | title = json['title']; 193 | type = json['type']; 194 | userId = json['userId']; 195 | visible = json['visible']; 196 | zan = json['zan']; 197 | } 198 | 199 | Map toJson() { 200 | final Map data = new Map(); 201 | data['apkLink'] = this.apkLink; 202 | data['audit'] = this.audit; 203 | data['author'] = this.author; 204 | data['canEdit'] = this.canEdit; 205 | data['chapterId'] = this.chapterId; 206 | data['chapterName'] = this.chapterName; 207 | data['collect'] = this.collect; 208 | data['courseId'] = this.courseId; 209 | data['desc'] = this.desc; 210 | data['descMd'] = this.descMd; 211 | data['envelopePic'] = this.envelopePic; 212 | data['fresh'] = this.fresh; 213 | data['host'] = this.host; 214 | data['id'] = this.id; 215 | data['link'] = this.link; 216 | data['niceDate'] = this.niceDate; 217 | data['niceShareDate'] = this.niceShareDate; 218 | data['origin'] = this.origin; 219 | data['prefix'] = this.prefix; 220 | data['projectLink'] = this.projectLink; 221 | data['publishTime'] = this.publishTime; 222 | data['realSuperChapterId'] = this.realSuperChapterId; 223 | data['selfVisible'] = this.selfVisible; 224 | data['shareDate'] = this.shareDate; 225 | data['shareUser'] = this.shareUser; 226 | data['superChapterId'] = this.superChapterId; 227 | data['superChapterName'] = this.superChapterName; 228 | if (this.tags != null) { 229 | data['tags'] = this.tags?.map((v) => v.toJson()).toList(); 230 | } 231 | data['title'] = this.title; 232 | data['type'] = this.type; 233 | data['userId'] = this.userId; 234 | data['visible'] = this.visible; 235 | data['zan'] = this.zan; 236 | return data; 237 | } 238 | } 239 | 240 | class Tags { 241 | String? name; 242 | String? url; 243 | 244 | Tags({this.name, this.url}); 245 | 246 | Tags.fromJson(Map json) { 247 | name = json['name']; 248 | url = json['url']; 249 | } 250 | 251 | Map toJson() { 252 | final Map data = new Map(); 253 | data['name'] = this.name; 254 | data['url'] = this.url; 255 | return data; 256 | } 257 | } -------------------------------------------------------------------------------- /lib/model/project/project_tab_model.dart: -------------------------------------------------------------------------------- 1 | class ProjectTabModel { 2 | String? name; 3 | int? id; 4 | List? children; 5 | String? tagIndex; 6 | 7 | ProjectTabModel.fromJson(Map json) 8 | : name = json['name'], 9 | id = json['id'], 10 | children = (json['children'] as List) 11 | .map((e) => e == null 12 | ? null 13 | : new ProjectTabModel.fromJson(e as Map)) 14 | .toList(); 15 | 16 | Map toJson() => { 17 | 'name': name, 18 | 'id': id, 19 | 'children': children, 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /lib/model/video_model.dart: -------------------------------------------------------------------------------- 1 | class VideoModel{ 2 | int videoId; 3 | 4 | VideoModel(this.videoId); 5 | } -------------------------------------------------------------------------------- /lib/navigator/bottom_navigator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_project/navigator/f_navigatior.dart'; 3 | import 'package:flutter_project/page/category_page.dart'; 4 | import 'package:flutter_project/page/home_page.dart'; 5 | import 'package:flutter_project/page/mine_page.dart'; 6 | import 'package:flutter_project/page/project_page.dart'; 7 | import 'package:flutter_project/utils/color.dart'; 8 | 9 | ///底部tab 10 | class BottomNavigator extends StatefulWidget { 11 | const BottomNavigator({Key? key}) : super(key: key); 12 | 13 | @override 14 | _BottomNavigatorState createState() => _BottomNavigatorState(); 15 | } 16 | 17 | class _BottomNavigatorState extends State { 18 | int _curIndex = 0; 19 | 20 | final PageController _pageController = PageController(initialPage: 0); 21 | List _pages = []; 22 | 23 | static int initialPage = 0; 24 | bool _hasBuild = false; 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | _pages = [ 29 | HomePage( 30 | onIntentTo: (index) { 31 | _onJumpTo(index, pageChange: false); 32 | }, 33 | ), 34 | ProjectPage(), 35 | CategoryPage(), 36 | MinePage(), 37 | ]; 38 | 39 | if (!_hasBuild) { 40 | FRouter.getInstance() 41 | ?.onBottomTabChange(initialPage, _pages[initialPage]); 42 | _hasBuild = true; 43 | } 44 | return Scaffold( 45 | body: PageView( 46 | controller: _pageController, 47 | children: _pages, 48 | onPageChanged: (index) { 49 | _onJumpTo(index, pageChange: true); 50 | }, 51 | physics: NeverScrollableScrollPhysics(), //viewpage禁止左右滑动 52 | ), 53 | bottomNavigationBar: BottomNavigationBar( 54 | currentIndex: _curIndex, 55 | onTap: (index) => _onJumpTo(index), 56 | type: BottomNavigationBarType.fixed, 57 | //显示bottom bar底部文字 58 | selectedItemColor: primary[50], 59 | items: [ 60 | _bottomItem('首页', Icons.home, 0), 61 | _bottomItem('项目', Icons.local_fire_department, 1), 62 | _bottomItem('分类', Icons.category_outlined, 2), 63 | _bottomItem('我的', Icons.person, 3), 64 | ], 65 | ), 66 | ); 67 | } 68 | 69 | _bottomItem(String s, IconData icon, int i) { 70 | return BottomNavigationBarItem( 71 | icon: Icon(icon, color: Colors.grey), 72 | activeIcon: Icon(icon, color: primary[50]), 73 | label: s); 74 | } 75 | 76 | void _onJumpTo(int value, {pageChange = false}) { 77 | if (!pageChange) { 78 | _pageController.jumpToPage(value); 79 | } else { 80 | FRouter.getInstance()?.onBottomTabChange(value, _pages[value]); 81 | } 82 | setState(() { 83 | _curIndex = value; 84 | }); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /lib/navigator/f_navigatior.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_project/navigator/bottom_navigator.dart'; 4 | import 'package:flutter_project/page/about_page.dart'; 5 | import 'package:flutter_project/page/article_page.dart'; 6 | import 'package:flutter_project/page/coin_rank_page.dart'; 7 | import 'package:flutter_project/page/home_page.dart'; 8 | import 'package:flutter_project/page/login_page.dart'; 9 | import 'package:flutter_project/page/my_collect_page.dart'; 10 | import 'package:flutter_project/page/register_page.dart'; 11 | import 'package:flutter_project/page/setting_page.dart'; 12 | import 'package:flutter_project/page/video_detail_page.dart'; 13 | import 'package:flutter_project/page/webview_page.dart'; 14 | 15 | ///路由状态变化listener 16 | ///通过堆栈信息发送变化来判断 17 | typedef RouteChangeListener(RouteStatusInfo curInfo, RouteStatusInfo preInfo); 18 | 19 | ///创建页面 20 | pageWrap(Widget child) { 21 | return MaterialPage(key: ValueKey(child.hashCode), child: child); 22 | } 23 | 24 | ///page在堆栈中的位置 25 | int getPageIndex(List pages, RouteStatus status) { 26 | for (int i = 0; i < pages.length; i++) { 27 | MaterialPage page = pages[i]; 28 | if (getStatus(page) == status) { 29 | return i; 30 | } 31 | } 32 | 33 | return -1; 34 | } 35 | 36 | ///枚举,代表页面 37 | enum RouteStatus { 38 | login, 39 | register, 40 | home, 41 | detail, 42 | webview, 43 | article, 44 | collect, 45 | about, 46 | setting, 47 | coinRank, 48 | unknown 49 | } 50 | 51 | RouteStatus getStatus(MaterialPage page) { 52 | if (page.child is LoginPage) { 53 | return RouteStatus.login; 54 | } else if (page.child is RegisterPage) { 55 | return RouteStatus.register; 56 | } else if (page.child is BottomNavigator) { 57 | return RouteStatus.home; 58 | } else if (page.child is VideoDetailPage) { 59 | return RouteStatus.detail; 60 | } else if (page.child is WebViewPage) { 61 | return RouteStatus.webview; 62 | } else if (page.child is ArticlePage) { 63 | return RouteStatus.article; 64 | } else if (page.child is MyCollectPage) { 65 | return RouteStatus.collect; 66 | } else if (page.child is AboutPage) { 67 | return RouteStatus.about; 68 | } else if (page.child is SettingPage) { 69 | return RouteStatus.setting; 70 | } else if(page.child is CoinRankPage){ 71 | return RouteStatus.coinRank; 72 | } 73 | 74 | else { 75 | return RouteStatus.unknown; 76 | } 77 | } 78 | 79 | class RouteStatusInfo { 80 | final RouteStatus routeStatus; 81 | final Widget page; 82 | 83 | RouteStatusInfo(this.routeStatus, this.page); 84 | } 85 | 86 | ///页面跳转 87 | class FRouter extends _RouteIntentListener { 88 | static FRouter? _instance; 89 | RouteIntentListener? routeJumpListener; 90 | List _listener = []; 91 | 92 | RouteStatusInfo? _cur; 93 | RouteStatusInfo? _bottomTab; 94 | 95 | static FRouter? getInstance() { 96 | if (_instance == null) { 97 | _instance = FRouter(); 98 | } 99 | return _instance; 100 | } 101 | 102 | void addRouteListener(RouteChangeListener listener) { 103 | if (!_listener.contains(listener)) { 104 | _listener.add(listener); 105 | } 106 | } 107 | 108 | void removeRouteListener(RouteChangeListener listener) { 109 | if (_listener.contains(listener)) { 110 | _listener.remove(listener); 111 | } 112 | } 113 | 114 | void notify(List curPages, List prePages) { 115 | if (curPages == prePages) return; 116 | 117 | //取最顶部 118 | var cur = RouteStatusInfo(getStatus(curPages.last), curPages.last.child); 119 | _notify(cur); 120 | } 121 | 122 | //注册跳转listener 123 | void registerRouteJumpListener(RouteIntentListener listener) { 124 | this.routeJumpListener = listener; 125 | } 126 | 127 | ///底部tab切换监听 128 | void onBottomTabChange(int index, Widget page) { 129 | _bottomTab = RouteStatusInfo(RouteStatus.home, page); 130 | _notify(_bottomTab!); 131 | } 132 | 133 | @override 134 | void onIntentTo(RouteStatus status, {Map? args}) { 135 | routeJumpListener!.onJumpTo!(status, args: args); 136 | } 137 | 138 | void _notify(RouteStatusInfo cur) { 139 | if (cur.page is BottomNavigator && _bottomTab != null) { 140 | cur = _bottomTab!; 141 | } 142 | _listener.forEach((element) { 143 | element(cur, _cur!); 144 | }); 145 | _cur = cur; 146 | } 147 | } 148 | 149 | abstract class _RouteIntentListener { 150 | void onIntentTo(RouteStatus status, {Map args}); 151 | } 152 | 153 | typedef OnJumpTo = void Function(RouteStatus routeStatus, {Map? args}); 154 | 155 | class RouteIntentListener { 156 | final OnJumpTo? onJumpTo; 157 | 158 | RouteIntentListener({this.onJumpTo}); 159 | } 160 | -------------------------------------------------------------------------------- /lib/page/about_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_project/widget/app_toolbar.dart'; 3 | 4 | class AboutPage extends StatefulWidget { 5 | const AboutPage({Key? key}) : super(key: key); 6 | 7 | @override 8 | _AboutPageState createState() => _AboutPageState(); 9 | } 10 | 11 | class _AboutPageState extends State { 12 | @override 13 | Widget build(BuildContext context) { 14 | return Scaffold( 15 | appBar: articleAppBar("关于"), 16 | body: Container( 17 | alignment: Alignment.topCenter, 18 | child: Padding(padding: EdgeInsets.only(top: 130), 19 | child: Image.asset("images/launch_image.png",width: 400,height: 300,),) 20 | 21 | ), 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/page/article_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_project/base/base_refresh_load_state.dart'; 3 | import 'package:flutter_project/http/core/f_error.dart'; 4 | import 'package:flutter_project/http/dao/home_dao.dart'; 5 | import 'package:flutter_project/model/home/home_article_model.dart'; 6 | import 'package:flutter_project/widget/app_toolbar.dart'; 7 | import 'package:flutter_project/widget/article_item.dart'; 8 | 9 | class ArticlePage extends StatefulWidget { 10 | final int cid; 11 | final String? title; 12 | 13 | const ArticlePage({Key? key, required this.cid, this.title}) 14 | : super(key: key); 15 | 16 | @override 17 | _ArticlePageState createState() => _ArticlePageState(); 18 | } 19 | 20 | class _ArticlePageState extends BaseRefreshLoadStateState { 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | } 27 | 28 | @override 29 | get child => Scaffold( 30 | appBar: articleAppBar(widget.title!), 31 | body: ListView.builder( 32 | controller: scrollController, 33 | itemBuilder: (BuildContext context, int index) { 34 | return ArticleItem( 35 | articleInfo: dataList[index], 36 | ); 37 | }, 38 | itemCount: dataList.length, 39 | ), 40 | ); 41 | 42 | @override 43 | Future getData(int curPage) async { 44 | HomeArticleModel articleModel = 45 | await HomeDao.getArticleByCid(curPage, widget.cid); 46 | return articleModel; 47 | } 48 | 49 | @override 50 | List parseList(HomeArticleModel result) { 51 | return result.datas!; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/page/category_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_project/http/dao/project_dao.dart'; 6 | import 'package:flutter_project/model/category/category_model.dart'; 7 | import 'package:flutter_project/navigator/f_navigatior.dart'; 8 | 9 | class CategoryPage extends StatefulWidget { 10 | const CategoryPage({Key? key}) : super(key: key); 11 | 12 | @override 13 | _CategoryPageState createState() => _CategoryPageState(); 14 | } 15 | 16 | class _CategoryPageState extends State 17 | with AutomaticKeepAliveClientMixin { 18 | List categoryList = []; 19 | List childList = []; 20 | 21 | @override 22 | void initState() { 23 | super.initState(); 24 | _loadCategoryList(); 25 | } 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return Scaffold( 30 | appBar: AppBar( 31 | centerTitle: true, 32 | title: Text( 33 | "分类", 34 | style: TextStyle(fontSize: 18), 35 | ), 36 | ), 37 | body: ListView.builder( 38 | itemBuilder: (BuildContext context, int index) { 39 | return Container( 40 | child: Padding( 41 | padding: EdgeInsets.only(left: 15, right: 15, top: 15), 42 | child: Column( 43 | mainAxisAlignment: MainAxisAlignment.start, 44 | crossAxisAlignment: CrossAxisAlignment.start, 45 | children: [ 46 | Text( 47 | categoryList[index].name!, 48 | style: TextStyle(fontSize: 18, fontWeight: FontWeight.w700), 49 | ), 50 | Divider( 51 | height: 1, 52 | color: Colors.black26, 53 | ), 54 | Padding( 55 | padding: EdgeInsets.only(top: 10), 56 | child: Container( 57 | child: Wrap( 58 | spacing: 12.0, 59 | runSpacing: 8.0, 60 | children: _buildItem(categoryList[index].children), 61 | ))), 62 | ], 63 | ), 64 | )); 65 | }, 66 | itemCount: categoryList.length, 67 | ), 68 | ); 69 | } 70 | 71 | List _buildItem(List? list) { 72 | return list!.map((item) { 73 | return Padding( 74 | padding: EdgeInsets.only(top: 10, left: 10, right: 10), 75 | child: ActionChip( 76 | onPressed: () { 77 | //跳转到相应的列表 78 | FRouter.getInstance()!.onIntentTo(RouteStatus.article, 79 | args: {"article_cid": item.id, "article_title": item.name}); 80 | }, 81 | label: Text(item.name!), 82 | elevation: 5.0, 83 | backgroundColor: 84 | Colors.primaries[Random().nextInt(Colors.primaries.length)], 85 | labelStyle: TextStyle( 86 | color: Colors.white, 87 | ), 88 | ), 89 | ); 90 | }).toList(); 91 | } 92 | 93 | void _loadCategoryList() async { 94 | List categoryList = await ProjectDao.getCategory(); 95 | setState(() { 96 | this.categoryList = categoryList; 97 | }); 98 | } 99 | 100 | @override 101 | bool get wantKeepAlive => true; 102 | } 103 | -------------------------------------------------------------------------------- /lib/page/coin_rank_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_project/http/core/f_error.dart'; 4 | import 'package:flutter_project/http/dao/person_dao.dart'; 5 | import 'package:flutter_project/model/mine/coin_model.dart'; 6 | import 'package:flutter_project/utils/constants.dart'; 7 | import 'package:flutter_project/utils/view_util.dart'; 8 | 9 | class CoinRankPage extends StatefulWidget { 10 | const CoinRankPage({Key? key}) : super(key: key); 11 | 12 | @override 13 | _CoinRankPageState createState() => _CoinRankPageState(); 14 | } 15 | 16 | class _CoinRankPageState extends State { 17 | List? coinRankList = []; 18 | 19 | @override 20 | void initState() { 21 | super.initState(); 22 | _loadCoin(); 23 | } 24 | 25 | Widget _buildItem(BuildContext context, int index) { 26 | return Container( 27 | height: 60, 28 | decoration: BoxDecoration( 29 | color: Colors.white, 30 | borderRadius: BorderRadius.all(Radius.circular(4.0)), 31 | border: Border.all(width: 1, color: Colors.white)), 32 | child: Padding( 33 | padding: EdgeInsets.only(left: 20, right: 20), 34 | child: Row( 35 | 36 | children: [ 37 | Text( 38 | "${index + 4}", 39 | style: TextStyle(fontSize: 20, color: Colors.red), 40 | ), 41 | viewSpace(width: 20), 42 | Text( 43 | "${coinRankList![index+3].username}", 44 | style: TextStyle(fontSize: 20, color: Colors.black), 45 | ), 46 | viewSpace(width: 20), 47 | Expanded(child: Text( 48 | "积分:${coinRankList![index+3].coinCount}", 49 | style: TextStyle(fontSize: 20, color: Colors.black26), 50 | )) 51 | 52 | ], 53 | ), 54 | )); 55 | } 56 | 57 | @override 58 | Widget build(BuildContext context) { 59 | return Scaffold( 60 | backgroundColor: Colors.red, 61 | body: CustomScrollView( 62 | slivers: [ 63 | SliverAppBar( 64 | pinned: true, 65 | centerTitle: true, 66 | title: Text("排行榜",style: TextStyle(fontSize: 22,color: Colors.white),), 67 | backgroundColor: Colors.red, 68 | ), 69 | SliverToBoxAdapter( 70 | child: Container( 71 | height: 270, 72 | child: Stack( 73 | children: [ 74 | Positioned( 75 | top: 100, 76 | left: 50, 77 | child: Column( 78 | children: [ 79 | Image( 80 | width: 60, 81 | height: 100, 82 | image: AssetImage('images/ic_second.png')), 83 | Text( 84 | coinRankList!.length > 0 85 | ? coinRankList![1].username! 86 | : '', 87 | style: TextStyle(fontSize: 16, color: Colors.white), 88 | ), 89 | Text( 90 | coinRankList!.length > 0 91 | ? "${coinRankList![1].coinCount!}" 92 | : '', 93 | style: TextStyle(fontSize: 12, color: Colors.white), 94 | ) 95 | ], 96 | )), 97 | Positioned( 98 | top: 20, 99 | left: 10, 100 | right: 10, 101 | child: Column( 102 | children: [ 103 | Image( 104 | width: 60, 105 | height: 100, 106 | image: AssetImage('images/ic_first.png')), 107 | Text( 108 | coinRankList!.length > 0 109 | ? coinRankList![0].username! 110 | : '', 111 | style: TextStyle(fontSize: 22, color: Colors.white), 112 | ), 113 | Text( 114 | coinRankList!.length > 0 115 | ? "${coinRankList![0].coinCount!}" 116 | : '', 117 | style: TextStyle(fontSize: 18, color: Colors.white), 118 | ) 119 | ], 120 | )), 121 | Positioned( 122 | top: 100, 123 | right: 50, 124 | child: Column( 125 | children: [ 126 | Image( 127 | width: 60, 128 | height: 100, 129 | image: AssetImage('images/ic_three.png')), 130 | Text( 131 | coinRankList!.length > 0 132 | ? coinRankList![2].username! 133 | : '', 134 | style: TextStyle(fontSize: 16, color: Colors.white), 135 | ), 136 | Text( 137 | coinRankList!.length > 0 138 | ? "${coinRankList![2].coinCount!}" 139 | : '', 140 | style: TextStyle(fontSize: 12, color: Colors.white), 141 | ) 142 | ], 143 | )), 144 | ], 145 | ), 146 | )), 147 | Container( 148 | child: SliverList( 149 | delegate: SliverChildBuilderDelegate(_buildItem, 150 | childCount: 151 | coinRankList!.length > 0 ? coinRankList!.length : 0), 152 | ), 153 | ) 154 | ], 155 | ), 156 | ); 157 | } 158 | 159 | ///加载积分排行榜 160 | void _loadCoin() async { 161 | try { 162 | CoinModel coinModel = await PersonDao.getCoinRank(); 163 | List? coinList = coinModel.datas; 164 | setState(() { 165 | coinRankList = coinList; 166 | }); 167 | } on FNetError catch (e) { 168 | print(e); 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /lib/page/home_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_project/base/base_refresh_load_state.dart'; 4 | import 'package:flutter_project/db/sp_cache.dart'; 5 | import 'package:flutter_project/http/core/dio_adapter.dart'; 6 | import 'package:flutter_project/http/core/f_error.dart'; 7 | import 'package:flutter_project/http/core/f_net_state.dart'; 8 | import 'package:flutter_project/http/dao/home_dao.dart'; 9 | import 'package:flutter_project/model/home/banner_model.dart'; 10 | import 'package:flutter_project/model/home/home_article_model.dart'; 11 | import 'package:flutter_project/navigator/f_navigatior.dart'; 12 | import 'package:flutter_project/page/search_delegate.dart'; 13 | import 'package:flutter_project/utils/color.dart'; 14 | import 'package:flutter_project/widget/article_item.dart'; 15 | import 'package:flutter_project/widget/f_banner.dart'; 16 | import 'package:flutter_project/widget/navigation_bar.dart'; 17 | 18 | ///首页 19 | class HomePage extends StatefulWidget { 20 | final ValueChanged? onIntentTo; 21 | 22 | const HomePage({Key? key, this.onIntentTo}) : super(key: key); 23 | 24 | @override 25 | _HomePageState createState() => _HomePageState(); 26 | } 27 | 28 | ///state继承BaseRefreshLoadStateState,BaseRefreshLoadStateState是封装了刷新和下拉加载更多的框架 29 | ///[HomeArticleModel] 相应列表model 30 | ///[ArticleInfo] 列表item model, 31 | ///[HomePage] 当前页面page 32 | ///实现父类的三个方法 33 | class _HomePageState 34 | extends BaseRefreshLoadStateState { 35 | var listener; 36 | List? bannerList = []; 37 | List? articleList = []; 38 | int pageIndex = 0; 39 | 40 | @override 41 | void initState() { 42 | super.initState(); 43 | FRouter.getInstance()?.addRouteListener( 44 | this.listener = (curInfo, preInfo) => {print('home ${curInfo.page}')}); 45 | _loadBanner(); 46 | //_loadArticle(); 47 | } 48 | 49 | @override 50 | void dispose() { 51 | super.dispose(); 52 | FRouter.getInstance()?.removeRouteListener(this.listener); 53 | } 54 | 55 | // @override 56 | // Widget build(BuildContext context) { 57 | // super.build(context); 58 | // return Scaffold( 59 | // backgroundColor: Colors.white, 60 | // body: Column( 61 | // children: [ 62 | // NavigationBar( 63 | // childWidget: _appBar(), 64 | // statusStyle: StatusStyle.DARK_STYLE, 65 | // ), 66 | // MediaQuery.removePadding( 67 | // context: context, 68 | // removeTop: true, 69 | // child: _buildTypeList(), 70 | // ) 71 | // ], 72 | // ), 73 | // ); 74 | // } 75 | 76 | @override 77 | bool get wantKeepAlive => true; 78 | 79 | //bannerUI 80 | _banner() { 81 | return Padding( 82 | padding: EdgeInsets.only(left: 20, right: 20, top: 20), 83 | child: FBanner( 84 | bannerList!, 85 | )); 86 | } 87 | 88 | //请求banner 89 | void _loadBanner() async { 90 | try { 91 | List banList = await HomeDao.getBanner(); 92 | setState(() { 93 | bannerList?.addAll(banList); 94 | }); 95 | } on FNetError catch (e) { 96 | print(e); 97 | } 98 | } 99 | 100 | void _loadArticle() async { 101 | try { 102 | HomeArticleModel articleModel = await HomeDao.getHomeArticle(pageIndex); 103 | List articleList = articleModel.datas!; 104 | setState(() { 105 | this.articleList = articleList; 106 | }); 107 | } on FNetError catch (e) { 108 | print(e); 109 | } 110 | } 111 | 112 | _appBar() { 113 | return Padding( 114 | padding: EdgeInsets.only(left: 15, right: 15), 115 | child: Row( 116 | children: [ 117 | InkWell( 118 | onTap: () { 119 | widget.onIntentTo!(3); 120 | }, 121 | child: ClipRRect( 122 | borderRadius: BorderRadius.circular(18), 123 | child: Image( 124 | height: 36, 125 | width: 36, 126 | image: AssetImage('images/default_avatar.png'), 127 | ), 128 | ), 129 | ), 130 | Expanded( 131 | child: Padding( 132 | padding: EdgeInsets.only(left: 15, right: 15), 133 | child: InkWell( 134 | child: ClipRRect( 135 | borderRadius: BorderRadius.circular(16), 136 | child: Container( 137 | padding: EdgeInsets.only(left: 10), 138 | height: 32, 139 | alignment: Alignment.centerLeft, 140 | child: Icon( 141 | Icons.search, 142 | color: Colors.grey, 143 | ), 144 | decoration: BoxDecoration(color: Colors.grey[100]), 145 | ), 146 | ), 147 | onTap: () { 148 | showSearch(context: context, delegate: SearchDelegatePage()); 149 | }, 150 | ), 151 | )), 152 | InkWell( 153 | child: Icon( 154 | Icons.message, 155 | color: Colors.grey, 156 | ), 157 | onTap: () { 158 | //点击bar消息 159 | }, 160 | ) 161 | ], 162 | ), 163 | ); 164 | } 165 | 166 | ///首页网络布局 167 | _gridView() { 168 | return Padding( 169 | padding: EdgeInsets.only(top: 20, left: 5, right: 5), 170 | child: Container( 171 | child: GridView.count( 172 | physics: NeverScrollableScrollPhysics(), 173 | shrinkWrap: true, 174 | mainAxisSpacing: 0, 175 | crossAxisSpacing: 5, 176 | crossAxisCount: 4, 177 | children: [ 178 | _buildGridItem('images/icon_iv.png', '面试', cid: 73), 179 | _buildGridItem('images/icon_big.png', '大厂分享', cid: 510), 180 | _buildGridItem('images/icon_op.png', '性能优化', cid: 78), 181 | _buildGridItem('images/icon_daily.png', '官方发布', cid: 269), 182 | _buildGridItem('images/icon_jetpack.png', 'Jetpack', cid: 422), 183 | _buildGridItem('images/icon_open.png', '开源库源码', cid: 460), 184 | _buildGridItem('images/icon_framework.png', 'Framework', cid: 152), 185 | _buildGridItem('images/icon_kotlin.png', 'Kotlin', cid: 231), 186 | ], 187 | )), 188 | ); 189 | } 190 | 191 | ///网格布局item 192 | _buildGridItem(String imgUrl, String title, {cid}) { 193 | return GestureDetector( 194 | onTap: () { 195 | FRouter.getInstance()!.onIntentTo(RouteStatus.article, 196 | args: {"article_cid": cid, "article_title": title}); 197 | }, 198 | child: Column( 199 | children: [ 200 | Image( 201 | image: AssetImage(imgUrl), 202 | width: 30, 203 | height: 30, 204 | ), 205 | Padding( 206 | padding: EdgeInsets.only(top: 4), 207 | child: Text( 208 | title, 209 | style: TextStyle(fontSize: 12), 210 | ), 211 | ) 212 | ], 213 | ), 214 | ); 215 | } 216 | 217 | ///首页文章列表 218 | _articleView(int index) { 219 | return MediaQuery.removePadding( 220 | context: context, 221 | removeTop: true, 222 | child: ArticleItem( 223 | articleInfo: dataList[index], 224 | onCollect: () { 225 | print("!!!!!!!!收藏"); 226 | if (!SpCache.getInstance()!.isLogin()) { 227 | FRouter.getInstance()!.onIntentTo(RouteStatus.login); 228 | } else { 229 | //点击收藏或者取消 230 | _collectArticle(dataList[index].id!); 231 | } 232 | }, 233 | ), 234 | ); 235 | } 236 | 237 | _collectArticle(int id) async { 238 | try { 239 | await HomeDao.collectArticle(id); 240 | } on FNetError catch (e) { 241 | print(e); 242 | } 243 | } 244 | 245 | _buildTypeList() { 246 | return Container( 247 | child: Expanded( 248 | child: Container( 249 | child: ListView.builder( 250 | itemCount: dataList.length, 251 | controller: scrollController, 252 | itemBuilder: (BuildContext context, int index) { 253 | if (index == 0) { 254 | return _banner(); 255 | } else if (index == 1) { 256 | return _gridView(); 257 | } else if (index == 2) { 258 | return Container( 259 | child: Text( 260 | "热门博文", 261 | style: TextStyle( 262 | fontSize: 18, 263 | color: Colors.black, 264 | fontWeight: FontWeight.w600), 265 | ), 266 | padding: EdgeInsets.only(left: 15, bottom: 10), 267 | ); 268 | } 269 | return _articleView(index - 3); 270 | }), 271 | )), 272 | ); 273 | } 274 | 275 | @override 276 | get child => Scaffold( 277 | backgroundColor: Colors.white, 278 | body: Column( 279 | children: [ 280 | NavigationBar( 281 | childWidget: _appBar(), 282 | statusStyle: StatusStyle.DARK_STYLE, 283 | ), 284 | MediaQuery.removePadding( 285 | context: context, 286 | removeTop: true, 287 | child: _buildTypeList(), 288 | ) 289 | ], 290 | ), 291 | ); 292 | 293 | @override 294 | Future getData(int curPage) async { 295 | HomeArticleModel articleModel = await HomeDao.getHomeArticle(curPage); 296 | return articleModel; 297 | } 298 | 299 | @override 300 | List parseList(HomeArticleModel result) { 301 | return result.datas!; 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /lib/page/login_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_project/db/sp_cache.dart'; 3 | import 'package:flutter_project/http/core/f_error.dart'; 4 | import 'package:flutter_project/http/core/f_net_state.dart'; 5 | import 'package:flutter_project/http/dao/login_dao.dart'; 6 | import 'package:flutter_project/model/mine/user.dart'; 7 | import 'package:flutter_project/navigator/f_navigatior.dart'; 8 | import 'package:flutter_project/provider/user_provider.dart'; 9 | import 'package:flutter_project/utils/string_util.dart'; 10 | import 'package:flutter_project/utils/toast_util.dart'; 11 | import 'package:flutter_project/widget/login_button.dart'; 12 | import 'package:flutter_project/widget/login_input.dart'; 13 | import 'package:provider/provider.dart'; 14 | 15 | class LoginPage extends StatefulWidget { 16 | const LoginPage({Key? key}) : super(key: key); 17 | 18 | @override 19 | _LoginPageState createState() => _LoginPageState(); 20 | } 21 | 22 | class _LoginPageState extends FNetState { 23 | String? userName; 24 | String? passWord; 25 | bool loginEnable = false; 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return Scaffold( 30 | body: Container( 31 | child: Stack( 32 | children: [ 33 | 34 | Positioned( 35 | height: 220, 36 | child: Container( 37 | width: MediaQuery.of(context).size.width, 38 | child: Image(image: AssetImage('images/ic_login.png'),fit: BoxFit.cover,), 39 | )), 40 | Positioned.fill( 41 | top: 200, 42 | child: Container( 43 | decoration: BoxDecoration( 44 | color: Colors.white, 45 | borderRadius: BorderRadius.only( 46 | topLeft: Radius.circular(20), 47 | topRight: Radius.circular(20)), 48 | ), 49 | child: ListView( 50 | children: [ 51 | LoginInput( 52 | title: "用户名", 53 | hint: "请输入用户名", 54 | onChanged: (text) { 55 | userName = text; 56 | checkInput(); 57 | }, 58 | ), 59 | LoginInput( 60 | title: "密码", 61 | hint: "请输入密码", 62 | onChanged: (text) { 63 | passWord = text; 64 | checkInput(); 65 | }, 66 | ), 67 | Padding( 68 | padding: EdgeInsets.only(left: 20, right: 20, top: 50), 69 | child: LoginButton( 70 | title: '登录', 71 | enable: loginEnable, 72 | onPressed: sendLogin, 73 | ), 74 | ), 75 | Padding( 76 | padding: EdgeInsets.only(top: 20), 77 | child: InkWell( 78 | onTap: () { 79 | FRouter.getInstance()! 80 | .onIntentTo(RouteStatus.register); 81 | }, 82 | child: Text( 83 | "注册", 84 | style: 85 | TextStyle(fontSize: 16, color: Colors.black26), 86 | textAlign: TextAlign.center, 87 | ), 88 | )), 89 | ], 90 | ), 91 | )), 92 | Positioned( 93 | top: 60, 94 | left: 20, 95 | child: InkWell( 96 | onTap: (){ 97 | Navigator.of(context).pop(); 98 | }, 99 | child: Icon(Icons.arrow_back_ios,color: Colors.white,), 100 | )), 101 | ], 102 | )), 103 | ); 104 | } 105 | 106 | ///检查用户名和密码是否为空等情况 107 | void checkInput() { 108 | bool enable; 109 | if (isNotEmpty(userName) && isNotEmpty(passWord)) { 110 | enable = true; 111 | } else { 112 | enable = false; 113 | } 114 | 115 | setState(() { 116 | loginEnable = enable; 117 | print('loginEnable = $loginEnable'); 118 | }); 119 | } 120 | 121 | ///登录请求发送 122 | void sendLogin() async { 123 | try { 124 | print("$userName $passWord "); 125 | var result = await LoginDao.login(userName!, passWord!); 126 | if (result['errorCode'] == 0) { 127 | print('登录成功'); 128 | User user = User.fromJsonMap(result['data']); 129 | var userProvider = context.read(); 130 | userProvider.saveUser(user); 131 | SpCache.getInstance()!.saveUser(user); 132 | Navigator.of(context).pop(true); 133 | showToast('登录成功'); 134 | } else { 135 | print(result['errorMsg']); 136 | } 137 | } on NeedAuth catch (e) {} on FNetError catch (e) {} 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /lib/page/mine_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/rendering.dart'; 3 | import 'package:flutter_project/db/sp_cache.dart'; 4 | import 'package:flutter_project/http/core/f_net_state.dart'; 5 | import 'package:flutter_project/model/mine/mine_item_model.dart'; 6 | import 'package:flutter_project/model/mine/user.dart'; 7 | import 'package:flutter_project/navigator/f_navigatior.dart'; 8 | import 'package:flutter_project/provider/user_provider.dart'; 9 | import 'package:flutter_project/utils/color.dart'; 10 | import 'package:flutter_project/utils/view_util.dart'; 11 | import 'package:flutter_project/widget/setting_item.dart'; 12 | import 'package:provider/provider.dart'; 13 | 14 | ///个人中心 15 | class MinePage extends StatefulWidget { 16 | const MinePage({Key? key}) : super(key: key); 17 | 18 | @override 19 | _MinePageState createState() => _MinePageState(); 20 | } 21 | 22 | class _MinePageState extends FNetState 23 | with AutomaticKeepAliveClientMixin { 24 | List mineItemList = [ 25 | MineItemModel("images/ic_zan.png", "我的收藏"), 26 | MineItemModel("images/ic_rank.png", "积分排行"), 27 | MineItemModel("images/ic_setting.png", "设置"), 28 | MineItemModel("images/ic_about.png", "关于"), 29 | ]; 30 | 31 | @override 32 | void initState() { 33 | super.initState(); 34 | } 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | return Scaffold(body: Consumer( 39 | builder: 40 | (BuildContext context, UserProvider userProvider, Widget? child) { 41 | return NestedScrollView( 42 | headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { 43 | return [ 44 | SliverAppBar( 45 | expandedHeight: 150, 46 | pinned: true, //固定顶部 47 | flexibleSpace: FlexibleSpaceBar( 48 | collapseMode: CollapseMode.parallax, 49 | titlePadding: EdgeInsets.only(left: 0), 50 | title: _buildHead(userProvider), 51 | background: Stack( 52 | children: [ 53 | Positioned.fill( 54 | child: Container( 55 | color: primary, 56 | )), 57 | // Positioned.fill( 58 | // child: BlurView( 59 | // sigma: 20, 60 | // )) 61 | ], 62 | ), 63 | ), 64 | ) 65 | ]; 66 | }, 67 | body: ListView.builder( 68 | itemBuilder: (BuildContext context, int index) { 69 | return SettingItem( 70 | iconPath: mineItemList[index].iconUrl, 71 | title: mineItemList[index].title, 72 | index: index, 73 | ); 74 | }, 75 | itemCount: mineItemList.length, 76 | ), 77 | ); 78 | }, 79 | )); 80 | } 81 | 82 | @override 83 | bool get wantKeepAlive => true; 84 | 85 | _buildHead([UserProvider? userProvider]) { 86 | String name = "登录/注册"; 87 | if (SpCache.getInstance()!.isLogin()) { 88 | var user = SpCache.getInstance()!.getUser(); 89 | print("user ${user.toJson()}"); 90 | name = user.username!; 91 | } 92 | User? user = userProvider!.user; 93 | return GestureDetector( 94 | child: Container( 95 | alignment: Alignment.bottomLeft, 96 | padding: EdgeInsets.only(bottom: 30, left: 10), 97 | child: Row( 98 | children: [ 99 | ClipRRect( 100 | borderRadius: BorderRadius.circular(100), 101 | child: Image( 102 | width: 60, 103 | height: 60, 104 | image: AssetImage('images/default_avatar.png'), 105 | ), 106 | ), 107 | viewSpace(width: 15), 108 | Text( 109 | name, 110 | style: TextStyle(fontSize: 14), 111 | ) 112 | ], 113 | ), 114 | ), 115 | onTap: () { 116 | //是否需要登录,跳转到登录界面 117 | FRouter.getInstance()!.onIntentTo(RouteStatus.login); 118 | }, 119 | ); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /lib/page/my_collect_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/widgets.dart'; 4 | import 'package:flutter_project/base/base_refresh_load_state.dart'; 5 | import 'package:flutter_project/http/dao/person_dao.dart'; 6 | import 'package:flutter_project/model/mine/collect_model.dart'; 7 | import 'package:flutter_project/utils/color.dart'; 8 | import 'package:flutter_project/utils/view_util.dart'; 9 | import 'package:flutter_project/widget/app_toolbar.dart'; 10 | 11 | class MyCollectPage extends StatefulWidget { 12 | const MyCollectPage({Key? key}) : super(key: key); 13 | 14 | @override 15 | _MyCollectPageState createState() => _MyCollectPageState(); 16 | } 17 | 18 | ///state继承BaseRefreshLoadStateState,BaseRefreshLoadStateState是封装了刷新和下拉加载更多的框架 19 | ///[CollectModel] 相应列表model 20 | ///[Article] 列表item model, 21 | ///[MyCollectPage] 当前页面page 22 | class _MyCollectPageState extends BaseRefreshLoadStateState { 23 | 24 | 25 | _buildItem(Article article) { 26 | return Container( 27 | padding: EdgeInsets.only(left: 15, right: 15, top: 10), 28 | child: Column( 29 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 30 | crossAxisAlignment: CrossAxisAlignment.start, 31 | children: [ 32 | Text( 33 | article.title!, 34 | style: TextStyle(fontSize: 16, color: Colors.black), 35 | maxLines: 1, 36 | overflow: TextOverflow.ellipsis, 37 | ), 38 | Padding( 39 | padding: EdgeInsets.only(top: 10), 40 | child: Row( 41 | children: [ 42 | Container( 43 | height: 20, 44 | padding: EdgeInsets.only(left: 4,right: 4), 45 | child: Text( 46 | article.chapterName!, 47 | textAlign: TextAlign.center, 48 | style: TextStyle(fontSize: 12, color: primary)), 49 | decoration: BoxDecoration( 50 | borderRadius: BorderRadius.all(Radius.circular(10)), 51 | border: Border.all(width: 1, color: primary)), 52 | ), 53 | viewSpace(width: 10), 54 | Text( 55 | article.niceDate!, 56 | style: TextStyle(fontSize: 14, color: Colors.black26), 57 | ) 58 | ], 59 | ), 60 | ), 61 | Padding( 62 | padding: EdgeInsets.only(top: 6), 63 | child: Divider( 64 | height: 1, 65 | color: Colors.black26, 66 | ), 67 | ) 68 | ], 69 | ), 70 | ); 71 | } 72 | 73 | 74 | 75 | @override 76 | get child => Scaffold( 77 | appBar: articleAppBar("我的收藏"), 78 | body: ListView.builder( 79 | controller: scrollController, 80 | itemBuilder: (BuildContext context, int index) { 81 | return _buildItem(dataList[index]); 82 | }, 83 | itemCount: dataList.length, 84 | )); 85 | 86 | @override 87 | Future getData(int curPage) async{ 88 | CollectModel model = await PersonDao.getMyCollectList(curPage); 89 | return model; 90 | } 91 | 92 | @override 93 | List
parseList(CollectModel result) { 94 | return result.datas!; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /lib/page/project_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_project/http/core/f_error.dart'; 3 | import 'package:flutter_project/http/core/f_net_state.dart'; 4 | import 'package:flutter_project/http/dao/project_dao.dart'; 5 | import 'package:flutter_project/model/project/project_tab_model.dart'; 6 | import 'package:flutter_project/utils/color.dart'; 7 | 8 | import 'project_top_tab_page.dart'; 9 | 10 | ///项目模块 11 | class ProjectPage extends StatefulWidget { 12 | const ProjectPage({Key? key}) : super(key: key); 13 | 14 | @override 15 | _ProjectPageState createState() => _ProjectPageState(); 16 | } 17 | 18 | class _ProjectPageState extends FNetState 19 | with AutomaticKeepAliveClientMixin, TickerProviderStateMixin { 20 | List projectTabList = []; 21 | 22 | TabController? _tabController; 23 | 24 | @override 25 | void initState() { 26 | super.initState(); 27 | _tabController = TabController(length: projectTabList.length, vsync: this); 28 | loadData(); 29 | } 30 | 31 | @override 32 | void dispose() { 33 | super.dispose(); 34 | _tabController?.dispose(); 35 | } 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | super.build(context); 40 | var top = MediaQuery.of(context).padding.top; //刘海屏 刘海的高度 41 | return Scaffold( 42 | body: Column( 43 | children: [ 44 | Container( 45 | child: _topTabBar(), 46 | color: Colors.white, 47 | padding: EdgeInsets.only(top: top), 48 | ), 49 | Flexible( 50 | child: TabBarView( 51 | children: projectTabList.map((tab) { 52 | return ProjectTabPage(cid: tab.id); 53 | }).toList(), 54 | controller: _tabController, 55 | )) 56 | ], 57 | ), 58 | ); 59 | } 60 | 61 | @override 62 | bool get wantKeepAlive => true; 63 | 64 | ///top tab样式 65 | _topTabBar() { 66 | return TabBar( 67 | unselectedLabelColor: Colors.black, 68 | labelColor: primary, 69 | controller: _tabController, 70 | isScrollable: true, 71 | tabs: projectTabList.map((tab) { 72 | return Tab( 73 | child: Padding( 74 | padding: EdgeInsets.only(left: 5, right: 5), 75 | child: Text( 76 | tab.name!, 77 | style: TextStyle(fontSize: 16), 78 | ), 79 | ), 80 | ); 81 | }).toList(), 82 | indicator: UnderlineTabIndicator( 83 | borderSide: BorderSide(color: primary, width: 3), 84 | insets: EdgeInsets.only(left: 15, right: 15)), 85 | ); 86 | } 87 | 88 | ///请求 89 | void loadData() async { 90 | try { 91 | List? tabModel = await ProjectDao.getTab(); 92 | print('loadData $tabModel'); 93 | if (tabModel != null) { 94 | _tabController = TabController(length: tabModel.length, vsync: this); 95 | } 96 | setState(() { 97 | projectTabList = tabModel!; 98 | }); 99 | } on NeedAuth catch (e) { 100 | print(e); 101 | } on FNetError catch (e) { 102 | print(e); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /lib/page/project_top_tab_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/rendering.dart'; 3 | import 'package:flutter_project/base/base_refresh_load_state.dart'; 4 | import 'package:flutter_project/http/dao/project_dao.dart'; 5 | import 'package:flutter_project/model/project/project_model.dart'; 6 | import 'package:flutter_project/widget/card_view.dart'; 7 | import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; 8 | 9 | ///项目模块下,每个tab下的公用页面 10 | class ProjectTabPage extends StatefulWidget { 11 | final int? cid; 12 | 13 | const ProjectTabPage({Key? key, this.cid}) : super(key: key); 14 | 15 | @override 16 | _ProjectTabPageState createState() => _ProjectTabPageState(); 17 | } 18 | 19 | class _ProjectTabPageState extends BaseRefreshLoadStateState { 21 | @override 22 | bool get wantKeepAlive => true; 23 | 24 | @override 25 | get child => MediaQuery.removePadding( 26 | context: context, 27 | removeTop: true, 28 | child: StaggeredGridView.countBuilder( 29 | controller: scrollController, 30 | padding: EdgeInsets.only(top: 10, left: 10, right: 10), 31 | crossAxisCount: 2, 32 | itemCount: dataList.length, 33 | itemBuilder: (BuildContext context, int index) { 34 | return CardView( 35 | projectInfo: dataList[index], 36 | ); 37 | }, 38 | staggeredTileBuilder: (int index) { 39 | return StaggeredTile.fit(1); 40 | })); 41 | 42 | @override 43 | Future getData(int curPage) async { 44 | ProjectModel projectModel = 45 | await ProjectDao.getProjectFromTab(curPage, widget.cid!); 46 | return projectModel; 47 | } 48 | 49 | @override 50 | List parseList(ProjectModel result) { 51 | return result.datas!; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/page/register_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_project/http/core/f_error.dart'; 3 | import 'package:flutter_project/http/core/f_net_state.dart'; 4 | import 'package:flutter_project/http/dao/login_dao.dart'; 5 | import 'package:flutter_project/navigator/f_navigatior.dart'; 6 | import 'package:flutter_project/utils/string_util.dart'; 7 | import 'package:flutter_project/utils/toast_util.dart'; 8 | import 'package:flutter_project/widget/login_button.dart'; 9 | import 'package:flutter_project/widget/login_input.dart'; 10 | 11 | ///注册页面 12 | class RegisterPage extends StatefulWidget { 13 | const RegisterPage({Key? key}) : super(key: key); 14 | 15 | @override 16 | _RegisterPageState createState() => _RegisterPageState(); 17 | } 18 | 19 | class _RegisterPageState extends FNetState { 20 | String? userName; 21 | String? passWord; 22 | String? rePassword; 23 | bool loginEnable = false; 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return Scaffold( 28 | body: Container( 29 | child: Stack( 30 | children: [ 31 | Positioned( 32 | height: 220, 33 | child: Container( 34 | width: MediaQuery.of(context).size.width, 35 | child: Image(image: AssetImage('images/ic_login.png'),fit: BoxFit.cover,), 36 | )), 37 | Positioned.fill( 38 | top: 200, 39 | child: Container( 40 | decoration: BoxDecoration( 41 | color: Colors.white, 42 | borderRadius: BorderRadius.only( 43 | topLeft: Radius.circular(20), 44 | topRight: Radius.circular(20)), 45 | ), 46 | child: ListView( 47 | children: [ 48 | LoginInput( 49 | title: "用户名", 50 | hint: "请输入用户名", 51 | onChanged: (text) { 52 | userName = text; 53 | checkInput(); 54 | }, 55 | ), 56 | LoginInput( 57 | title: "密码", 58 | hint: "请输入密码", 59 | onChanged: (text) { 60 | passWord = text; 61 | checkInput(); 62 | }, 63 | ), 64 | LoginInput( 65 | title: "确认密码", 66 | hint: "请输入密码", 67 | onChanged: (text) { 68 | rePassword = text; 69 | checkInput(); 70 | }, 71 | ), 72 | Padding( 73 | padding: EdgeInsets.only(top: 20, left: 20, right: 20), 74 | child: _loginButton(), 75 | ), 76 | ], 77 | ), 78 | )), 79 | Positioned( 80 | top: 60, 81 | left: 20, 82 | child: InkWell( 83 | onTap: () { 84 | Navigator.of(context).pop(); 85 | }, 86 | child: Icon(Icons.arrow_back_ios,color: Colors.white,), 87 | )), 88 | ], 89 | )), 90 | ); 91 | } 92 | 93 | void checkInput() { 94 | bool enable; 95 | if (isNotEmpty(userName) && 96 | isNotEmpty(passWord) && 97 | isNotEmpty(rePassword)) { 98 | enable = true; 99 | } else { 100 | enable = false; 101 | } 102 | 103 | setState(() { 104 | loginEnable = enable; 105 | }); 106 | } 107 | 108 | _loginButton() { 109 | return InkWell( 110 | onTap: () { 111 | if (loginEnable) { 112 | checkParams(); 113 | } else {} 114 | }, 115 | child: LoginButton(title: "注册", enable: loginEnable), 116 | ); 117 | } 118 | 119 | void checkParams() { 120 | String? tips; 121 | if (passWord != rePassword) { 122 | tips = '两次密码不一致'; 123 | } 124 | if (tips != null) { 125 | showToast(tips); 126 | return; 127 | } 128 | send(); 129 | } 130 | 131 | void send() async { 132 | try { 133 | print("$userName $passWord $rePassword"); 134 | var result = 135 | await LoginDao.registration(userName!, passWord!, rePassword!); 136 | if (result['code'] == 0) { 137 | print('注册成功'); 138 | FRouter.getInstance()?.onIntentTo(RouteStatus.login); 139 | } else { 140 | print(result['message']); 141 | } 142 | } on NeedAuth catch (e) {} on FNetError catch (e) {} 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /lib/page/search_delegate.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_project/http/dao/home_dao.dart'; 3 | import 'package:flutter_project/model/home/home_article_model.dart'; 4 | import 'package:flutter_project/navigator/f_navigatior.dart'; 5 | 6 | class SearchDelegatePage extends SearchDelegate { 7 | @override 8 | List buildActions(BuildContext context) { 9 | return [ 10 | IconButton( 11 | onPressed: () { 12 | query = ""; 13 | }, 14 | icon: Icon(Icons.clear)) 15 | ]; 16 | } 17 | 18 | @override 19 | Widget buildLeading(BuildContext context) { 20 | return Container( 21 | child: IconButton( 22 | icon: Icon(Icons.arrow_back_ios_sharp), 23 | onPressed: () { 24 | close(context, ''); 25 | }, 26 | ), 27 | ); 28 | } 29 | 30 | @override 31 | Widget buildResults(BuildContext context) { 32 | return FutureBuilder( 33 | future: getSearchResult(query), 34 | builder: (context, snapshot) { 35 | switch (snapshot.connectionState) { 36 | case ConnectionState.none: 37 | break; 38 | case ConnectionState.waiting: 39 | break; 40 | case ConnectionState.active: 41 | break; 42 | case ConnectionState.done: 43 | if (snapshot.hasData) { 44 | List? result = snapshot.data as List?; 45 | return Container( 46 | child: ListView.builder( 47 | itemBuilder: (BuildContext context, int index) { 48 | return GestureDetector( 49 | behavior: HitTestBehavior.opaque, 50 | onTap: () { 51 | FRouter.getInstance()! 52 | .onIntentTo(RouteStatus.webview, args: { 53 | "article_path": result![index].link!, 54 | "article_title": result[index].title 55 | }); 56 | }, 57 | child: Container( 58 | height: 60, 59 | child: Padding( 60 | padding: EdgeInsets.only(left: 20, right: 20), 61 | child: Column( 62 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 63 | children: [ 64 | Text( 65 | result![index].title!, 66 | textAlign: TextAlign.start, 67 | style: TextStyle( 68 | fontSize: 16, color: Colors.black), 69 | overflow: TextOverflow.ellipsis, 70 | maxLines: 1, 71 | ), 72 | Divider( 73 | height: 1, 74 | color: Colors.black26, 75 | ) 76 | ], 77 | ), 78 | ), 79 | ), 80 | ); 81 | }, 82 | itemCount: result?.length, 83 | ), 84 | ); 85 | } 86 | break; 87 | } 88 | 89 | return Container(); 90 | }); 91 | } 92 | 93 | @override 94 | Widget buildSuggestions(BuildContext context) { 95 | return Container( 96 | child: ListView.builder( 97 | itemBuilder: (BuildContext context, int index) { 98 | return ListTile( 99 | title: Text( 100 | "", 101 | style: TextStyle(fontSize: 12, color: Colors.black), 102 | ), 103 | ); 104 | }, 105 | itemCount: 10, 106 | ), 107 | ); 108 | } 109 | 110 | Future?> getSearchResult(String query) async { 111 | HomeArticleModel articleModel = await HomeDao.getSearchResult(0, query); 112 | List? articleList = articleModel.datas; 113 | return articleList; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /lib/page/setting_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_project/db/sp_cache.dart'; 4 | import 'package:flutter_project/http/dao/login_dao.dart'; 5 | import 'package:flutter_project/provider/user_provider.dart'; 6 | import 'package:flutter_project/utils/color.dart'; 7 | import 'package:flutter_project/widget/app_toolbar.dart'; 8 | import 'package:flutter_project/widget/login_button.dart'; 9 | import 'package:provider/provider.dart'; 10 | 11 | class SettingPage extends StatefulWidget { 12 | const SettingPage({Key? key}) : super(key: key); 13 | 14 | @override 15 | _SettingPageState createState() => _SettingPageState(); 16 | } 17 | 18 | class _SettingPageState extends State { 19 | bool flag = false; 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return Scaffold( 24 | appBar: articleAppBar("设置"), 25 | body: ConstrainedBox( 26 | constraints: BoxConstraints.expand(), 27 | child: Stack( 28 | children: [ 29 | Column( 30 | children: [ 31 | Row( 32 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 33 | children: [ 34 | Padding( 35 | padding: EdgeInsets.only(left: 20), 36 | child: Text( 37 | "暗黑模式", 38 | style: TextStyle(fontSize: 18), 39 | ), 40 | ), 41 | Switch( 42 | onChanged: (bool value) { 43 | setState(() { 44 | flag = value; 45 | }); 46 | }, 47 | value: flag, 48 | activeColor: primary, 49 | ) 50 | ], 51 | ), 52 | Divider( 53 | height: 1, 54 | color: Colors.black26, 55 | ), 56 | ], 57 | ), 58 | Positioned( 59 | bottom: 60, 60 | left: 40, 61 | right: 40, 62 | child: Container( 63 | child: MaterialButton( 64 | shape: RoundedRectangleBorder( 65 | borderRadius: BorderRadius.circular(20)), 66 | height: 50, 67 | disabledColor: Colors.grey.shade300, 68 | color: primary, 69 | onPressed: () { 70 | _showDialog(); 71 | }, 72 | child: Text( 73 | "退出登录", 74 | style: TextStyle(color: Colors.white, fontSize: 18), 75 | ), 76 | ))) 77 | ], 78 | ), 79 | )); 80 | } 81 | 82 | void _showDialog() { 83 | CupertinoAlertDialog dialog = CupertinoAlertDialog( 84 | content: Text( 85 | "确定是否退出登录?", 86 | style: TextStyle(fontSize: 14), 87 | ), 88 | actions: [ 89 | TextButton(onPressed: () { 90 | Navigator.of(context, rootNavigator: true).pop(); 91 | }, child: Text("取消")), 92 | TextButton(onPressed: () { 93 | _sendLogout(); 94 | }, child: Text("确定")), 95 | ], 96 | ); 97 | showCupertinoDialog( 98 | context: context, 99 | builder: (BuildContext context) { 100 | return dialog; 101 | }, 102 | ); 103 | } 104 | 105 | void _sendLogout() async { 106 | LoginDao.logout(); 107 | SpCache.getInstance()!.clearLoginInfo(); 108 | var userProvider = context.read(); 109 | userProvider.clearUser(); 110 | Navigator.of(context, rootNavigator: true).pop(); 111 | Navigator.of(context).pop(); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /lib/page/video_detail_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_project/model/video_model.dart'; 3 | 4 | class VideoDetailPage extends StatefulWidget { 5 | 6 | final VideoModel videoModel; 7 | 8 | const VideoDetailPage(this.videoModel); 9 | 10 | @override 11 | _VideoDetailPageState createState() => _VideoDetailPageState(); 12 | } 13 | 14 | class _VideoDetailPageState extends State { 15 | @override 16 | Widget build(BuildContext context) { 17 | return Scaffold( 18 | appBar: AppBar(), 19 | body: Container( 20 | child: Column( 21 | children: [ 22 | Text('视频详情 ${widget.videoModel.videoId}'), 23 | MaterialButton( 24 | onPressed: () => {}, 25 | child: Text('跳转'), 26 | ) 27 | ], 28 | ), 29 | ), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/page/webview_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_inappwebview/flutter_inappwebview.dart'; 3 | import 'package:flutter_project/widget/app_toolbar.dart'; 4 | 5 | class WebViewPage extends StatefulWidget { 6 | 7 | final String? url;//h5链接 8 | final String? title;//toolbar 名称 9 | 10 | const WebViewPage({Key? key, this.url, this.title}) : super(key: key); 11 | 12 | @override 13 | _WebViewPageState createState() => _WebViewPageState(); 14 | } 15 | 16 | class _WebViewPageState extends State { 17 | @override 18 | Widget build(BuildContext context) { 19 | return Scaffold( 20 | appBar: articleAppBar(widget.title!), 21 | body: _buildWbContent(), 22 | ); 23 | } 24 | 25 | _buildWbContent() { 26 | return Column( 27 | children: [ 28 | SizedBox( 29 | height: 1, 30 | width: double.infinity, 31 | child: const DecoratedBox(decoration: BoxDecoration(color: Color(0xFFEEEEEE))), 32 | ), 33 | Expanded(child: InAppWebView( 34 | initialOptions: InAppWebViewGroupOptions( 35 | crossPlatform: InAppWebViewOptions( 36 | useShouldOverrideUrlLoading: true, 37 | javaScriptEnabled: true, 38 | ) 39 | ), 40 | initialUrlRequest: URLRequest(url: Uri.parse(widget.url!)), 41 | 42 | 43 | )) 44 | ], 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/provider/provider_manager.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | import 'package:flutter_project/provider/theme_provider.dart'; 4 | import 'package:flutter_project/provider/user_provider.dart'; 5 | import 'package:provider/provider.dart'; 6 | import 'package:provider/single_child_widget.dart'; 7 | 8 | List topProviders = [ 9 | ChangeNotifierProvider(create: (_) => UserProvider()), 10 | ChangeNotifierProvider(create: (_) => ThemeProvider()), 11 | 12 | ]; 13 | -------------------------------------------------------------------------------- /lib/provider/theme_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_project/db/sp_cache.dart'; 4 | 5 | extension ThemeModeExtension on ThemeMode { 6 | String get value => ['System', 'Light', 'Dark'][index]; 7 | } 8 | 9 | class ThemeProvider extends ChangeNotifier { 10 | ThemeMode? _themeMode; 11 | 12 | bool isDark(){ 13 | return _themeMode == ThemeMode.dark; 14 | } 15 | 16 | ThemeMode? getThemeMode() { 17 | var theme = SpCache.getInstance()!.get("key"); 18 | switch (theme) { 19 | case "Dark": 20 | _themeMode = ThemeMode.dark; 21 | break; 22 | case "Light": 23 | _themeMode = ThemeMode.light; 24 | break; 25 | case "System": 26 | _themeMode = ThemeMode.system; 27 | break; 28 | default: 29 | _themeMode = ThemeMode.light; 30 | break; 31 | } 32 | 33 | return _themeMode; 34 | } 35 | 36 | void setThemeMode(String mode) { 37 | SpCache.getInstance()!.setString("key", mode); 38 | notifyListeners(); 39 | } 40 | 41 | ThemeData getTheme({bool isDarkMode = false}) { 42 | var themeData = ThemeData( 43 | brightness: isDarkMode ? Brightness.dark : Brightness.light, 44 | primaryColor: isDarkMode ? Colors.black : Colors.white, 45 | accentColor: isDarkMode ? Colors.black26 : Colors.white, 46 | scaffoldBackgroundColor: isDarkMode ? Colors.black : Colors.white); 47 | return themeData; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/provider/user_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter_project/model/mine/user.dart'; 3 | 4 | class UserProvider extends ChangeNotifier { 5 | User? _user; 6 | 7 | User? get user => _user; 8 | 9 | bool get hasUser => user != null; 10 | 11 | saveUser(User user){ 12 | _user = user; 13 | notifyListeners(); 14 | } 15 | 16 | clearUser(){ 17 | _user = null; 18 | notifyListeners(); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /lib/utils/cache_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | 5 | ///图片缓存 6 | Widget cachedImage(String url, {double? width, double? height}) { 7 | return CachedNetworkImage( 8 | height: height, 9 | width: width, 10 | fit: BoxFit.cover, 11 | placeholder: ( 12 | BuildContext context, 13 | String url, 14 | ) => 15 | Container(color: Colors.grey[200]), 16 | errorWidget: ( 17 | BuildContext context, 18 | String url, 19 | dynamic error, 20 | ) => 21 | Icon(Icons.error), 22 | imageUrl: url); 23 | } 24 | -------------------------------------------------------------------------------- /lib/utils/color.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | const MaterialColor white = const MaterialColor(0xFFFFFFFF, const { 4 | 50: const Color(0xFFFFFFFF), 5 | 100: Color(0xFFFFFFFF), 6 | 200: Color(0xFFFFFFFF), 7 | 300: Color(0xFFFFFFFF), 8 | 400: Color(0xFFFFFFFF), 9 | 500: Color(0xFFFFFFFF), 10 | 600: Color(0xFFFFFFFF), 11 | 700: Color(0xFFFFFFFF), 12 | 800: Color(0xFFFFFFFF), 13 | 900: Color(0xFFFFFFFF), 14 | }); 15 | 16 | const MaterialColor primary = const MaterialColor(0xff739AF0, const { 17 | 50: const Color(0xff739AF0), 18 | 100: Color(0xff739AF0), 19 | 200: Color(0xff739AF0), 20 | 300: Color(0xff739AF0), 21 | 400: Color(0xff739AF0), 22 | 500: Color(0xff739AF0), 23 | 600: Color(0xff739AF0), 24 | 700: Color(0xff739AF0), 25 | 800: Color(0xff739AF0), 26 | 900: Color(0xff739AF0) 27 | }); 28 | 29 | class BaColors{ 30 | 31 | static const Color dark_bg= Colors.black; 32 | } 33 | -------------------------------------------------------------------------------- /lib/utils/constants.dart: -------------------------------------------------------------------------------- 1 | class Constants{ 2 | 3 | } -------------------------------------------------------------------------------- /lib/utils/string_util.dart: -------------------------------------------------------------------------------- 1 | bool isNotEmpty(String? text) { 2 | return text?.isNotEmpty ?? false; 3 | } 4 | 5 | bool isEmpty(String? text){ 6 | return text?.isEmpty ?? false; 7 | } 8 | -------------------------------------------------------------------------------- /lib/utils/toast_util.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | import 'package:fluttertoast/fluttertoast.dart'; 4 | 5 | void showToast(String text){ 6 | Fluttertoast.showToast(msg: text,toastLength: Toast.LENGTH_LONG,gravity: ToastGravity.CENTER); 7 | } -------------------------------------------------------------------------------- /lib/utils/view_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | ///view间距 5 | ///[height] 竖向,[width]横向 6 | SizedBox viewSpace({double height: 1, double width: 1}) { 7 | return SizedBox(height: height, width: width); 8 | } 9 | 10 | ///listView间隔线 11 | borderLine(BuildContext context, {bottom: true, top: false}) { 12 | BorderSide borderSide = BorderSide(width: 0.5, color: Colors.grey[200]!); 13 | return Border( 14 | bottom: bottom ? borderSide : BorderSide.none, 15 | top: top ? borderSide : BorderSide.none, 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /lib/widget/app_toolbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | appBar(String title, String rightTitle, VoidCallback rightButtonClick) { 5 | return AppBar( 6 | centerTitle: false, 7 | titleSpacing: 0, 8 | leading: BackButton(), 9 | title: Text( 10 | title, 11 | style: TextStyle(fontSize: 18), 12 | ), 13 | actions: [ 14 | InkWell( 15 | onTap: rightButtonClick, 16 | child: Container( 17 | padding: EdgeInsets.only(left: 15, right: 15), 18 | alignment: Alignment.center, 19 | child: Text( 20 | rightTitle, 21 | style: TextStyle(fontSize: 18, color: Colors.grey[500]), 22 | textAlign: TextAlign.center, 23 | ), 24 | ), 25 | ) 26 | ], 27 | ); 28 | } 29 | 30 | articleAppBar(String title){ 31 | return AppBar( 32 | centerTitle: true, 33 | leading: BackButton(), 34 | title: Text( 35 | title, 36 | style: TextStyle(fontSize: 18), 37 | ), 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /lib/widget/article_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_project/model/home/home_article_model.dart'; 3 | import 'package:flutter_project/navigator/f_navigatior.dart'; 4 | import 'package:flutter_project/utils/color.dart'; 5 | import 'package:flutter_project/utils/view_util.dart'; 6 | 7 | ///文章list item组件 8 | class ArticleItem extends StatelessWidget { 9 | final ArticleInfo? articleInfo; 10 | final VoidCallback? onCollect; 11 | 12 | const ArticleItem({Key? key, this.articleInfo, this.onCollect}) 13 | : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | String name = ""; 18 | bool isCollect = articleInfo!.collect!;//是否已经收藏 19 | 20 | if (articleInfo!.shareUser!.isEmpty) { 21 | name = articleInfo!.author!; 22 | } else { 23 | name = articleInfo!.shareUser!; 24 | } 25 | return GestureDetector( 26 | behavior: HitTestBehavior.opaque, //空白处点击响应 27 | onTap: () { 28 | FRouter.getInstance()!.onIntentTo(RouteStatus.webview, args: { 29 | "article_path": articleInfo!.link!, 30 | "article_title": articleInfo!.title 31 | }); 32 | }, 33 | child: Container( 34 | padding: EdgeInsets.only(left: 15, right: 15, bottom: 5), 35 | height: 90, 36 | child: Column( 37 | crossAxisAlignment: CrossAxisAlignment.start, 38 | mainAxisAlignment: MainAxisAlignment.spaceAround, 39 | children: [ 40 | Text( 41 | articleInfo!.title!, 42 | style: TextStyle(fontSize: 16, color: Colors.black), 43 | maxLines: 2, 44 | overflow: TextOverflow.ellipsis, 45 | ), 46 | Row( 47 | children: [ 48 | Container( 49 | height: 20, 50 | padding: EdgeInsets.only(left: 4, right: 4), 51 | child: Text(articleInfo!.chapterName!, 52 | textAlign: TextAlign.center, 53 | style: TextStyle(fontSize: 12, color: primary)), 54 | decoration: BoxDecoration( 55 | borderRadius: BorderRadius.all(Radius.circular(10)), 56 | border: Border.all(width: 1, color: primary)), 57 | ), 58 | viewSpace(width: 10), 59 | InkWell( 60 | onTap: onCollect, 61 | child: Image( 62 | image: isCollect 63 | ? AssetImage('images/ic_favorite.png') 64 | : AssetImage('images/ic_un_favorite.png'), 65 | width: 20, 66 | height: 20, 67 | ), 68 | ), 69 | Expanded( 70 | child: Padding( 71 | padding: EdgeInsets.only(left: 10), 72 | child: Text( 73 | name, 74 | style: TextStyle(fontSize: 12, color: Colors.black26), 75 | ), 76 | )) 77 | ], 78 | verticalDirection: VerticalDirection.up, 79 | mainAxisAlignment: MainAxisAlignment.start, 80 | ), 81 | Divider( 82 | height: 1, 83 | color: Colors.black26, 84 | ) 85 | ], 86 | )), 87 | ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib/widget/blur_view.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | ///高斯模糊组件 6 | class BlurView extends StatelessWidget { 7 | final Widget? child; 8 | final double sigma; 9 | 10 | const BlurView({Key? key, this.sigma = 10, this.child}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return BackdropFilter( 15 | filter: ImageFilter.blur(sigmaX: sigma, sigmaY: sigma), 16 | child: Container( 17 | color: Colors.white10, 18 | child: child, 19 | ), 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/widget/card_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_project/model/project/project_model.dart'; 5 | import 'package:flutter_project/navigator/f_navigatior.dart'; 6 | import 'package:flutter_project/utils/cache_util.dart'; 7 | import 'package:flutter_project/utils/color.dart'; 8 | 9 | class CardView extends StatelessWidget { 10 | final ProjectInfo? projectInfo; 11 | 12 | const CardView({Key? key, this.projectInfo}) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return InkWell( 17 | onTap: () { 18 | FRouter.getInstance()?.onIntentTo(RouteStatus.webview, args: { 19 | "article_path": projectInfo!.link!, 20 | "article_title": projectInfo!.title 21 | }); 22 | }, 23 | child: SizedBox( 24 | height: 280, 25 | child: Card( 26 | margin: EdgeInsets.only(left: 10, right: 10, bottom: 10, top: 10), 27 | child: ClipRRect( 28 | borderRadius: BorderRadius.circular(6), 29 | child: Column( 30 | crossAxisAlignment: CrossAxisAlignment.start, 31 | children: [_image(context), _itemTitle()], 32 | ), 33 | ), 34 | ), 35 | ), 36 | ); 37 | } 38 | 39 | _image(BuildContext context) { 40 | final size = MediaQuery.of(context).size; 41 | return Stack( 42 | children: [ 43 | cachedImage(projectInfo!.envelopePic!, 44 | width: size.width / 2 - 10, height: 180), 45 | Positioned( 46 | left: 0, 47 | right: 0, 48 | bottom: 0, 49 | child: Container( 50 | padding: EdgeInsets.only(left: 8, right: 8, bottom: 3, top: 5), 51 | decoration: BoxDecoration( 52 | //渐变 53 | gradient: LinearGradient( 54 | begin: Alignment.bottomCenter, 55 | end: Alignment.topCenter, 56 | colors: [Colors.black54, Colors.transparent])), 57 | child: Row( 58 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 59 | children: [], 60 | ), 61 | )) 62 | ], 63 | ); 64 | } 65 | 66 | _itemTitle() { 67 | return Expanded( 68 | child: Container( 69 | child: Column( 70 | crossAxisAlignment: CrossAxisAlignment.start, 71 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 72 | children: [ 73 | Padding( 74 | padding: EdgeInsets.only(left: 6, right: 6), 75 | child: Text( 76 | projectInfo!.title!, 77 | style: TextStyle(color: Colors.black, fontSize: 16), 78 | maxLines: 2, 79 | overflow: TextOverflow.ellipsis, 80 | ), 81 | ), 82 | Expanded( 83 | child: Row( 84 | children: [ 85 | ClipRRect( 86 | borderRadius: BorderRadius.circular(12), 87 | child: Icon( 88 | Icons.person, 89 | color: primary, 90 | ), 91 | ), 92 | Padding( 93 | padding: EdgeInsets.only(left: 8), 94 | child: Text( 95 | projectInfo!.author!, 96 | style: TextStyle(fontSize: 12, color: Colors.black54), 97 | ), 98 | ), 99 | ], 100 | )) 101 | ], 102 | ), 103 | )); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /lib/widget/f_banner.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_project/model/home/banner_model.dart'; 3 | import 'package:flutter_swiper_null_safety/flutter_swiper_null_safety.dart'; 4 | 5 | ///banner组件 6 | ///Swiper 7 | class FBanner extends StatelessWidget { 8 | final List? bannerList; //数据 9 | final double? bannerHeight; //banner高度 10 | final EdgeInsetsGeometry? padding; 11 | 12 | const FBanner(this.bannerList, {Key? key, this.bannerHeight = 110, this.padding}) 13 | : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Container( 18 | height: bannerHeight, 19 | child: _banner(), 20 | ); 21 | } 22 | 23 | _banner() { 24 | var right = 10 + (padding?.horizontal ?? 0) / 2; 25 | return Swiper( 26 | key: UniqueKey(), 27 | itemCount: bannerList!.length, 28 | autoplay: true, 29 | itemBuilder: (BuildContext context, int index) { 30 | return _image(bannerList![index]); 31 | }, 32 | pagination: SwiperPagination( 33 | alignment: Alignment.bottomRight, 34 | margin: EdgeInsets.only(right: right, bottom: 10), 35 | builder: DotSwiperPaginationBuilder( 36 | color: Colors.white60, size: 6, activeSize: 6)), 37 | ); 38 | } 39 | 40 | _image(BannerModel bannerModel) { 41 | return InkWell( 42 | onTap: () { 43 | print('11'); 44 | }, 45 | child: Container( 46 | padding: padding, 47 | child: ClipRRect( 48 | borderRadius: BorderRadius.all(Radius.circular(6)), 49 | child: Image.network(bannerModel.imagePath, fit: BoxFit.cover), 50 | ), 51 | ), 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/widget/login_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_project/utils/color.dart'; 3 | 4 | ///登录、注册界面按钮 5 | class LoginButton extends StatelessWidget { 6 | final String? title; 7 | final bool enable; 8 | 9 | final VoidCallback? onPressed; 10 | 11 | const LoginButton({Key? key, this.title, this.enable = true, this.onPressed}) 12 | : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return FractionallySizedBox( 17 | widthFactor: 1, 18 | child: MaterialButton( 19 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), 20 | height: 50, 21 | onPressed: enable ? onPressed : null, 22 | disabledColor: Colors.grey.shade300, 23 | color: primary, 24 | child: Text( 25 | title!, 26 | style: TextStyle(color: Colors.white, fontSize: 18), 27 | ), 28 | ), 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/widget/login_input.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_project/utils/color.dart'; 3 | 4 | ///登录、注册界面输入框组件 5 | class LoginInput extends StatefulWidget { 6 | final String? title; 7 | final String? hint; 8 | final ValueChanged? onChanged; 9 | final ValueChanged? focusChanged; 10 | final bool lineStretch; 11 | final bool obscureText; 12 | final TextInputType? inputType; 13 | 14 | const LoginInput( 15 | {Key? key, 16 | this.title, 17 | this.hint, 18 | this.onChanged, 19 | this.focusChanged, 20 | this.lineStretch = false, 21 | this.obscureText = false, 22 | this.inputType}) 23 | : super(key: key); 24 | 25 | @override 26 | _LoginInputState createState() => _LoginInputState(); 27 | } 28 | 29 | class _LoginInputState extends State { 30 | final _focusNode = FocusNode(); 31 | 32 | @override 33 | void initState() { 34 | super.initState(); 35 | _focusNode.addListener(() { 36 | if (widget.focusChanged != null) { 37 | widget.focusChanged!(_focusNode.hasFocus); 38 | } 39 | }); 40 | } 41 | 42 | @override 43 | void dispose() { 44 | _focusNode.dispose(); 45 | super.dispose(); 46 | } 47 | 48 | @override 49 | Widget build(BuildContext context) { 50 | return Column( 51 | crossAxisAlignment: CrossAxisAlignment.start, 52 | children: [ 53 | Padding( 54 | padding: EdgeInsets.only(left: 25,top: 15), 55 | child: Text( 56 | widget.title!, 57 | style: TextStyle(fontSize: 18), 58 | ), 59 | ), 60 | _input(), 61 | Padding( 62 | padding: EdgeInsets.only( 63 | left: !widget.lineStretch ? 25 : 0, 64 | right: !widget.lineStretch ? 25 : 0), 65 | child: Divider( 66 | height: 1, 67 | thickness: 0.5, 68 | ), 69 | ) 70 | ], 71 | ); 72 | } 73 | 74 | _input() { 75 | return Container( 76 | child: TextField( 77 | focusNode: _focusNode, 78 | onChanged: widget.onChanged, 79 | obscureText: widget.obscureText, 80 | keyboardType: widget.inputType, 81 | autofocus: !widget.obscureText, 82 | cursorColor: primary, 83 | style: TextStyle( 84 | fontSize: 16, color: Colors.black, fontWeight: FontWeight.w300), 85 | decoration: InputDecoration( 86 | contentPadding: EdgeInsets.only(left: 25, right: 25), 87 | border: InputBorder.none, 88 | hintText: widget.hint ?? '', 89 | hintStyle: TextStyle(fontSize: 15, color: Colors.grey), 90 | ), 91 | )); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /lib/widget/navigation_bar.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/services.dart'; 5 | import 'package:flutter_project/provider/theme_provider.dart'; 6 | import 'package:flutter_project/utils/color.dart'; 7 | import 'package:provider/provider.dart'; 8 | 9 | class NavigationBar extends StatefulWidget { 10 | final StatusStyle statusStyle; 11 | final Color color; 12 | final double height; 13 | final Widget? childWidget; 14 | 15 | const NavigationBar( 16 | {Key? key, 17 | this.statusStyle = StatusStyle.DARK_STYLE, 18 | this.color = Colors.white, 19 | this.height = 45, 20 | this.childWidget}) 21 | : super(key: key); 22 | 23 | @override 24 | _NavigationBarState createState() => _NavigationBarState(); 25 | } 26 | 27 | class _NavigationBarState extends State { 28 | var _color; 29 | var _statusStyle; 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | //沉浸式状态栏样式 34 | var brightness; 35 | 36 | var themeProvider = context.watch(); 37 | if (themeProvider.isDark()) { 38 | _color = BaColors.dark_bg; 39 | _statusStyle = StatusStyle.LIGHT_STYLE; 40 | }else{ 41 | _color = widget.color; 42 | _statusStyle = StatusStyle.DARK_STYLE; 43 | } 44 | 45 | if (Platform.isIOS) { 46 | brightness = _statusStyle == StatusStyle.LIGHT_STYLE 47 | ? Brightness.dark 48 | : Brightness.light; 49 | } else { 50 | brightness = _statusStyle == StatusStyle.LIGHT_STYLE 51 | ? Brightness.light 52 | : Brightness.dark; 53 | } 54 | SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light.copyWith( 55 | statusBarColor: Colors.transparent, 56 | statusBarBrightness: brightness, 57 | statusBarIconBrightness: brightness, 58 | )); 59 | 60 | var top = MediaQuery.of(context).padding.top; //刘海屏 刘海的高度 61 | print('top : $top'); 62 | return Container( 63 | width: MediaQuery.of(context).size.width, 64 | height: top + widget.height, 65 | child: widget.childWidget, 66 | padding: EdgeInsets.only(top: top), 67 | decoration: BoxDecoration(color: _color), 68 | ); 69 | } 70 | } 71 | 72 | enum StatusStyle { LIGHT_STYLE, DARK_STYLE } 73 | -------------------------------------------------------------------------------- /lib/widget/setting_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_project/http/dao/person_dao.dart'; 3 | import 'package:flutter_project/navigator/f_navigatior.dart'; 4 | import 'package:flutter_project/utils/view_util.dart'; 5 | 6 | ///个人中心Item组件 7 | ///包含icon和title 8 | class SettingItem extends StatelessWidget { 9 | final String? iconPath; 10 | final String? title; 11 | final int? index; 12 | 13 | const SettingItem({Key? key, this.iconPath, this.title, this.index}) 14 | : super(key: key); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return InkWell( 19 | onTap: () { 20 | switch (index) { 21 | case 0: 22 | //我的收藏 23 | FRouter.getInstance()!.onIntentTo(RouteStatus.collect); 24 | break; 25 | case 1: 26 | FRouter.getInstance()!.onIntentTo(RouteStatus.coinRank); 27 | break; 28 | case 2: 29 | //设置 30 | FRouter.getInstance()!.onIntentTo(RouteStatus.setting); 31 | break; 32 | case 3: 33 | //关于 34 | FRouter.getInstance()!.onIntentTo(RouteStatus.about); 35 | break; 36 | } 37 | }, 38 | child: Container( 39 | margin: EdgeInsets.only(left: 20, right: 20), 40 | padding: EdgeInsets.only(top: 5, bottom: 5), 41 | height: 70, 42 | decoration: BoxDecoration(border: borderLine(context)), 43 | child: Column( 44 | mainAxisAlignment: MainAxisAlignment.center, 45 | children: [ 46 | Row( 47 | children: [ 48 | Image( 49 | image: AssetImage(iconPath!), 50 | height: 30, 51 | width: 30, 52 | ), 53 | viewSpace(width: 30), 54 | Text(title!, style: TextStyle(fontSize: 14)), 55 | ], 56 | ), 57 | ], 58 | ))); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_project 2 | description: A new Flutter project. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.12.0 <3.0.0" 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | 27 | 28 | # The following adds the Cupertino Icons font to your application. 29 | # Use with the CupertinoIcons class for iOS style icons. 30 | cupertino_icons: ^1.0.2 31 | dio: ^4.0.4 32 | json_annotation: ^4.0.1 33 | shared_preferences: ^2.0.6 34 | fluttertoast: ^8.0.6 35 | underline_indicator: ^0.0.2 #指示器 36 | flutter_swiper_null_safety: ^1.0.2 37 | flutter_staggered_grid_view: ^0.4.0 38 | transparent_image: ^2.0.0 39 | cached_network_image: ^3.0.0 40 | flutter_inappwebview: ^5.3.2 41 | provider: ^5.0.0 42 | dio_cookie_manager: ^2.0.0 43 | cookie_jar: 3.0.1 44 | path_provider: ^2.0.1 45 | 46 | dev_dependencies: 47 | flutter_test: 48 | sdk: flutter 49 | json_serializable: ^4.1.3 50 | build_runner: ^2.0.4 51 | 52 | # For information on the generic Dart part of this file, see the 53 | # following page: https://dart.dev/tools/pub/pubspec 54 | 55 | # The following section is specific to Flutter. 56 | flutter: 57 | 58 | # The following line ensures that the Material Icons font is 59 | # included with your application, so that you can use the icons in 60 | # the material Icons class. 61 | uses-material-design: true 62 | 63 | # To add assets to your application, add an assets section, like this: 64 | assets: 65 | - images/ 66 | # - images/a_dot_ham.jpeg 67 | 68 | # An image asset can refer to one or more resolution-specific "variants", see 69 | # https://flutter.dev/assets-and-images/#resolution-aware. 70 | 71 | # For details regarding adding assets from package dependencies, see 72 | # https://flutter.dev/assets-and-images/#from-packages 73 | 74 | # To add custom fonts to your application, add a fonts section here, 75 | # in this "flutter" section. Each entry in this list should have a 76 | # "family" key with the font family name, and a "fonts" key with a 77 | # list giving the asset and other descriptors for the font. For 78 | # example: 79 | # fonts: 80 | # - family: Schyler 81 | # fonts: 82 | # - asset: fonts/Schyler-Regular.ttf 83 | # - asset: fonts/Schyler-Italic.ttf 84 | # style: italic 85 | # - family: Trajan Pro 86 | # fonts: 87 | # - asset: fonts/TrajanPro.ttf 88 | # - asset: fonts/TrajanPro_Bold.ttf 89 | # weight: 700 90 | # 91 | # For details regarding fonts from package dependencies, 92 | # see https://flutter.dev/custom-fonts/#from-packages 93 | -------------------------------------------------------------------------------- /screenshots/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/screenshots/1.jpg -------------------------------------------------------------------------------- /screenshots/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/screenshots/2.jpg -------------------------------------------------------------------------------- /screenshots/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/screenshots/3.jpg -------------------------------------------------------------------------------- /screenshots/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/screenshots/4.jpg -------------------------------------------------------------------------------- /screenshots/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/screenshots/5.jpg -------------------------------------------------------------------------------- /screenshots/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/screenshots/6.jpg -------------------------------------------------------------------------------- /screenshots/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/screenshots/7.png -------------------------------------------------------------------------------- /screenshots/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuusy/flutter_read/99e7defa9783791738ab9649d64fca1979c7fe9c/screenshots/8.jpg -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:flutter_project/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | --------------------------------------------------------------------------------