├── .gitignore ├── .metadata ├── README.md ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── tao │ │ │ │ └── flutter_wan │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── key.properties ├── key_store.jks └── settings.gradle ├── images └── ttxs.jpg ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── 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 ├── app │ ├── config │ │ └── route.dart │ ├── init.dart │ ├── typedef │ │ └── function.dart │ └── utils │ │ ├── cache │ │ └── sp_util.dart │ │ ├── show │ │ └── toast_util.dart │ │ ├── tool │ │ └── string_util.dart │ │ └── ui │ │ ├── future.dart │ │ ├── keep_alive_page.dart │ │ ├── ui_adapter.dart │ │ └── view_util.dart ├── bean │ ├── README.md │ ├── common │ │ ├── article_detail_bean.dart │ │ └── btn_info.dart │ ├── home │ │ ├── banner_bean.dart │ │ ├── banner_bean.g.dart │ │ ├── home_article_bean.dart │ │ └── home_article_bean.g.dart │ ├── navi │ │ ├── navi_info_bean.dart │ │ └── navi_info_bean.g.dart │ ├── project │ │ ├── project_bean.dart │ │ ├── project_bean.g.dart │ │ ├── project_detail_bean.dart │ │ └── project_detail_bean.g.dart │ ├── tree │ │ ├── tree_info_bean.dart │ │ ├── tree_info_bean.g.dart │ │ ├── tree_info_item_bean.dart │ │ └── tree_info_item_bean.g.dart │ └── user │ │ ├── collect_list_bean.dart │ │ ├── collect_list_bean.g.dart │ │ ├── common_response.dart │ │ ├── common_response.g.dart │ │ ├── hot_word_bean.dart │ │ ├── hot_word_bean.g.dart │ │ ├── login_info_bean.dart │ │ ├── login_info_bean.g.dart │ │ ├── register_info_bean.dart │ │ └── register_info_bean.g.dart ├── http │ ├── api.dart │ └── http.dart ├── main.dart ├── module │ ├── home │ │ ├── action.dart │ │ ├── effect.dart │ │ ├── page.dart │ │ ├── reducer.dart │ │ ├── state.dart │ │ ├── view.dart │ │ └── widget │ │ │ └── home_banner.dart │ ├── main │ │ ├── action.dart │ │ ├── collect │ │ │ ├── action.dart │ │ │ ├── adapter.dart │ │ │ ├── collect_item │ │ │ │ ├── action.dart │ │ │ │ ├── component.dart │ │ │ │ ├── effect.dart │ │ │ │ ├── state.dart │ │ │ │ ├── view.dart │ │ │ │ └── widget │ │ │ │ │ └── collect_item_view.dart │ │ │ ├── effect.dart │ │ │ ├── page.dart │ │ │ ├── reducer.dart │ │ │ ├── state.dart │ │ │ └── view.dart │ │ ├── effect.dart │ │ ├── login │ │ │ ├── action.dart │ │ │ ├── effect.dart │ │ │ ├── page.dart │ │ │ ├── state.dart │ │ │ ├── view.dart │ │ │ └── widget │ │ │ │ └── login_view.dart │ │ ├── page.dart │ │ ├── reducer.dart │ │ ├── register │ │ │ ├── action.dart │ │ │ ├── effect.dart │ │ │ ├── page.dart │ │ │ ├── state.dart │ │ │ ├── view.dart │ │ │ └── widget │ │ │ │ └── register_view.dart │ │ ├── search │ │ │ ├── action.dart │ │ │ ├── effect.dart │ │ │ ├── page.dart │ │ │ ├── reducer.dart │ │ │ ├── state.dart │ │ │ ├── view.dart │ │ │ └── widget │ │ │ │ ├── search_app_bar.dart │ │ │ │ └── search_hot_word.dart │ │ ├── state.dart │ │ ├── view.dart │ │ └── widget │ │ │ ├── main_app_bar.dart │ │ │ ├── main_body.dart │ │ │ ├── main_bottom_navigation.dart │ │ │ └── main_drawer.dart │ ├── navi │ │ ├── action.dart │ │ ├── adapter.dart │ │ ├── effect.dart │ │ ├── navi_item │ │ │ ├── action.dart │ │ │ ├── component.dart │ │ │ ├── effect.dart │ │ │ ├── state.dart │ │ │ ├── view.dart │ │ │ └── widget │ │ │ │ └── navi_detail_Item_view.dart │ │ ├── page.dart │ │ ├── reducer.dart │ │ ├── state.dart │ │ └── view.dart │ ├── project │ │ ├── action.dart │ │ ├── effect.dart │ │ ├── page.dart │ │ ├── project_tab │ │ │ ├── action.dart │ │ │ ├── adapter.dart │ │ │ ├── effect.dart │ │ │ ├── page.dart │ │ │ ├── reducer.dart │ │ │ ├── state.dart │ │ │ └── view.dart │ │ ├── project_tab_item │ │ │ ├── action.dart │ │ │ ├── component.dart │ │ │ ├── effect.dart │ │ │ ├── state.dart │ │ │ ├── view.dart │ │ │ └── widget │ │ │ │ └── project_item_detail.dart │ │ ├── reducer.dart │ │ ├── state.dart │ │ └── view.dart │ ├── tree │ │ ├── action.dart │ │ ├── adapter.dart │ │ ├── effect.dart │ │ ├── page.dart │ │ ├── reducer.dart │ │ ├── state.dart │ │ ├── tree_detail │ │ │ ├── page.dart │ │ │ ├── state.dart │ │ │ ├── tree_detail_tab │ │ │ │ ├── page.dart │ │ │ │ ├── state.dart │ │ │ │ └── view.dart │ │ │ └── view.dart │ │ ├── tree_item │ │ │ ├── action.dart │ │ │ ├── component.dart │ │ │ ├── effect.dart │ │ │ ├── state.dart │ │ │ ├── view.dart │ │ │ └── widget │ │ │ │ └── tree_item_style.dart │ │ └── view.dart │ └── web │ │ ├── action.dart │ │ ├── effect.dart │ │ ├── page.dart │ │ ├── reducer.dart │ │ ├── state.dart │ │ ├── view.dart │ │ └── widget │ │ └── common_webview.dart └── view │ ├── component │ └── article_list │ │ ├── action.dart │ │ ├── adapter.dart │ │ ├── component.dart │ │ ├── effect.dart │ │ ├── item │ │ ├── action.dart │ │ ├── component.dart │ │ ├── effect.dart │ │ ├── state.dart │ │ ├── view.dart │ │ └── widget │ │ │ └── article_item_detail.dart │ │ ├── reducer.dart │ │ ├── state.dart │ │ └── view.dart │ ├── dialog │ └── easy │ │ └── easy_dialog.dart │ └── widget │ ├── input │ └── input_actions.dart │ └── search │ └── search_bar.dart ├── linux ├── .gitignore ├── CMakeLists.txt ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake ├── main.cc ├── my_application.cc └── my_application.h ├── macos ├── .gitignore ├── Flutter │ ├── Flutter-Debug.xcconfig │ ├── Flutter-Release.xcconfig │ └── GeneratedPluginRegistrant.swift ├── Podfile ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── app_icon_1024.png │ │ ├── app_icon_128.png │ │ ├── app_icon_16.png │ │ ├── app_icon_256.png │ │ ├── app_icon_32.png │ │ ├── app_icon_512.png │ │ └── app_icon_64.png │ ├── Base.lproj │ └── MainMenu.xib │ ├── Configs │ ├── AppInfo.xcconfig │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── Warnings.xcconfig │ ├── DebugProfile.entitlements │ ├── Info.plist │ ├── MainFlutterWindow.swift │ └── Release.entitlements ├── pubspec.lock ├── pubspec.yaml ├── test └── widget_test.dart ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ └── Icon-512.png ├── index.html └── manifest.json └── windows ├── .gitignore ├── CMakeLists.txt ├── flutter ├── CMakeLists.txt ├── generated_plugin_registrant.cc ├── generated_plugin_registrant.h └── generated_plugins.cmake └── runner ├── CMakeLists.txt ├── Runner.rc ├── flutter_window.cpp ├── flutter_window.h ├── main.cpp ├── resource.h ├── resources └── app_icon.ico ├── run_loop.cpp ├── run_loop.h ├── runner.exe.manifest ├── utils.cpp ├── utils.h ├── win32_window.cpp └── win32_window.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | -------------------------------------------------------------------------------- /.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: 0b8abb4724aa590dd0f429683339b1e045a1594d 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## fish_redux使用 2 | - 注:该项目为Flutter + fish_redux,页面基本均是fish_redux搭建 3 | - fish-redux使用 4 | - 教程:[掘金:fish_redux使用详解---看完就会用!](https://juejin.im/post/6860029460524040199) 5 | - 备用:[博客园:fish_redux使用详解---看完就会用!](https://www.cnblogs.com/xdd666/p/13803224.html) 6 | - [代码demo地址](https://github.com/CNAD666/ExampleCode/tree/master/Flutter/fish_redux_demo) 7 | 8 | ## 说明 9 | 10 | **多谢铁子们给我点的小星星,为了对得起你们的支持,我重写了第一版所有模块,第二版的坑填完了** 11 | 12 | - 初版的fish_redux的玩Android是我刚学flutter时写的,代码写的比较混乱,重构代码,也是为了让大家更清晰了解fish_redux结构,也给出TabBar控制器在fish_redux初始化的解决方案,大家可以看看 13 | - 重构的所有模块,无限弱化了reducer层作用 14 | - 在日程使用fish_redux和flutter_bloc后,实际能深刻体会reducer实际上只是相当于bloc中yield或emit关键字的作用,职能完全可以弱化为,仅仅作为状态刷新;这样可以大大简化开发流程,只需要关注view -> action -> effect (reducer: 统一刷新事件) 15 | - view模块中,页面使用widget组合的方式去构造的,只传入必要的数据源和保留一些点击回调 16 | - 为什么用widget组合方式构造页面:非常复杂的界面,必须将页面分成一个个小模块,然后再将其组合,每个小模块Widget内部应当对自身的的职能,能逻辑自洽的处理 17 | - 组合widget关键点:一般来说,我们并不关注widget内部页面的实现,只需要关心的是widget需要的数据源,以及widget对交互的反馈;例如:我点击widget后,widget回调事件,并传达一些数据给我;至于内部怎么实现,外部并不关心,请勿将dispatch传递到封装的widget内部,这会使我们关注的事件被封装在内部 18 | 19 | ## 第一版 20 | 21 | - [x] ~~首页banner展示,首页文章分页加载,完成下拉刷新,上拉加载~~ 22 | - [x] ~~完成知识体系和体系详情模块~~ 23 | - [x] ~~完成导航模块~~ 24 | - [x] ~~完成项目模块~~ 25 | - [x] ~~侧栏布局完成,功能留坑,方便添加~~ 26 | 27 | 28 | 29 | ## 第二版 30 | 31 | - [x] ~~搜索功能~~ 32 | - [x] ~~热搜词~~ 33 | - [x] ~~收藏,查看收藏功能~~ 34 | - [x] ~~登录功能(这个实际就是登拿个参数,去请求收藏的信息等)~~ 35 | - [x] ~~用户注册~~ 36 | - [x] ~~侧栏的一列功能填坑~~ 37 | 38 | 39 | ## 打包操作 40 | 41 | - android(关闭R8压缩) 42 | - flutter build apk --no-shrink 43 | 44 | - ios(生成 .app ,然后 在 XCode 执行 打包 操作) 45 | - flutter build ios --release --target=lib/app/main_dev.dart 46 | 47 | 48 | ## APP安装地址 49 | 50 | - android 51 | 52 | ![](https://cdn.jsdelivr.net/gh/CNAD666/MyData/pic/flutter/scan/20201111192005.png) 53 | 54 | - ios 55 | 56 | - 下载项目,直接跑吧 57 | 58 | ## app效果 59 | 60 | ![玩android演示](https://cdn.jsdelivr.net/gh/CNAD666/MyData/pic/flutter/project/20201107214332.gif) 61 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /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.0' 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 | //处理打包 29 | def keystoreProperties = new Properties() 30 | keystoreProperties.load(new FileInputStream(rootProject.file("key.properties"))) 31 | 32 | android { 33 | compileSdkVersion 28 34 | 35 | sourceSets { 36 | main.java.srcDirs += 'src/main/kotlin' 37 | } 38 | 39 | lintOptions { 40 | disable 'InvalidPackage' 41 | } 42 | 43 | defaultConfig { 44 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 45 | applicationId "com.tao.flutter_wan" 46 | minSdkVersion 17 47 | targetSdkVersion 28 48 | versionCode flutterVersionCode.toInteger() 49 | versionName flutterVersionName 50 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 51 | 52 | ndk { 53 | //设置支持的SO库架构 54 | abiFilters 'arm64-v8a'//, 'x86', 'armeabi-v7a', 'x86_64' 55 | } 56 | } 57 | 58 | signingConfigs { 59 | release { 60 | keyAlias keystoreProperties['keyAlias'] 61 | keyPassword keystoreProperties['keyPassword'] 62 | storeFile file(keystoreProperties['storeFile']) 63 | storePassword keystoreProperties['storePassword'] 64 | } 65 | } 66 | 67 | buildTypes { 68 | release { 69 | // TODO: Add your own signing config for the release build. 70 | // Signing with the debug keys for now, so `flutter run --release` works. 71 | signingConfig signingConfigs.debug 72 | } 73 | } 74 | } 75 | 76 | flutter { 77 | source '../..' 78 | } 79 | 80 | dependencies { 81 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 82 | testImplementation 'junit:junit:4.12' 83 | androidTestImplementation 'androidx.test:runner:1.1.1' 84 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 85 | } 86 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 28 | 29 | 30 | 31 | 32 | 33 | 35 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/tao/flutter_wan/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.tao.flutter_wan 2 | 3 | import androidx.annotation.NonNull; 4 | import io.flutter.embedding.android.FlutterActivity 5 | import io.flutter.embedding.engine.FlutterEngine 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { 10 | GeneratedPluginRegistrant.registerWith(flutterEngine); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.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 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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:3.6.1' 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 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Mar 22 15:08:17 CST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip 7 | -------------------------------------------------------------------------------- /android/key.properties: -------------------------------------------------------------------------------- 1 | storePassword= 1q2w3e4r 2 | keyPassword= 1q2w3e4r 3 | keyAlias= key 4 | storeFile= ../key_store.jks -------------------------------------------------------------------------------- /android/key_store.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/android/key_store.jks -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /images/ttxs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/images/ttxs.jpg -------------------------------------------------------------------------------- /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/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /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.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/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/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/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/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/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/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/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/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/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/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/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/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/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/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/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/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/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/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/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/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/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/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/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/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/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/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/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/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/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/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/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/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 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | CFBundleDevelopmentRegion 11 | $(DEVELOPMENT_LANGUAGE) 12 | CFBundleExecutable 13 | $(EXECUTABLE_NAME) 14 | CFBundleIdentifier 15 | $(PRODUCT_BUNDLE_IDENTIFIER) 16 | CFBundleInfoDictionaryVersion 17 | 6.0 18 | CFBundleName 19 | flutter_wan 20 | CFBundlePackageType 21 | APPL 22 | CFBundleShortVersionString 23 | $(FLUTTER_BUILD_NAME) 24 | CFBundleSignature 25 | ???? 26 | CFBundleVersion 27 | $(FLUTTER_BUILD_NUMBER) 28 | LSRequiresIPhoneOS 29 | 30 | UILaunchStoryboardName 31 | LaunchScreen 32 | UIMainStoryboardFile 33 | Main 34 | UISupportedInterfaceOrientations 35 | 36 | UIInterfaceOrientationPortrait 37 | UIInterfaceOrientationLandscapeLeft 38 | UIInterfaceOrientationLandscapeRight 39 | 40 | UISupportedInterfaceOrientations~ipad 41 | 42 | UIInterfaceOrientationPortrait 43 | UIInterfaceOrientationPortraitUpsideDown 44 | UIInterfaceOrientationLandscapeLeft 45 | UIInterfaceOrientationLandscapeRight 46 | 47 | UIViewControllerBasedStatusBarAppearance 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /lib/app/config/route.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter_wan/module/home/page.dart'; 3 | import 'package:flutter_wan/module/main/collect/page.dart'; 4 | import 'package:flutter_wan/module/main/login/page.dart'; 5 | import 'package:flutter_wan/module/main/page.dart'; 6 | import 'package:flutter_wan/module/main/register/page.dart'; 7 | import 'package:flutter_wan/module/main/search/page.dart'; 8 | import 'package:flutter_wan/module/navi/page.dart'; 9 | import 'package:flutter_wan/module/project/page.dart'; 10 | import 'package:flutter_wan/module/tree/page.dart'; 11 | import 'package:flutter_wan/module/tree/tree_detail/page.dart'; 12 | import 'package:flutter_wan/module/web/page.dart'; 13 | 14 | ///路由管理 15 | class RouteConfig { 16 | ///定义你的路由名称比如 static final String routeHome = 'page/home'; 17 | ///主模块 18 | static const String mainPage = 'page/main'; 19 | 20 | ///登录 21 | static const String loginPage = 'page/main/login'; 22 | 23 | ///注册 24 | static const String registerPage = 'page/main/login/register'; 25 | 26 | ///新闻主页面 27 | static const String homePage = 'page/home'; 28 | 29 | ///知识体系主页面 30 | static const String treePage = 'page/tree'; 31 | 32 | ///知识体系细节 33 | static const String treeDetailPage = 'page/treeDetail'; 34 | 35 | ///我的模块主页面 36 | static const String naviPage = 'page/navi'; 37 | 38 | ///项目模块 39 | static const String projectPage = 'page/project'; 40 | 41 | ///展示文章内容,统一的容器 42 | static const String webViewPage = 'page/webview'; 43 | 44 | ///收藏主页 45 | static const String collectPage = 'page/main/collect'; 46 | 47 | ///搜索页面 48 | static const String searchPage = 'page/main/search'; 49 | 50 | ///将你的路由名称和页面映射在一起,比如:RouteConfig.homePage: HomePage(), 51 | static final AbstractRoutes routes = PageRoutes( 52 | pages: >{ 53 | RouteConfig.mainPage: MainPage(), 54 | RouteConfig.homePage: HomePage(), 55 | RouteConfig.treePage: TreePage(), 56 | RouteConfig.treeDetailPage: TreeDetailPage(), 57 | RouteConfig.naviPage: NaviPage(), 58 | RouteConfig.projectPage: ProjectPage(), 59 | RouteConfig.webViewPage: WebViewPage(), 60 | RouteConfig.loginPage: LoginPage(), 61 | RouteConfig.registerPage: RegisterPage(), 62 | RouteConfig.collectPage: CollectPage(), 63 | RouteConfig.searchPage: SearchPage(), 64 | }, 65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /lib/app/init.dart: -------------------------------------------------------------------------------- 1 | //初始化一些必须的操作 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter_wan/app/utils/cache/sp_util.dart'; 4 | 5 | void initApp(BuildContext context) { 6 | //初始化本地缓存 7 | SpUtil.init(); 8 | } 9 | -------------------------------------------------------------------------------- /lib/app/typedef/function.dart: -------------------------------------------------------------------------------- 1 | ///无参数请求回调 2 | typedef ParamVoidCallback = dynamic Function(); 3 | 4 | ///回调一个参数 5 | typedef ParamSingleCallback = dynamic Function(D data); 6 | 7 | ///回到俩个参数 8 | typedef ParamTwiceCallback = dynamic Function(O dataOne, T dataTwo); 9 | 10 | ///回调三个参数 11 | typedef ParamThreeCallback = dynamic Function( 12 | O dataOne, T dataTwo, K threeData); -------------------------------------------------------------------------------- /lib/app/utils/cache/sp_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:shared_preferences/shared_preferences.dart'; 2 | 3 | class SpUtil { 4 | static SharedPreferences _instance; 5 | 6 | ///使用前必须初始化 7 | static void init() async { 8 | _instance = await SharedPreferences.getInstance(); 9 | } 10 | 11 | /// 保存数据 12 | static void put(String key, T value) async{ 13 | SharedPreferences instance = await SharedPreferences.getInstance(); 14 | 15 | switch (T) { 16 | case String: 17 | instance.setString(key, value as String); 18 | break; 19 | case int: 20 | instance.setInt(key, value as int); 21 | break; 22 | case bool: 23 | instance.setBool(key, value as bool); 24 | break; 25 | case double: 26 | instance.setDouble(key, value as double); 27 | break; 28 | } 29 | } 30 | 31 | /// 读取数据 32 | static Future get(String key) async { 33 | SharedPreferences instance = await SharedPreferences.getInstance(); 34 | 35 | T res; 36 | switch (T) { 37 | case String: 38 | res = instance.getString(key) as T; 39 | break; 40 | case int: 41 | res = instance.getInt(key) as T; 42 | break; 43 | case bool: 44 | res = instance.getBool(key) as T; 45 | break; 46 | case double: 47 | res = instance.getDouble(key) as T; 48 | break; 49 | } 50 | return res; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/app/utils/show/toast_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_wan/app/utils/ui/ui_adapter.dart'; 3 | import 'package:fluttertoast/fluttertoast.dart'; 4 | 5 | void showToast(msg) { 6 | Fluttertoast.showToast( 7 | msg: msg, 8 | toastLength: Toast.LENGTH_SHORT, 9 | gravity: ToastGravity.CENTER, 10 | timeInSecForIosWeb: 1, 11 | backgroundColor: Colors.grey, 12 | textColor: Colors.white, 13 | fontSize: setSp(32), 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /lib/app/utils/tool/string_util.dart: -------------------------------------------------------------------------------- 1 | class StringUtil { 2 | ///判断是否为空 3 | static bool isEmpty(String s) { 4 | if (s == null || s == '') { 5 | return true; 6 | } 7 | 8 | return false; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/app/utils/ui/future.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | -------------------------------------------------------------------------------- /lib/app/utils/ui/keep_alive_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | //页面保活方法 4 | Widget keepAlivePage(Widget child) => AliveWidget(child: child); 5 | 6 | class AliveWidget extends StatefulWidget { 7 | final Widget child; 8 | 9 | AliveWidget({this.child}); 10 | 11 | @override 12 | State createState() { 13 | return _KeepAliveState(); 14 | } 15 | } 16 | 17 | class _KeepAliveState extends State 18 | with AutomaticKeepAliveClientMixin { 19 | @override 20 | bool get wantKeepAlive => true; 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | super.build(context); 25 | return widget.child; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/app/utils/ui/ui_adapter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 5 | 6 | //按照宽度适配 7 | double setWidth(double size) { 8 | return ScreenUtil().setWidth(size); 9 | } 10 | 11 | //按照高度适配 12 | double setHeight(double size) { 13 | return ScreenUtil().setHeight(size); 14 | } 15 | 16 | //适配文字 17 | double setSp(double size) { 18 | if (isMobile()) { 19 | return ScreenUtil().setSp(size); 20 | } else { 21 | return size.toDouble(); 22 | } 23 | } 24 | 25 | //自动适配,方便调整 26 | double auto(double size) { 27 | if (isMobile()) { 28 | return ScreenUtil().setWidth(size); 29 | } else { 30 | return size.toDouble(); 31 | } 32 | } 33 | 34 | //界面适配 35 | void initUiAdapter(BuildContext context) { 36 | //填入设计稿中设备的屏幕尺寸 37 | //默认 width : 1080px , height:1920px , allowFontScaling:false 38 | // ScreenUtil.init(context); 39 | //假如设计稿是按iPhone6的尺寸设计的(iPhone6 750*1334) 40 | // ScreenUtil.init(context, width: 750, height: 1334); 41 | //设置字体大小根据系统的“字体大小”辅助选项来进行缩放,默认为false 42 | // ScreenUtil.init(context, width: 750, height: 1334, allowFontScaling: true); 43 | if (!isMobile()) { 44 | return; 45 | } 46 | ScreenUtil.init(context, width: 750, height: 1334, allowFontScaling: true); 47 | } 48 | 49 | 50 | //判断是否是移动端平台 51 | bool isMobile() { 52 | if (Platform.isAndroid || Platform.isIOS) { 53 | return true; 54 | } else { 55 | return false; 56 | } 57 | } 58 | 59 | 60 | extension dimensionsNum on num { 61 | ///自动适配移动界面 62 | num get dp => auto(this); 63 | 64 | ///配置文字,文字适配请用sp单位 65 | num get sp => setSp(this); 66 | } 67 | -------------------------------------------------------------------------------- /lib/app/utils/ui/view_util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | 5 | class ViewUtil { 6 | ///界面初始化完成好的 7 | static Future initFinish() async { 8 | Completer completer = new Completer(); 9 | 10 | WidgetsBinding.instance.addPostFrameCallback((timeStamp) { 11 | completer.complete(); 12 | }); 13 | 14 | return completer.future; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/bean/README.md: -------------------------------------------------------------------------------- 1 | ## 实体命名生成 2 | json快捷生成bean文件网址:[https://caijinglong.github.io/json2dart/index_ch.html](https://caijinglong.github.io/json2dart/index_ch.html) 3 | 4 | ## 生成实体附属文件命令 5 | 6 | ### 使用 build_runner 生成 .g.dart 文件 7 | flutter packages pub run build_runner build 8 | 9 | ### 监控生成文件,如果有改动时自动生成/更新 .g.dart 文件 10 | flutter packages pub run build_runner watch 11 | 12 | ### 删除并重新创建.g.dart文件 13 | flutter packages pub run build_runner build --delete-conflicting-outputs 14 | 15 | 16 | -------------------------------------------------------------------------------- /lib/bean/common/article_detail_bean.dart: -------------------------------------------------------------------------------- 1 | class ArticleDetailBean { 2 | ArticleDetailBean({ 3 | this.title, 4 | this.url, 5 | this.id, 6 | this.isCollect, 7 | }); 8 | 9 | ///文章id 10 | int id; 11 | 12 | ///标题 13 | String title; 14 | 15 | ///文章链接 16 | String url; 17 | 18 | ///是否被收藏 19 | bool isCollect; 20 | } 21 | -------------------------------------------------------------------------------- /lib/bean/common/btn_info.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | ///按钮信息 4 | class BtnInfo { 5 | BtnInfo({ 6 | this.title, 7 | this.tag, 8 | this.icon, 9 | }); 10 | 11 | ///按钮名称 12 | String title; 13 | 14 | ///按钮标识 15 | String tag; 16 | 17 | ///正常情况图标 18 | Icon icon; 19 | 20 | 21 | /// jsonDecode(jsonStr) 方法中会调用实体类的这个方法。如果实体类中没有这个方法,会报错。 22 | Map toJson() { 23 | Map map = Map(); 24 | map["title"] = this.title; 25 | map["tag"] = this.tag; 26 | map["icon"] = this.icon; 27 | return map; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/bean/home/banner_bean.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'banner_bean.g.dart'; 4 | 5 | @JsonSerializable() 6 | class BannerBean extends Object { 7 | @JsonKey(name: 'data') 8 | List data; 9 | 10 | @JsonKey(name: 'errorCode') 11 | int errorCode; 12 | 13 | @JsonKey(name: 'errorMsg') 14 | String errorMsg; 15 | 16 | BannerBean( 17 | this.data, 18 | this.errorCode, 19 | this.errorMsg, 20 | ); 21 | 22 | factory BannerBean.fromJson(Map srcJson) => 23 | _$BannerBeanFromJson(srcJson); 24 | 25 | Map toJson() => _$BannerBeanToJson(this); 26 | } 27 | 28 | @JsonSerializable() 29 | class Data extends Object { 30 | @JsonKey(name: 'desc') 31 | String desc; 32 | 33 | @JsonKey(name: 'id') 34 | int id; 35 | 36 | @JsonKey(name: 'imagePath') 37 | String imagePath; 38 | 39 | @JsonKey(name: 'isVisible') 40 | int isVisible; 41 | 42 | @JsonKey(name: 'order') 43 | int order; 44 | 45 | @JsonKey(name: 'title') 46 | String title; 47 | 48 | @JsonKey(name: 'type') 49 | int type; 50 | 51 | @JsonKey(name: 'url') 52 | String url; 53 | 54 | Data( 55 | this.desc, 56 | this.id, 57 | this.imagePath, 58 | this.isVisible, 59 | this.order, 60 | this.title, 61 | this.type, 62 | this.url, 63 | ); 64 | 65 | factory Data.fromJson(Map srcJson) => 66 | _$DataFromJson(srcJson); 67 | 68 | Map toJson() => _$DataToJson(this); 69 | } 70 | -------------------------------------------------------------------------------- /lib/bean/home/banner_bean.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'banner_bean.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | BannerBean _$BannerBeanFromJson(Map json) { 10 | return BannerBean( 11 | (json['data'] as List) 12 | ?.map( 13 | (e) => e == null ? null : Data.fromJson(e as Map)) 14 | ?.toList(), 15 | json['errorCode'] as int, 16 | json['errorMsg'] as String, 17 | ); 18 | } 19 | 20 | Map _$BannerBeanToJson(BannerBean instance) => 21 | { 22 | 'data': instance.data, 23 | 'errorCode': instance.errorCode, 24 | 'errorMsg': instance.errorMsg, 25 | }; 26 | 27 | Data _$DataFromJson(Map json) { 28 | return Data( 29 | json['desc'] as String, 30 | json['id'] as int, 31 | json['imagePath'] as String, 32 | json['isVisible'] as int, 33 | json['order'] as int, 34 | json['title'] as String, 35 | json['type'] as int, 36 | json['url'] as String, 37 | ); 38 | } 39 | 40 | Map _$DataToJson(Data instance) => { 41 | 'desc': instance.desc, 42 | 'id': instance.id, 43 | 'imagePath': instance.imagePath, 44 | 'isVisible': instance.isVisible, 45 | 'order': instance.order, 46 | 'title': instance.title, 47 | 'type': instance.type, 48 | 'url': instance.url, 49 | }; 50 | -------------------------------------------------------------------------------- /lib/bean/project/project_bean.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'project_bean.g.dart'; 4 | 5 | 6 | @JsonSerializable() 7 | class ProjectBean extends Object { 8 | 9 | @JsonKey(name: 'data') 10 | List data; 11 | 12 | @JsonKey(name: 'errorCode') 13 | int errorCode; 14 | 15 | @JsonKey(name: 'errorMsg') 16 | String errorMsg; 17 | 18 | ProjectBean(this.data,this.errorCode,this.errorMsg,); 19 | 20 | factory ProjectBean.fromJson(Map srcJson) => _$ProjectBeanFromJson(srcJson); 21 | 22 | Map toJson() => _$ProjectBeanToJson(this); 23 | 24 | } 25 | 26 | 27 | @JsonSerializable() 28 | class Data extends Object { 29 | 30 | @JsonKey(name: 'children') 31 | List children; 32 | 33 | @JsonKey(name: 'courseId') 34 | int courseId; 35 | 36 | @JsonKey(name: 'id') 37 | int id; 38 | 39 | @JsonKey(name: 'name') 40 | String name; 41 | 42 | @JsonKey(name: 'order') 43 | int order; 44 | 45 | @JsonKey(name: 'parentChapterId') 46 | int parentChapterId; 47 | 48 | @JsonKey(name: 'userControlSetTop') 49 | bool userControlSetTop; 50 | 51 | @JsonKey(name: 'visible') 52 | int visible; 53 | 54 | Data(this.children,this.courseId,this.id,this.name,this.order,this.parentChapterId,this.userControlSetTop,this.visible,); 55 | 56 | factory Data.fromJson(Map srcJson) => _$DataFromJson(srcJson); 57 | 58 | Map toJson() => _$DataToJson(this); 59 | 60 | } 61 | 62 | 63 | -------------------------------------------------------------------------------- /lib/bean/project/project_bean.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'project_bean.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | ProjectBean _$ProjectBeanFromJson(Map json) { 10 | return ProjectBean( 11 | (json['data'] as List) 12 | ?.map( 13 | (e) => e == null ? null : Data.fromJson(e as Map)) 14 | ?.toList(), 15 | json['errorCode'] as int, 16 | json['errorMsg'] as String, 17 | ); 18 | } 19 | 20 | Map _$ProjectBeanToJson(ProjectBean instance) => 21 | { 22 | 'data': instance.data, 23 | 'errorCode': instance.errorCode, 24 | 'errorMsg': instance.errorMsg, 25 | }; 26 | 27 | Data _$DataFromJson(Map json) { 28 | return Data( 29 | json['children'] as List, 30 | json['courseId'] as int, 31 | json['id'] as int, 32 | json['name'] as String, 33 | json['order'] as int, 34 | json['parentChapterId'] as int, 35 | json['userControlSetTop'] as bool, 36 | json['visible'] as int, 37 | ); 38 | } 39 | 40 | Map _$DataToJson(Data instance) => { 41 | 'children': instance.children, 42 | 'courseId': instance.courseId, 43 | 'id': instance.id, 44 | 'name': instance.name, 45 | 'order': instance.order, 46 | 'parentChapterId': instance.parentChapterId, 47 | 'userControlSetTop': instance.userControlSetTop, 48 | 'visible': instance.visible, 49 | }; 50 | -------------------------------------------------------------------------------- /lib/bean/tree/tree_info_bean.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'tree_info_bean.g.dart'; 4 | 5 | @JsonSerializable() 6 | class TreeInfoBean extends Object { 7 | @JsonKey(name: 'data') 8 | List data; 9 | 10 | @JsonKey(name: 'errorCode') 11 | int errorCode; 12 | 13 | @JsonKey(name: 'errorMsg') 14 | String errorMsg; 15 | 16 | TreeInfoBean( 17 | this.data, 18 | this.errorCode, 19 | this.errorMsg, 20 | ); 21 | 22 | factory TreeInfoBean.fromJson(Map srcJson) => 23 | _$TreeInfoBeanFromJson(srcJson); 24 | 25 | Map toJson() => _$TreeInfoBeanToJson(this); 26 | } 27 | 28 | @JsonSerializable() 29 | class Data extends Object { 30 | @JsonKey(name: 'children') 31 | List children; 32 | 33 | @JsonKey(name: 'courseId') 34 | int courseId; 35 | 36 | @JsonKey(name: 'id') 37 | int id; 38 | 39 | @JsonKey(name: 'name') 40 | String name; 41 | 42 | @JsonKey(name: 'order') 43 | int order; 44 | 45 | @JsonKey(name: 'parentChapterId') 46 | int parentChapterId; 47 | 48 | @JsonKey(name: 'userControlSetTop') 49 | bool userControlSetTop; 50 | 51 | @JsonKey(name: 'visible') 52 | int visible; 53 | 54 | Data( 55 | this.children, 56 | this.courseId, 57 | this.id, 58 | this.name, 59 | this.order, 60 | this.parentChapterId, 61 | this.userControlSetTop, 62 | this.visible, 63 | ); 64 | 65 | factory Data.fromJson(Map srcJson) => 66 | _$DataFromJson(srcJson); 67 | 68 | Map toJson() => _$DataToJson(this); 69 | } 70 | 71 | @JsonSerializable() 72 | class Children extends Object { 73 | @JsonKey(name: 'children') 74 | List children; 75 | 76 | @JsonKey(name: 'courseId') 77 | int courseId; 78 | 79 | @JsonKey(name: 'id') 80 | int id; 81 | 82 | @JsonKey(name: 'name') 83 | String name; 84 | 85 | @JsonKey(name: 'order') 86 | int order; 87 | 88 | @JsonKey(name: 'parentChapterId') 89 | int parentChapterId; 90 | 91 | @JsonKey(name: 'userControlSetTop') 92 | bool userControlSetTop; 93 | 94 | @JsonKey(name: 'visible') 95 | int visible; 96 | 97 | Children( 98 | this.children, 99 | this.courseId, 100 | this.id, 101 | this.name, 102 | this.order, 103 | this.parentChapterId, 104 | this.userControlSetTop, 105 | this.visible, 106 | ); 107 | 108 | factory Children.fromJson(Map srcJson) => 109 | _$ChildrenFromJson(srcJson); 110 | 111 | Map toJson() => _$ChildrenToJson(this); 112 | } 113 | -------------------------------------------------------------------------------- /lib/bean/tree/tree_info_bean.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'tree_info_bean.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | TreeInfoBean _$TreeInfoBeanFromJson(Map json) { 10 | return TreeInfoBean( 11 | (json['data'] as List) 12 | ?.map( 13 | (e) => e == null ? null : Data.fromJson(e as Map)) 14 | ?.toList(), 15 | json['errorCode'] as int, 16 | json['errorMsg'] as String, 17 | ); 18 | } 19 | 20 | Map _$TreeInfoBeanToJson(TreeInfoBean instance) => 21 | { 22 | 'data': instance.data, 23 | 'errorCode': instance.errorCode, 24 | 'errorMsg': instance.errorMsg, 25 | }; 26 | 27 | Data _$DataFromJson(Map json) { 28 | return Data( 29 | (json['children'] as List) 30 | ?.map((e) => 31 | e == null ? null : Children.fromJson(e as Map)) 32 | ?.toList(), 33 | json['courseId'] as int, 34 | json['id'] as int, 35 | json['name'] as String, 36 | json['order'] as int, 37 | json['parentChapterId'] as int, 38 | json['userControlSetTop'] as bool, 39 | json['visible'] as int, 40 | ); 41 | } 42 | 43 | Map _$DataToJson(Data instance) => { 44 | 'children': instance.children, 45 | 'courseId': instance.courseId, 46 | 'id': instance.id, 47 | 'name': instance.name, 48 | 'order': instance.order, 49 | 'parentChapterId': instance.parentChapterId, 50 | 'userControlSetTop': instance.userControlSetTop, 51 | 'visible': instance.visible, 52 | }; 53 | 54 | Children _$ChildrenFromJson(Map json) { 55 | return Children( 56 | json['children'] as List, 57 | json['courseId'] as int, 58 | json['id'] as int, 59 | json['name'] as String, 60 | json['order'] as int, 61 | json['parentChapterId'] as int, 62 | json['userControlSetTop'] as bool, 63 | json['visible'] as int, 64 | ); 65 | } 66 | 67 | Map _$ChildrenToJson(Children instance) => { 68 | 'children': instance.children, 69 | 'courseId': instance.courseId, 70 | 'id': instance.id, 71 | 'name': instance.name, 72 | 'order': instance.order, 73 | 'parentChapterId': instance.parentChapterId, 74 | 'userControlSetTop': instance.userControlSetTop, 75 | 'visible': instance.visible, 76 | }; 77 | -------------------------------------------------------------------------------- /lib/bean/user/collect_list_bean.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'collect_list_bean.g.dart'; 4 | 5 | 6 | @JsonSerializable() 7 | class CollectListBean extends Object { 8 | 9 | @JsonKey(name: 'data') 10 | Data data; 11 | 12 | @JsonKey(name: 'errorCode') 13 | int errorCode; 14 | 15 | @JsonKey(name: 'errorMsg') 16 | String errorMsg; 17 | 18 | CollectListBean(this.data,this.errorCode,this.errorMsg,); 19 | 20 | factory CollectListBean.fromJson(Map srcJson) => _$CollectListBeanFromJson(srcJson); 21 | 22 | Map toJson() => _$CollectListBeanToJson(this); 23 | 24 | } 25 | 26 | 27 | @JsonSerializable() 28 | class Data extends Object { 29 | 30 | @JsonKey(name: 'curPage') 31 | int curPage; 32 | 33 | @JsonKey(name: 'datas') 34 | List datas; 35 | 36 | @JsonKey(name: 'offset') 37 | int offset; 38 | 39 | @JsonKey(name: 'over') 40 | bool over; 41 | 42 | @JsonKey(name: 'pageCount') 43 | int pageCount; 44 | 45 | @JsonKey(name: 'size') 46 | int size; 47 | 48 | @JsonKey(name: 'total') 49 | int total; 50 | 51 | Data(this.curPage,this.datas,this.offset,this.over,this.pageCount,this.size,this.total,); 52 | 53 | factory Data.fromJson(Map srcJson) => _$DataFromJson(srcJson); 54 | 55 | Map toJson() => _$DataToJson(this); 56 | 57 | } 58 | 59 | 60 | @JsonSerializable() 61 | class Datas extends Object { 62 | 63 | @JsonKey(name: 'author') 64 | String author; 65 | 66 | @JsonKey(name: 'chapterId') 67 | int chapterId; 68 | 69 | @JsonKey(name: 'chapterName') 70 | String chapterName; 71 | 72 | @JsonKey(name: 'courseId') 73 | int courseId; 74 | 75 | @JsonKey(name: 'desc') 76 | String desc; 77 | 78 | @JsonKey(name: 'envelopePic') 79 | String envelopePic; 80 | 81 | @JsonKey(name: 'id') 82 | int id; 83 | 84 | @JsonKey(name: 'link') 85 | String link; 86 | 87 | @JsonKey(name: 'niceDate') 88 | String niceDate; 89 | 90 | @JsonKey(name: 'origin') 91 | String origin; 92 | 93 | @JsonKey(name: 'originId') 94 | int originId; 95 | 96 | @JsonKey(name: 'publishTime') 97 | int publishTime; 98 | 99 | @JsonKey(name: 'title') 100 | String title; 101 | 102 | @JsonKey(name: 'userId') 103 | int userId; 104 | 105 | @JsonKey(name: 'visible') 106 | int visible; 107 | 108 | @JsonKey(name: 'zan') 109 | int zan; 110 | 111 | Datas(this.author,this.chapterId,this.chapterName,this.courseId,this.desc,this.envelopePic,this.id,this.link,this.niceDate,this.origin,this.originId,this.publishTime,this.title,this.userId,this.visible,this.zan,); 112 | 113 | factory Datas.fromJson(Map srcJson) => _$DatasFromJson(srcJson); 114 | 115 | Map toJson() => _$DatasToJson(this); 116 | 117 | } 118 | 119 | 120 | -------------------------------------------------------------------------------- /lib/bean/user/common_response.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'common_response.g.dart'; 4 | 5 | 6 | @JsonSerializable() 7 | class CommonResponse extends Object { 8 | 9 | @JsonKey(name: 'data') 10 | String data; 11 | 12 | @JsonKey(name: 'errorCode') 13 | int errorCode; 14 | 15 | @JsonKey(name: 'errorMsg') 16 | String errorMsg; 17 | 18 | CommonResponse(this.data,this.errorCode,this.errorMsg,); 19 | 20 | factory CommonResponse.fromJson(Map srcJson) => _$CommonResponseFromJson(srcJson); 21 | 22 | Map toJson() => _$CommonResponseToJson(this); 23 | 24 | } 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/bean/user/common_response.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'common_response.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | CommonResponse _$CommonResponseFromJson(Map json) { 10 | return CommonResponse( 11 | json['data'] as String, 12 | json['errorCode'] as int, 13 | json['errorMsg'] as String, 14 | ); 15 | } 16 | 17 | Map _$CommonResponseToJson(CommonResponse instance) => 18 | { 19 | 'data': instance.data, 20 | 'errorCode': instance.errorCode, 21 | 'errorMsg': instance.errorMsg, 22 | }; 23 | -------------------------------------------------------------------------------- /lib/bean/user/hot_word_bean.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'hot_word_bean.g.dart'; 4 | 5 | 6 | @JsonSerializable() 7 | class HotWordBean extends Object { 8 | 9 | @JsonKey(name: 'data') 10 | List data; 11 | 12 | @JsonKey(name: 'errorCode') 13 | int errorCode; 14 | 15 | @JsonKey(name: 'errorMsg') 16 | String errorMsg; 17 | 18 | HotWordBean(this.data,this.errorCode,this.errorMsg,); 19 | 20 | factory HotWordBean.fromJson(Map srcJson) => _$HotWordBeanFromJson(srcJson); 21 | 22 | Map toJson() => _$HotWordBeanToJson(this); 23 | 24 | } 25 | 26 | 27 | @JsonSerializable() 28 | class Data extends Object { 29 | 30 | @JsonKey(name: 'id') 31 | int id; 32 | 33 | @JsonKey(name: 'link') 34 | String link; 35 | 36 | @JsonKey(name: 'name') 37 | String name; 38 | 39 | @JsonKey(name: 'order') 40 | int order; 41 | 42 | @JsonKey(name: 'visible') 43 | int visible; 44 | 45 | Data(this.id,this.link,this.name,this.order,this.visible,); 46 | 47 | factory Data.fromJson(Map srcJson) => _$DataFromJson(srcJson); 48 | 49 | Map toJson() => _$DataToJson(this); 50 | 51 | } 52 | 53 | 54 | -------------------------------------------------------------------------------- /lib/bean/user/hot_word_bean.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'hot_word_bean.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | HotWordBean _$HotWordBeanFromJson(Map json) { 10 | return HotWordBean( 11 | (json['data'] as List) 12 | ?.map( 13 | (e) => e == null ? null : Data.fromJson(e as Map)) 14 | ?.toList(), 15 | json['errorCode'] as int, 16 | json['errorMsg'] as String, 17 | ); 18 | } 19 | 20 | Map _$HotWordBeanToJson(HotWordBean instance) => 21 | { 22 | 'data': instance.data, 23 | 'errorCode': instance.errorCode, 24 | 'errorMsg': instance.errorMsg, 25 | }; 26 | 27 | Data _$DataFromJson(Map json) { 28 | return Data( 29 | json['id'] as int, 30 | json['link'] as String, 31 | json['name'] as String, 32 | json['order'] as int, 33 | json['visible'] as int, 34 | ); 35 | } 36 | 37 | Map _$DataToJson(Data instance) => { 38 | 'id': instance.id, 39 | 'link': instance.link, 40 | 'name': instance.name, 41 | 'order': instance.order, 42 | 'visible': instance.visible, 43 | }; 44 | -------------------------------------------------------------------------------- /lib/bean/user/login_info_bean.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'login_info_bean.g.dart'; 4 | 5 | @JsonSerializable() 6 | class LoginInfoBean extends Object { 7 | @JsonKey(name: 'data') 8 | Data data; 9 | 10 | @JsonKey(name: 'errorCode') 11 | int errorCode; 12 | 13 | @JsonKey(name: 'errorMsg') 14 | String errorMsg; 15 | 16 | LoginInfoBean( 17 | this.data, 18 | this.errorCode, 19 | this.errorMsg, 20 | ); 21 | 22 | factory LoginInfoBean.fromJson(Map srcJson) => 23 | _$LoginInfoBeanFromJson(srcJson); 24 | 25 | Map toJson() => _$LoginInfoBeanToJson(this); 26 | } 27 | 28 | @JsonSerializable() 29 | class Data extends Object { 30 | @JsonKey(name: 'admin') 31 | bool admin; 32 | 33 | @JsonKey(name: 'chapterTops') 34 | List chapterTops; 35 | 36 | @JsonKey(name: 'coinCount') 37 | int coinCount; 38 | 39 | @JsonKey(name: 'collectIds') 40 | List collectIds; 41 | 42 | @JsonKey(name: 'email') 43 | String email; 44 | 45 | @JsonKey(name: 'icon') 46 | String icon; 47 | 48 | @JsonKey(name: 'id') 49 | int id; 50 | 51 | @JsonKey(name: 'nickname') 52 | String nickname; 53 | 54 | @JsonKey(name: 'password') 55 | String password; 56 | 57 | @JsonKey(name: 'publicName') 58 | String publicName; 59 | 60 | @JsonKey(name: 'token') 61 | String token; 62 | 63 | @JsonKey(name: 'type') 64 | int type; 65 | 66 | @JsonKey(name: 'username') 67 | String username; 68 | 69 | Data( 70 | this.admin, 71 | this.chapterTops, 72 | this.coinCount, 73 | this.collectIds, 74 | this.email, 75 | this.icon, 76 | this.id, 77 | this.nickname, 78 | this.password, 79 | this.publicName, 80 | this.token, 81 | this.type, 82 | this.username, 83 | ); 84 | 85 | factory Data.fromJson(Map srcJson) => 86 | _$DataFromJson(srcJson); 87 | 88 | Map toJson() => _$DataToJson(this); 89 | } 90 | -------------------------------------------------------------------------------- /lib/bean/user/login_info_bean.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'login_info_bean.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | LoginInfoBean _$LoginInfoBeanFromJson(Map json) { 10 | return LoginInfoBean( 11 | json['data'] == null 12 | ? null 13 | : Data.fromJson(json['data'] as Map), 14 | json['errorCode'] as int, 15 | json['errorMsg'] as String, 16 | ); 17 | } 18 | 19 | Map _$LoginInfoBeanToJson(LoginInfoBean instance) => 20 | { 21 | 'data': instance.data, 22 | 'errorCode': instance.errorCode, 23 | 'errorMsg': instance.errorMsg, 24 | }; 25 | 26 | Data _$DataFromJson(Map json) { 27 | return Data( 28 | json['admin'] as bool, 29 | json['chapterTops'] as List, 30 | json['coinCount'] as int, 31 | json['collectIds'] as List, 32 | json['email'] as String, 33 | json['icon'] as String, 34 | json['id'] as int, 35 | json['nickname'] as String, 36 | json['password'] as String, 37 | json['publicName'] as String, 38 | json['token'] as String, 39 | json['type'] as int, 40 | json['username'] as String, 41 | ); 42 | } 43 | 44 | Map _$DataToJson(Data instance) => { 45 | 'admin': instance.admin, 46 | 'chapterTops': instance.chapterTops, 47 | 'coinCount': instance.coinCount, 48 | 'collectIds': instance.collectIds, 49 | 'email': instance.email, 50 | 'icon': instance.icon, 51 | 'id': instance.id, 52 | 'nickname': instance.nickname, 53 | 'password': instance.password, 54 | 'publicName': instance.publicName, 55 | 'token': instance.token, 56 | 'type': instance.type, 57 | 'username': instance.username, 58 | }; 59 | -------------------------------------------------------------------------------- /lib/bean/user/register_info_bean.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'register_info_bean.g.dart'; 4 | 5 | 6 | @JsonSerializable() 7 | class RegisterInfoBean extends Object { 8 | 9 | @JsonKey(name: 'data') 10 | Data data; 11 | 12 | @JsonKey(name: 'errorCode') 13 | int errorCode; 14 | 15 | @JsonKey(name: 'errorMsg') 16 | String errorMsg; 17 | 18 | RegisterInfoBean(this.data,this.errorCode,this.errorMsg,); 19 | 20 | factory RegisterInfoBean.fromJson(Map srcJson) => _$RegisterInfoBeanFromJson(srcJson); 21 | 22 | Map toJson() => _$RegisterInfoBeanToJson(this); 23 | 24 | } 25 | 26 | 27 | @JsonSerializable() 28 | class Data extends Object { 29 | 30 | @JsonKey(name: 'admin') 31 | bool admin; 32 | 33 | @JsonKey(name: 'chapterTops') 34 | List chapterTops; 35 | 36 | @JsonKey(name: 'coinCount') 37 | int coinCount; 38 | 39 | @JsonKey(name: 'collectIds') 40 | List collectIds; 41 | 42 | @JsonKey(name: 'email') 43 | String email; 44 | 45 | @JsonKey(name: 'icon') 46 | String icon; 47 | 48 | @JsonKey(name: 'id') 49 | int id; 50 | 51 | @JsonKey(name: 'nickname') 52 | String nickname; 53 | 54 | @JsonKey(name: 'password') 55 | String password; 56 | 57 | @JsonKey(name: 'publicName') 58 | String publicName; 59 | 60 | @JsonKey(name: 'token') 61 | String token; 62 | 63 | @JsonKey(name: 'type') 64 | int type; 65 | 66 | @JsonKey(name: 'username') 67 | String username; 68 | 69 | Data(this.admin,this.chapterTops,this.coinCount,this.collectIds,this.email,this.icon,this.id,this.nickname,this.password,this.publicName,this.token,this.type,this.username,); 70 | 71 | factory Data.fromJson(Map srcJson) => _$DataFromJson(srcJson); 72 | 73 | Map toJson() => _$DataToJson(this); 74 | 75 | } 76 | 77 | 78 | -------------------------------------------------------------------------------- /lib/bean/user/register_info_bean.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'register_info_bean.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | RegisterInfoBean _$RegisterInfoBeanFromJson(Map json) { 10 | return RegisterInfoBean( 11 | json['data'] == null 12 | ? null 13 | : Data.fromJson(json['data'] as Map), 14 | json['errorCode'] as int, 15 | json['errorMsg'] as String, 16 | ); 17 | } 18 | 19 | Map _$RegisterInfoBeanToJson(RegisterInfoBean instance) => 20 | { 21 | 'data': instance.data, 22 | 'errorCode': instance.errorCode, 23 | 'errorMsg': instance.errorMsg, 24 | }; 25 | 26 | Data _$DataFromJson(Map json) { 27 | return Data( 28 | json['admin'] as bool, 29 | json['chapterTops'] as List, 30 | json['coinCount'] as int, 31 | json['collectIds'] as List, 32 | json['email'] as String, 33 | json['icon'] as String, 34 | json['id'] as int, 35 | json['nickname'] as String, 36 | json['password'] as String, 37 | json['publicName'] as String, 38 | json['token'] as String, 39 | json['type'] as int, 40 | json['username'] as String, 41 | ); 42 | } 43 | 44 | Map _$DataToJson(Data instance) => { 45 | 'admin': instance.admin, 46 | 'chapterTops': instance.chapterTops, 47 | 'coinCount': instance.coinCount, 48 | 'collectIds': instance.collectIds, 49 | 'email': instance.email, 50 | 'icon': instance.icon, 51 | 'id': instance.id, 52 | 'nickname': instance.nickname, 53 | 'password': instance.password, 54 | 'publicName': instance.publicName, 55 | 'token': instance.token, 56 | 'type': instance.type, 57 | 'username': instance.username, 58 | }; 59 | -------------------------------------------------------------------------------- /lib/http/api.dart: -------------------------------------------------------------------------------- 1 | class ApiUrl { 2 | static const String _BASE_URL = "https://www.wanandroid.com"; 3 | 4 | ///获取轮播信息 首页Banner 5 | static const String GET_BANNER_URL = "$_BASE_URL/banner/json"; 6 | 7 | ///获取轮播信息 首页文章 8 | static const String GET_HOME_ARTICLE = "$_BASE_URL/article/list/"; 9 | 10 | /// 获取体系数据 11 | static const String GET_TREE = "$_BASE_URL/tree/json"; 12 | 13 | ///体系文章详情 14 | static const String GET_TREE_DETAIL = "$_BASE_URL/article/list/0/json"; 15 | 16 | ///获取导航数据 17 | static const String GET_NAVI_INFO = "$_BASE_URL/navi/json"; 18 | 19 | ///获取项目数据 20 | static const String GET_PROJECT_INFO = "$_BASE_URL/project/tree/json"; 21 | static const String GET_PROJECT_DETAIL = "$_BASE_URL/project/list/1/json"; 22 | 23 | ///登录 24 | static const String LOGIN = "$_BASE_URL/user/login"; 25 | 26 | ///注册 27 | static const String REGISTER = "$_BASE_URL/user/register"; 28 | 29 | ///收藏文章列表 30 | static const String COLLECT_ARTICLE_LIST = "$_BASE_URL/lg/collect/list/"; 31 | 32 | ///收藏文章 33 | static const String COLLECT = "$_BASE_URL/lg/collect/"; 34 | 35 | ///取消收藏文章 36 | static const String CANCEL_COLLECT = "$_BASE_URL/lg/uncollect_originId/"; 37 | 38 | ///搜索热词 39 | static const String SEARCH_HOT_WORD = "$_BASE_URL/hotkey/json"; 40 | 41 | ///搜索 42 | static const String SEARCH = "$_BASE_URL/article/query/"; 43 | } 44 | -------------------------------------------------------------------------------- /lib/http/http.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:flutter_wan/app/utils/cache/sp_util.dart'; 3 | 4 | ///获取dio设置 5 | Future getOptions() async { 6 | String userName = await SpUtil.get('userName'); 7 | String password = await SpUtil.get('password'); 8 | 9 | if (userName == null) { 10 | return null; 11 | } 12 | 13 | Map headers = Map(); 14 | String cookie ="loginUserName=$userName;loginUserPassword=$password"; 15 | headers['Cookie'] = cookie; 16 | Options options = Options(headers: headers); 17 | 18 | return options; 19 | } 20 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart' hide Page; 2 | import 'package:flutter/material.dart' hide Page; 3 | import 'package:flutter_localizations/flutter_localizations.dart'; 4 | import 'package:flutter_wan/app/init.dart'; 5 | 6 | import 'app/config/route.dart'; 7 | 8 | void main() { 9 | runApp(createApp()); 10 | } 11 | 12 | Widget createApp() { 13 | return MyApp(); 14 | } 15 | 16 | 17 | class _MyAppState extends State { 18 | @override 19 | void initState() { 20 | super.initState(); 21 | 22 | initApp(context); 23 | } 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return MaterialApp( 28 | title: '玩android', 29 | localizationsDelegates: [ 30 | //国际化 31 | GlobalMaterialLocalizations.delegate, 32 | GlobalWidgetsLocalizations.delegate, 33 | ], 34 | home: RouteConfig.routes.buildPage(RouteConfig.mainPage, null), //作为默认页面 35 | onGenerateRoute: (RouteSettings settings) { 36 | //ios页面切换风格 37 | return CupertinoPageRoute(builder: (BuildContext context) { 38 | return RouteConfig.routes.buildPage(settings.name, settings.arguments); 39 | }); 40 | }, 41 | ); 42 | } 43 | } 44 | 45 | class MyApp extends StatefulWidget { 46 | @override 47 | _MyAppState createState() => _MyAppState(); 48 | } 49 | -------------------------------------------------------------------------------- /lib/module/home/action.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | //TODO replace with your own action 4 | enum HomeAction { 5 | //切换banner数据 6 | switchBanner, 7 | //打开banner文章内容 8 | openBannerContent, 9 | //上拉加载 10 | onListLoad, 11 | //下拉刷新 12 | onListRefresh, 13 | //界面刷新 14 | onRefresh, 15 | } 16 | 17 | class HomeActionCreator { 18 | static Action onRefresh() { 19 | return Action(HomeAction.onRefresh); 20 | } 21 | 22 | static Action onListLoad() { 23 | return Action(HomeAction.onListLoad); 24 | } 25 | 26 | static Action onListRefresh() { 27 | return Action(HomeAction.onListRefresh); 28 | } 29 | 30 | static Action openBannerContent(int index) { 31 | return Action(HomeAction.openBannerContent, payload: index); 32 | } 33 | 34 | static Action switchBanner(int bannerImage) { 35 | return Action(HomeAction.switchBanner, payload: bannerImage); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/module/home/effect.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:dio/dio.dart'; 4 | import 'package:fish_redux/fish_redux.dart'; 5 | import 'package:flutter/material.dart' hide Action; 6 | import 'package:flutter_wan/app/config/route.dart'; 7 | import 'package:flutter_wan/bean/common/article_detail_bean.dart'; 8 | import 'package:flutter_wan/bean/home/banner_bean.dart'; 9 | import 'package:flutter_wan/http/api.dart'; 10 | 11 | import 'action.dart'; 12 | import 'state.dart'; 13 | 14 | Effect buildEffect() { 15 | return combineEffects(>{ 16 | //初始化操作 17 | Lifecycle.initState: _init, 18 | //打开文章 19 | HomeAction.openBannerContent: _openBannerContent, 20 | //切换banner 21 | HomeAction.switchBanner: _switchBanner, 22 | }); 23 | } 24 | 25 | void _switchBanner(Action action, Context ctx) { 26 | ctx.state.bannerIndex = action.payload; 27 | ctx.dispatch(HomeActionCreator.onRefresh()); 28 | } 29 | 30 | void _init(Action action, Context ctx) { 31 | _getBannerData(action, ctx); 32 | } 33 | 34 | //获取Banner数据 35 | void _getBannerData(Action action, Context ctx) async { 36 | try { 37 | Response response = await Dio().get(ApiUrl.GET_BANNER_URL); //获取banner数据 38 | BannerBean bannerBean = 39 | BannerBean.fromJson(json.decode(response.toString())); 40 | ctx.state.banners = bannerBean.data; 41 | ctx.state.bannerImages = _getImageList(ctx); 42 | 43 | ctx.dispatch(HomeActionCreator.onRefresh()); 44 | } catch (e) { 45 | println("获取首页banner数据失败: " + e.toString()); 46 | } 47 | } 48 | 49 | //获取Banner图片数据 50 | List _getImageList(Context ctx) { 51 | List imageList = List(); 52 | for (int i = 0; i < ctx.state.banners.length; i++) { 53 | imageList 54 | ..add(Image.network( 55 | ctx.state.banners[i].imagePath, 56 | fit: BoxFit.fill, 57 | )); 58 | } 59 | return imageList; 60 | } 61 | 62 | //打开banner文章内容 63 | void _openBannerContent(Action action, Context ctx) { 64 | int index = action.payload; 65 | var data = ctx.state.banners[index]; 66 | ArticleDetailBean articleDetailBean = ArticleDetailBean( 67 | url: data.url, 68 | title: data.title, 69 | id: data.id, 70 | isCollect: null, 71 | ); 72 | 73 | Navigator.pushNamed( 74 | ctx.context, 75 | RouteConfig.webViewPage, 76 | arguments: {"articleDetail": articleDetailBean}, 77 | ); 78 | } 79 | -------------------------------------------------------------------------------- /lib/module/home/page.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter_wan/view/component/article_list/component.dart'; 3 | 4 | import 'effect.dart'; 5 | import 'reducer.dart'; 6 | import 'state.dart'; 7 | import 'view.dart'; 8 | 9 | class HomePage extends Page> { 10 | HomePage() 11 | : super( 12 | initState: initState, 13 | effect: buildEffect(), 14 | reducer: buildReducer(), 15 | view: buildView, 16 | dependencies: Dependencies( 17 | adapter: null, 18 | slots: >{ 19 | "ArticleList": ArticleConnector() + ArticleListComponent(), 20 | }), 21 | middleware: >[], 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /lib/module/home/reducer.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | import 'action.dart'; 4 | import 'state.dart'; 5 | 6 | Reducer buildReducer() { 7 | return asReducer( 8 | >{ 9 | HomeAction.onRefresh: _onRefresh, 10 | }, 11 | ); 12 | } 13 | 14 | HomeState _onRefresh(HomeState state, Action action) { 15 | return state.clone(); 16 | } 17 | 18 | HomeState _onBannerIndex(HomeState state, Action action) { 19 | return state.clone() 20 | ..bannerIndex = action.payload; 21 | } -------------------------------------------------------------------------------- /lib/module/home/state.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_wan/bean/home/banner_bean.dart'; 4 | import 'package:flutter_wan/bean/home/home_article_bean.dart' hide Data; 5 | import 'package:flutter_wan/view/component/article_list/state.dart'; 6 | 7 | class HomeState implements Cloneable { 8 | //轮播所有数据 9 | List banners; 10 | 11 | //所有轮播图 12 | List bannerImages; 13 | 14 | //当前展示的轮播图下标 15 | int bannerIndex; 16 | 17 | //文章列表数据 18 | List mList; //总数据源 19 | 20 | //文章数据源 21 | ArticleListState subState; 22 | 23 | @override 24 | HomeState clone() { 25 | return HomeState() 26 | ..subState = subState 27 | ..banners = banners 28 | ..bannerImages = bannerImages 29 | ..bannerIndex = bannerIndex; 30 | } 31 | } 32 | 33 | HomeState initState(Map args) { 34 | return HomeState() 35 | ..subState = ArticleListState( 36 | type: 0, 37 | ) 38 | ..banners = [] 39 | ..bannerImages = [] 40 | ..bannerIndex = 0; 41 | } 42 | 43 | ///文章列表连接器 44 | class ArticleConnector extends ConnOp 45 | with ReselectMixin { 46 | @override 47 | ArticleListState computed(HomeState state) { 48 | return state.subState.clone(); 49 | } 50 | 51 | @override 52 | void set(HomeState state, ArticleListState subState) { 53 | state.subState = subState; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/module/home/view.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'action.dart'; 5 | import 'state.dart'; 6 | import 'widget/home_banner.dart'; 7 | 8 | Widget buildView(HomeState state, Dispatch dispatch, ViewService viewService) { 9 | return Scaffold( 10 | body: Column(children: [ 11 | HomeBanner( 12 | data: state, 13 | onChanged: (int index) { 14 | dispatch(HomeActionCreator.switchBanner(index)); 15 | }, 16 | onTap: (int index) { 17 | dispatch(HomeActionCreator.openBannerContent(index)); 18 | }, 19 | ), 20 | Expanded(child: viewService.buildComponent("ArticleList")) 21 | ]), 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /lib/module/main/action.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | //TODO replace with your own action 4 | enum MainAction { 5 | //切换tab 6 | selectTab, 7 | //侧边栏item点击 8 | clickDrawer, 9 | //搜索 10 | toSearch, 11 | //统一刷新事件 12 | onRefresh, 13 | } 14 | 15 | class MainActionCreator { 16 | static Action toSearch() { 17 | return Action(MainAction.toSearch); 18 | } 19 | 20 | static Action selectTab(int index) { 21 | return Action(MainAction.selectTab, payload: index); 22 | } 23 | 24 | static Action onRefresh() { 25 | return Action(MainAction.onRefresh); 26 | } 27 | 28 | static Action clickDrawer(String tag) { 29 | return Action(MainAction.clickDrawer, payload: tag); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/module/main/collect/action.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | enum CollectAction { 4 | onRefresh, 5 | //上拉加载 6 | onListLoad, 7 | //下拉刷新 8 | onListRefresh, 9 | } 10 | 11 | class CollectActionCreator { 12 | static Action onRefresh() { 13 | return const Action(CollectAction.onRefresh); 14 | } 15 | 16 | static Action onListLoad() { 17 | return Action(CollectAction.onListLoad); 18 | } 19 | 20 | static Action onListRefresh() { 21 | return Action(CollectAction.onListRefresh); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/module/main/collect/adapter.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | import 'collect_item/component.dart'; 4 | import 'state.dart'; 5 | 6 | class CollectAdapter extends SourceFlowAdapter { 7 | static const String collect_item = "collect_item"; 8 | 9 | CollectAdapter() 10 | : super( 11 | pool: >{ 12 | collect_item: CollectItemComponent(), 13 | }, 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /lib/module/main/collect/collect_item/action.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | enum CollectItemAction { 4 | //跳转详情 5 | toDetail, 6 | } 7 | 8 | class CollectItemActionCreator { 9 | static Action toDetail() { 10 | return const Action(CollectItemAction.toDetail); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/module/main/collect/collect_item/component.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | import 'effect.dart'; 4 | import 'state.dart'; 5 | import 'view.dart'; 6 | 7 | class CollectItemComponent extends Component { 8 | CollectItemComponent() 9 | : super( 10 | effect: buildEffect(), 11 | view: buildView, 12 | dependencies: Dependencies( 13 | adapter: null, 14 | slots: >{ 15 | }),); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /lib/module/main/collect/collect_item/effect.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter/material.dart' hide Action; 3 | import 'package:flutter_wan/app/config/route.dart'; 4 | import 'package:flutter_wan/bean/common/article_detail_bean.dart'; 5 | 6 | import 'action.dart'; 7 | import 'state.dart'; 8 | 9 | Effect buildEffect() { 10 | return combineEffects(>{ 11 | CollectItemAction.toDetail: _toDetail, 12 | }); 13 | } 14 | 15 | void _toDetail(Action action, Context ctx) { 16 | var data = ctx.state.item; 17 | ArticleDetailBean articleDetailBean = ArticleDetailBean( 18 | url: data.link, 19 | title: data.title, 20 | id: data.id, 21 | ); 22 | 23 | Navigator.pushNamed( 24 | ctx.context, 25 | RouteConfig.webViewPage, 26 | arguments: { 27 | "articleDetail": articleDetailBean, 28 | }, 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /lib/module/main/collect/collect_item/state.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter_wan/bean/user/collect_list_bean.dart'; 3 | 4 | class CollectItemState implements Cloneable { 5 | CollectItemState({this.item}); 6 | 7 | Datas item; 8 | 9 | @override 10 | CollectItemState clone() { 11 | return CollectItemState()..item = item; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/module/main/collect/collect_item/view.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'action.dart'; 5 | import 'state.dart'; 6 | import 'widget/collect_item_view.dart'; 7 | 8 | Widget buildView( 9 | CollectItemState state, Dispatch dispatch, ViewService viewService) { 10 | return _body(state, dispatch); 11 | } 12 | 13 | Widget _body(CollectItemState state, Dispatch dispatch) { 14 | return CollectItemView( 15 | data: state, 16 | onTap: () { 17 | dispatch(CollectItemActionCreator.toDetail()); 18 | }, 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /lib/module/main/collect/effect.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:dio/dio.dart'; 4 | import 'package:fish_redux/fish_redux.dart'; 5 | import 'package:flutter_wan/app/utils/ui/view_util.dart'; 6 | import 'package:flutter_wan/bean/user/collect_list_bean.dart'; 7 | import 'package:flutter_wan/http/api.dart'; 8 | import 'package:flutter_wan/http/http.dart'; 9 | import 'package:flutter_wan/module/main/collect/action.dart'; 10 | import 'package:flutter_wan/module/main/collect/collect_item/state.dart'; 11 | 12 | import 'state.dart'; 13 | 14 | Effect buildEffect() { 15 | return combineEffects(>{ 16 | Lifecycle.initState: _init, 17 | //下拉刷新 18 | CollectAction.onListRefresh: _onListRefresh, 19 | //上拉加载 20 | CollectAction.onListLoad: _onListLoad, 21 | }); 22 | } 23 | 24 | void _onListLoad(Action action, Context ctx) async { 25 | _loadData(++ctx.state.index, ctx); 26 | } 27 | 28 | void _onListRefresh(Action action, Context ctx) async { 29 | _loadData(ctx.state.index = 0, ctx); 30 | } 31 | 32 | void _init(Action action, Context ctx) async { 33 | await ViewUtil.initFinish(); 34 | } 35 | 36 | void _loadData(int index, Context ctx) async { 37 | var result = await Dio().get( 38 | '${ApiUrl.COLLECT_ARTICLE_LIST}$index/json', 39 | options: await getOptions(), 40 | ); 41 | var bean = CollectListBean.fromJson(json.decode(result.toString())); 42 | 43 | var list = List.generate(bean.data.datas.length, (index) { 44 | return CollectItemState(item: bean.data.datas[index]); 45 | }); 46 | 47 | if (index == 0) { 48 | ctx.state.items = list; 49 | 50 | ctx.state.easyRefreshController.finishRefresh(); 51 | ctx.state.easyRefreshController.finishLoad(noMore: false); 52 | } else { 53 | if (list.length == 0) { 54 | ctx.state.easyRefreshController.finishLoad(noMore: true); 55 | } else { 56 | ctx.state.easyRefreshController.finishLoad(noMore: false); 57 | } 58 | 59 | ctx.state.items.addAll(list); 60 | } 61 | 62 | //刷新数据源 63 | ctx.dispatch(CollectActionCreator.onRefresh()); 64 | } 65 | -------------------------------------------------------------------------------- /lib/module/main/collect/page.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter_wan/module/main/collect/adapter.dart'; 3 | 4 | import 'effect.dart'; 5 | import 'reducer.dart'; 6 | import 'state.dart'; 7 | import 'view.dart'; 8 | 9 | class CollectPage extends Page> { 10 | CollectPage() 11 | : super( 12 | initState: initState, 13 | effect: buildEffect(), 14 | reducer: buildReducer(), 15 | view: buildView, 16 | dependencies: Dependencies( 17 | adapter: NoneConn() + CollectAdapter(), 18 | slots: >{}, 19 | ), 20 | middleware: >[], 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /lib/module/main/collect/reducer.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | import 'action.dart'; 4 | import 'state.dart'; 5 | 6 | Reducer buildReducer() { 7 | return asReducer( 8 | >{ 9 | CollectAction.onRefresh: _onRefresh, 10 | }, 11 | ); 12 | } 13 | 14 | CollectState _onRefresh(CollectState state, Action action) { 15 | return state.clone(); 16 | } 17 | -------------------------------------------------------------------------------- /lib/module/main/collect/state.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter_easyrefresh/easy_refresh.dart'; 3 | import 'package:flutter_wan/module/main/collect/adapter.dart'; 4 | 5 | class CollectState extends MutableSource implements Cloneable { 6 | ///列表数据源 7 | int index; 8 | 9 | ///数据源 10 | List items; 11 | 12 | ///刷新控制器 13 | EasyRefreshController easyRefreshController; 14 | 15 | @override 16 | CollectState clone() { 17 | return CollectState() 18 | ..index = index 19 | ..items = items 20 | ..easyRefreshController = easyRefreshController; 21 | } 22 | 23 | @override 24 | Object getItemData(int index) => items[index]; 25 | 26 | @override 27 | String getItemType(int index) => CollectAdapter.collect_item; 28 | 29 | @override 30 | int get itemCount => items.length; 31 | 32 | @override 33 | void setItemData(int index, Object data) => items[index] = data; 34 | } 35 | 36 | CollectState initState(Map args) { 37 | return CollectState() 38 | ..index = 0 39 | ..items = [] 40 | ..easyRefreshController = EasyRefreshController(); 41 | } 42 | -------------------------------------------------------------------------------- /lib/module/main/collect/view.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_easyrefresh/easy_refresh.dart'; 4 | import 'package:flutter_wan/module/main/collect/action.dart'; 5 | 6 | import 'state.dart'; 7 | 8 | Widget buildView( 9 | CollectState state, Dispatch dispatch, ViewService viewService) { 10 | return Scaffold( 11 | appBar: AppBar( 12 | title: Text('收藏'), 13 | elevation: 0, 14 | ), 15 | body: EasyRefresh( 16 | controller: state.easyRefreshController, 17 | header: MaterialHeader(), 18 | footer: MaterialFooter(), 19 | firstRefresh: true, 20 | enableControlFinishRefresh: true, 21 | enableControlFinishLoad: true, 22 | //下拉刷新 23 | onRefresh: () async => dispatch(CollectActionCreator.onListRefresh()), 24 | //上拉加载 25 | onLoad: () async => dispatch(CollectActionCreator.onListLoad()), 26 | child: ListView.builder( 27 | itemBuilder: viewService.buildAdapter().itemBuilder, 28 | itemCount: viewService.buildAdapter().itemCount, 29 | ), 30 | ), 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /lib/module/main/login/action.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | enum LoginAction { 4 | //注册 5 | onRegister, 6 | //登录 7 | onLogin, 8 | } 9 | 10 | class LoginActionCreator { 11 | static Action onLogin() { 12 | return const Action(LoginAction.onLogin); 13 | } 14 | 15 | static Action onRegister() { 16 | return const Action(LoginAction.onRegister); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/module/main/login/effect.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:dio/dio.dart'; 4 | import 'package:fish_redux/fish_redux.dart'; 5 | import 'package:flutter/material.dart' hide Action; 6 | import 'package:flutter_wan/app/config/route.dart'; 7 | import 'package:flutter_wan/app/utils/cache/sp_util.dart'; 8 | import 'package:flutter_wan/app/utils/show/toast_util.dart'; 9 | import 'package:flutter_wan/app/utils/tool/string_util.dart'; 10 | import 'package:flutter_wan/bean/user/login_info_bean.dart'; 11 | import 'package:flutter_wan/http/api.dart'; 12 | 13 | import 'action.dart'; 14 | import 'state.dart'; 15 | 16 | Effect buildEffect() { 17 | return combineEffects(>{ 18 | LoginAction.onLogin: _onLogin, 19 | LoginAction.onRegister: _onRegister, 20 | }); 21 | } 22 | 23 | void _onRegister(Action action, Context ctx) { 24 | ///注册操作 25 | Navigator.pushNamed(ctx.context, RouteConfig.registerPage); 26 | } 27 | 28 | void _onLogin(Action action, Context ctx) async { 29 | if (!_isAccess(ctx)) { 30 | return; 31 | } 32 | //登录操作 33 | var data = { 34 | 'username': ctx.state.userName, 35 | // 'username': 'xdd613', 36 | 'password': ctx.state.password, 37 | // 'password': '1q2w3e4r', 38 | }; 39 | var result = await Dio().post(ApiUrl.LOGIN, queryParameters: data); 40 | var loginInfo = LoginInfoBean.fromJson(json.decode(result.toString())); 41 | if (loginInfo.errorCode == 0) { 42 | showToast("登录成功"); 43 | _saveUserInfo(ctx.state); 44 | Navigator.pop(ctx.context, true); 45 | }else{ 46 | showToast(loginInfo.errorMsg); 47 | } 48 | } 49 | 50 | //保存用户信息 51 | void _saveUserInfo(LoginState state) async{ 52 | SpUtil.put('userName', state.userName); 53 | SpUtil.put('password', state.password); 54 | } 55 | 56 | bool _isAccess(Context ctx) { 57 | var state = ctx.state; 58 | 59 | if (StringUtil.isEmpty(state.userName)) { 60 | showToast('请输入用户名'); 61 | return false; 62 | } 63 | 64 | if (StringUtil.isEmpty(state.password)) { 65 | showToast('请输入密码'); 66 | return false; 67 | } 68 | 69 | return true; 70 | } 71 | -------------------------------------------------------------------------------- /lib/module/main/login/page.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | import 'effect.dart'; 4 | import 'state.dart'; 5 | import 'view.dart'; 6 | 7 | class LoginPage extends Page> { 8 | LoginPage() 9 | : super( 10 | initState: initState, 11 | effect: buildEffect(), 12 | view: buildView, 13 | dependencies: Dependencies( 14 | adapter: null, 15 | slots: >{ 16 | }), 17 | middleware: >[ 18 | ],); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /lib/module/main/login/state.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | class LoginState implements Cloneable { 4 | ///用户名 5 | String userName; 6 | 7 | ///密码 8 | String password; 9 | 10 | @override 11 | LoginState clone() { 12 | return LoginState() 13 | ..userName = userName 14 | ..password = password; 15 | } 16 | } 17 | 18 | LoginState initState(Map args) { 19 | return LoginState(); 20 | } 21 | -------------------------------------------------------------------------------- /lib/module/main/login/view.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'action.dart'; 5 | import 'state.dart'; 6 | import 'widget/login_view.dart'; 7 | 8 | Widget buildView(LoginState state, Dispatch dispatch, ViewService viewService) { 9 | return Scaffold( 10 | appBar: AppBar(title: Text('登录')), 11 | body: SingleChildScrollView( 12 | child: LoginView( 13 | //用户名 14 | onUserName: (String msg) => state.userName = msg, 15 | //密码 16 | onPassword: (String msg) => state.password = msg, 17 | //注册 18 | onRegister: () => dispatch(LoginActionCreator.onRegister()), 19 | //登录 20 | onLogin: () => dispatch(LoginActionCreator.onLogin()), 21 | ), 22 | ), 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /lib/module/main/page.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | import 'effect.dart'; 4 | import 'reducer.dart'; 5 | import 'state.dart'; 6 | import 'view.dart'; 7 | 8 | class MainPage extends Page> { 9 | MainPage() 10 | : super( 11 | initState: initState, 12 | effect: buildEffect(), 13 | reducer: buildReducer(), 14 | view: buildView, 15 | dependencies: Dependencies( 16 | adapter: null, slots: >{}), 17 | middleware: >[], 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /lib/module/main/reducer.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | import 'action.dart'; 4 | import 'state.dart'; 5 | 6 | Reducer buildReducer() { 7 | return asReducer( 8 | >{ 9 | MainAction.onRefresh: _onRefresh, 10 | }, 11 | ); 12 | } 13 | 14 | MainState _onRefresh(MainState state, Action action) { 15 | return state.clone(); 16 | } 17 | -------------------------------------------------------------------------------- /lib/module/main/register/action.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | enum RegisterAction { 4 | //注册 5 | onRegister, 6 | } 7 | 8 | class RegisterActionCreator { 9 | static Action onRegister() { 10 | return const Action(RegisterAction.onRegister); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/module/main/register/effect.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:dio/dio.dart'; 4 | import 'package:fish_redux/fish_redux.dart'; 5 | import 'package:flutter/cupertino.dart' hide Action; 6 | import 'package:flutter_wan/app/utils/show/toast_util.dart'; 7 | import 'package:flutter_wan/app/utils/tool/string_util.dart'; 8 | import 'package:flutter_wan/bean/user/register_info_bean.dart'; 9 | import 'package:flutter_wan/http/api.dart'; 10 | 11 | import 'action.dart'; 12 | import 'state.dart'; 13 | 14 | Effect buildEffect() { 15 | return combineEffects(>{ 16 | //注册 17 | RegisterAction.onRegister: _onRegister, 18 | }); 19 | } 20 | 21 | void _onRegister(Action action, Context ctx) async { 22 | RegisterState state = ctx.state; 23 | if (!_isAccess(state)) { 24 | return; 25 | } 26 | 27 | var query = { 28 | 'username': state.userName, 29 | 'password': state.password, 30 | 'repassword': state.rePassword, 31 | }; 32 | var result = await Dio().post(ApiUrl.REGISTER, queryParameters: query); 33 | RegisterInfoBean bean = 34 | RegisterInfoBean.fromJson(json.decode(result.toString())); 35 | print(bean.toJson()); 36 | 37 | if (bean.errorCode == 0) { 38 | showToast("注册成功"); 39 | Navigator.pop(ctx.context); 40 | } else { 41 | showToast(bean.errorMsg); 42 | } 43 | } 44 | 45 | bool _isAccess(RegisterState state) { 46 | if (StringUtil.isEmpty(state.userName)) { 47 | showToast('请输入用户名'); 48 | return false; 49 | } 50 | 51 | if (StringUtil.isEmpty(state.userName)) { 52 | showToast('请输入密码'); 53 | return false; 54 | } 55 | 56 | if (StringUtil.isEmpty(state.userName)) { 57 | showToast('请输入确认密码'); 58 | return false; 59 | } 60 | 61 | return true; 62 | } 63 | -------------------------------------------------------------------------------- /lib/module/main/register/page.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | import 'effect.dart'; 4 | import 'state.dart'; 5 | import 'view.dart'; 6 | 7 | class RegisterPage extends Page> { 8 | RegisterPage() 9 | : super( 10 | initState: initState, 11 | effect: buildEffect(), 12 | view: buildView, 13 | dependencies: Dependencies( 14 | adapter: null, 15 | slots: >{ 16 | }), 17 | middleware: >[ 18 | ],); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /lib/module/main/register/state.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | class RegisterState implements Cloneable { 4 | String userName; 5 | String password; 6 | String rePassword; 7 | 8 | @override 9 | RegisterState clone() { 10 | return RegisterState() 11 | ..userName = userName 12 | ..password = password 13 | ..rePassword = rePassword; 14 | } 15 | } 16 | 17 | RegisterState initState(Map args) { 18 | return RegisterState(); 19 | } 20 | -------------------------------------------------------------------------------- /lib/module/main/register/view.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'action.dart'; 5 | import 'state.dart'; 6 | import 'widget/register_view.dart'; 7 | 8 | Widget buildView( 9 | RegisterState state, Dispatch dispatch, ViewService viewService) { 10 | return Scaffold( 11 | appBar: AppBar(title: Text('登录')), 12 | body: SingleChildScrollView( 13 | child: RegisterView( 14 | //用户名 15 | onUserName: (String msg) => state.userName = msg, 16 | //密码 17 | onPassword: (String msg) => state.password = msg, 18 | //确认密码 19 | onRePassword: (String msg) => state.rePassword = msg, 20 | //注册 21 | onRegister: () => dispatch(RegisterActionCreator.onRegister()), 22 | ), 23 | ), 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /lib/module/main/search/action.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter_wan/bean/user/hot_word_bean.dart'; 3 | 4 | //TODO replace with your own action 5 | enum SearchAction { 6 | //搜索文章信息 7 | search, 8 | onRefresh, 9 | //搜索热词 10 | searchHotWord, 11 | //删除搜索内容 12 | onClear, 13 | } 14 | 15 | class SearchActionCreator { 16 | static Action onClear() { 17 | return Action(SearchAction.onClear); 18 | } 19 | 20 | static Action searchHotWord(Data item) { 21 | return Action(SearchAction.searchHotWord, payload: item); 22 | } 23 | 24 | static Action search(String msg) { 25 | return Action(SearchAction.search, payload: msg); 26 | } 27 | 28 | static Action onRefresh() { 29 | return const Action(SearchAction.onRefresh); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/module/main/search/effect.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:dio/dio.dart'; 4 | import 'package:fish_redux/fish_redux.dart'; 5 | import 'package:flutter_wan/app/utils/tool/string_util.dart'; 6 | import 'package:flutter_wan/app/utils/ui/view_util.dart'; 7 | import 'package:flutter_wan/bean/user/hot_word_bean.dart'; 8 | import 'package:flutter_wan/http/api.dart'; 9 | 10 | import 'action.dart'; 11 | import 'state.dart'; 12 | 13 | Effect buildEffect() { 14 | return combineEffects(>{ 15 | //初始化 16 | Lifecycle.initState: _init, 17 | //点击键盘上的搜索从操作 18 | SearchAction.search: _search, 19 | //点击搜索热词 20 | SearchAction.searchHotWord: _searchHotWord, 21 | //点击搜索框中删除按钮 22 | SearchAction.onClear: _onClear, 23 | }); 24 | } 25 | 26 | void _onClear(Action action, Context ctx) async { 27 | ctx.state.subState.searchMsg = ''; 28 | ctx.state.subState.items = []; 29 | ctx.dispatch(SearchActionCreator.onRefresh()); 30 | } 31 | 32 | void _searchHotWord(Action action, Context ctx) async { 33 | Data item = action.payload; 34 | //填充数据源 35 | ctx.state.controller.text = item.name; 36 | 37 | //请求列表 38 | ctx.state.subState.searchMsg = item.name; 39 | ctx.state.subState.controller.callRefresh(); 40 | 41 | ctx.dispatch(SearchActionCreator.onRefresh()); 42 | } 43 | 44 | void _init(Action action, Context ctx) async { 45 | await ViewUtil.initFinish(); 46 | 47 | //获取搜索热词 48 | var result = await Dio().get(ApiUrl.SEARCH_HOT_WORD); 49 | 50 | var bean = HotWordBean.fromJson(json.decode(result.toString())); 51 | ctx.state.hotList = bean.data; 52 | 53 | ctx.dispatch(SearchActionCreator.onRefresh()); 54 | } 55 | 56 | void _search(Action action, Context ctx) { 57 | //搜索 58 | String searchMsg = action.payload; 59 | 60 | if (StringUtil.isEmpty(searchMsg)) { 61 | ctx.state.subState.searchMsg = ''; 62 | ctx.state.subState.items = []; 63 | } else { 64 | ctx.state.subState.searchMsg = searchMsg; 65 | //通知刷新数据 66 | ctx.state.subState.controller.callRefresh(); 67 | } 68 | 69 | ctx.dispatch(SearchActionCreator.onRefresh()); 70 | } 71 | -------------------------------------------------------------------------------- /lib/module/main/search/page.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter_wan/view/component/article_list/component.dart'; 3 | 4 | import 'effect.dart'; 5 | import 'reducer.dart'; 6 | import 'state.dart'; 7 | import 'view.dart'; 8 | 9 | class SearchPage extends Page> { 10 | SearchPage() 11 | : super( 12 | initState: initState, 13 | effect: buildEffect(), 14 | reducer: buildReducer(), 15 | view: buildView, 16 | dependencies: Dependencies( 17 | adapter: null, 18 | slots: >{ 19 | "ArticleList": SearchArticleConnector() + ArticleListComponent(), 20 | }), 21 | middleware: >[ 22 | ],); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /lib/module/main/search/reducer.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | import 'action.dart'; 4 | import 'state.dart'; 5 | 6 | Reducer buildReducer() { 7 | return asReducer( 8 | >{ 9 | SearchAction.onRefresh: _onRefresh, 10 | }, 11 | ); 12 | } 13 | 14 | SearchState _onRefresh(SearchState state, Action action) { 15 | return state.clone(); 16 | } 17 | -------------------------------------------------------------------------------- /lib/module/main/search/state.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_wan/bean/user/hot_word_bean.dart'; 4 | import 'package:flutter_wan/view/component/article_list/state.dart'; 5 | 6 | class SearchState implements Cloneable { 7 | //文章数据源 8 | ArticleListState subState; 9 | 10 | //搜索热词 11 | List hotList; 12 | 13 | //输入框控制器 14 | TextEditingController controller; 15 | 16 | @override 17 | SearchState clone() { 18 | return SearchState() 19 | ..subState = subState 20 | ..controller = controller 21 | ..hotList = hotList; 22 | } 23 | } 24 | 25 | SearchState initState(Map args) { 26 | return SearchState() 27 | ..hotList = [] 28 | ..controller = TextEditingController() 29 | ..subState = ArticleListState( 30 | type: 2, 31 | firstRefresh: false, 32 | ); 33 | } 34 | 35 | ///文章列表连接器 36 | class SearchArticleConnector extends ConnOp 37 | with ReselectMixin { 38 | @override 39 | ArticleListState computed(SearchState state) { 40 | return state.subState.clone(); 41 | } 42 | 43 | @override 44 | void set(SearchState state, ArticleListState subState) { 45 | state.subState = subState; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/module/main/search/view.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_wan/module/main/search/action.dart'; 4 | import 'package:flutter_wan/module/main/search/widget/search_hot_word.dart'; 5 | 6 | import 'state.dart'; 7 | import 'widget/search_app_bar.dart'; 8 | 9 | Widget buildView( 10 | SearchState state, Dispatch dispatch, ViewService viewService) { 11 | return Scaffold( 12 | appBar: searchAppBar( 13 | data: state, 14 | //搜索 15 | onSearch: (msg) => dispatch(SearchActionCreator.search(msg)), 16 | //清除输入 17 | onClear: () => dispatch(SearchActionCreator.onClear()), 18 | ), 19 | body: Stack(children: [ 20 | //列表 21 | viewService.buildComponent("ArticleList"), 22 | 23 | //热搜词 24 | Visibility( 25 | visible: state.subState.items.length == 0, 26 | child: SearchHotWord( 27 | data: state, 28 | onHot: (item) => dispatch(SearchActionCreator.searchHotWord(item)), 29 | ), 30 | ), 31 | ]), 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /lib/module/main/search/widget/search_app_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_wan/app/typedef/function.dart'; 3 | import 'package:flutter_wan/view/widget/search/search_bar.dart'; 4 | 5 | import '../state.dart'; 6 | 7 | AppBar searchAppBar({ 8 | SearchState data, 9 | ParamSingleCallback onSearch, 10 | ParamVoidCallback onClear, 11 | }) { 12 | return AppBar( 13 | automaticallyImplyLeading: false, 14 | title: Container( 15 | child: SearchBar( 16 | controller: data.controller, 17 | onComplete: onSearch, 18 | hintText: '请输入查询内容', 19 | onClear: onClear, 20 | ), 21 | ), 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /lib/module/main/search/widget/search_hot_word.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_wan/app/typedef/function.dart'; 5 | import 'package:flutter_wan/app/utils/ui/ui_adapter.dart'; 6 | import 'package:flutter_wan/bean/user/hot_word_bean.dart'; 7 | import 'package:flutter_wan/module/main/search/state.dart'; 8 | 9 | class SearchHotWord extends StatelessWidget { 10 | SearchHotWord({ 11 | this.data, 12 | this.onHot, 13 | }); 14 | 15 | final SearchState data; 16 | 17 | final ParamSingleCallback onHot; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return _buildHotWord(); 22 | } 23 | 24 | ///主体入口 25 | Widget _buildHotWord() { 26 | return _buildBg( 27 | children: data.hotList.map((e) => _buildItem(e)).toList(), 28 | ); 29 | } 30 | 31 | Widget _buildItem(Data item) { 32 | return InkWell( 33 | customBorder: StadiumBorder(), 34 | onTap: () { 35 | onHot(item); 36 | }, 37 | child: Container( 38 | padding: EdgeInsets.symmetric( 39 | horizontal: auto(30), 40 | vertical: auto(10), 41 | ), 42 | decoration: BoxDecoration( 43 | border: Border.all( 44 | color: 45 | Color.fromRGBO(randomColor(), randomColor(), randomColor(), 1), 46 | width: auto(2), 47 | ), 48 | borderRadius: BorderRadius.all(Radius.circular(auto(100))), 49 | ), 50 | child: Text(item.name), 51 | ), 52 | ); 53 | } 54 | 55 | //一些背景设置,无关页面,无需在乎 56 | Widget _buildBg({List children}) { 57 | return Container( 58 | margin: EdgeInsets.only(top: auto(20), left: auto(36), right: auto(36)), 59 | child: Material( 60 | color: Colors.transparent, 61 | child: Wrap( 62 | runSpacing: 10, 63 | spacing: 10, 64 | children: children, 65 | ), 66 | ), 67 | ); 68 | } 69 | 70 | int randomColor() { 71 | return Random().nextInt(255); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/module/main/state.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_wan/app/utils/ui/keep_alive_page.dart'; 4 | import 'package:flutter_wan/bean/common/btn_info.dart'; 5 | import 'package:flutter_wan/module/home/page.dart'; 6 | import 'package:flutter_wan/module/navi/page.dart'; 7 | import 'package:flutter_wan/module/project/page.dart'; 8 | import 'package:flutter_wan/module/tree/page.dart'; 9 | 10 | class MainState implements Cloneable { 11 | ///底部导航栏tab标识 12 | int selectedIndex = 0; 13 | 14 | ///tab页面 15 | List tabPage; 16 | 17 | ///tab按钮 18 | List tabBar; 19 | 20 | ///页面控制器 21 | PageController pageController; 22 | 23 | ///抽屉items数据 24 | List drawerItems; 25 | 26 | @override 27 | MainState clone() { 28 | return MainState() 29 | ..selectedIndex = selectedIndex 30 | ..pageController = pageController 31 | ..drawerItems = drawerItems 32 | ..tabBar = tabBar 33 | ..tabPage = tabPage; 34 | } 35 | } 36 | 37 | MainState initState(Map args) { 38 | return MainState() 39 | ..selectedIndex = 0 40 | ..pageController = PageController() 41 | ..tabBar = [ 42 | BottomNavigationBarItem(icon: Icon(Icons.account_balance), label: "主页"), 43 | BottomNavigationBarItem(icon: Icon(Icons.print), label: "知识体系"), 44 | BottomNavigationBarItem(icon: Icon(Icons.poll), label: "导航"), 45 | BottomNavigationBarItem(icon: Icon(Icons.tab), label: "项目") 46 | ] 47 | ..tabPage = [ 48 | keepAlivePage(HomePage().buildPage(null)), 49 | keepAlivePage(TreePage().buildPage(null)), 50 | keepAlivePage(NaviPage().buildPage(null)), 51 | keepAlivePage(ProjectPage().buildPage(null)) 52 | ] 53 | ..drawerItems = [ 54 | BtnInfo(title: '登录', tag: 'person', icon: Icon(Icons.person)), 55 | BtnInfo(title: '我的收藏', tag: 'myCollect', icon: Icon(Icons.favorite)), 56 | BtnInfo(title: '关于', tag: 'about', icon: Icon(Icons.code)), 57 | BtnInfo(title: '反馈', tag: 'feedback', icon: Icon(Icons.announcement)), 58 | ]; 59 | } 60 | -------------------------------------------------------------------------------- /lib/module/main/view.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_wan/app/utils/ui/ui_adapter.dart'; 4 | import 'package:flutter_wan/module/main/widget/main_app_bar.dart'; 5 | 6 | import 'action.dart'; 7 | import 'state.dart'; 8 | import 'widget/main_body.dart'; 9 | import 'widget/main_bottom_navigation.dart'; 10 | import 'widget/main_drawer.dart'; 11 | 12 | Widget buildView(MainState state, Dispatch dispatch, ViewService viewService) { 13 | //此处初始化一次即可,effect里面初始化会报错 14 | initUiAdapter(viewService.context); 15 | 16 | return Scaffold( 17 | //顶部AppBar 18 | appBar: mainAppBar( 19 | onTap: () => dispatch(MainActionCreator.toSearch()), 20 | ), 21 | //侧边抽屉模块 22 | drawer: MainDrawer( 23 | data: state, 24 | onTap: (String tag) => dispatch(MainActionCreator.clickDrawer(tag)), 25 | ), 26 | //页面主体 27 | body: MainBody( 28 | data: state, 29 | onChanged: (int index) => dispatch(MainActionCreator.selectTab(index)), 30 | ), 31 | //底部导航 32 | bottomNavigationBar: MainBottomNavigation( 33 | data: state, 34 | onTap: (int index) => dispatch(MainActionCreator.selectTab(index)), 35 | ), 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /lib/module/main/widget/main_app_bar.dart: -------------------------------------------------------------------------------- 1 | //顶部appbar封装 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_wan/app/typedef/function.dart'; 4 | 5 | AppBar mainAppBar({ParamVoidCallback onTap}) { 6 | return AppBar( 7 | title: Text("玩Android"), 8 | elevation: 0, 9 | actions: [ 10 | IconButton( 11 | icon: Icon(Icons.search), 12 | onPressed: onTap, 13 | ) 14 | ], 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /lib/module/main/widget/main_body.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_wan/app/typedef/function.dart'; 3 | 4 | import '../state.dart'; 5 | 6 | class MainBody extends StatelessWidget { 7 | MainBody({ 8 | this.data, 9 | this.onChanged, 10 | }); 11 | 12 | ///数据源 13 | final MainState data; 14 | 15 | ///页面切换监听 16 | final ParamSingleCallback onChanged; 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return _body(); 21 | } 22 | 23 | Widget _body() { 24 | return PageView.builder( 25 | physics: NeverScrollableScrollPhysics(), 26 | //禁止页面左右滑动切换 27 | controller: data.pageController, 28 | onPageChanged: (index) { 29 | //切换页面时的回调 30 | onChanged(index); 31 | }, 32 | //回调函数 33 | itemCount: data.tabPage.length, 34 | itemBuilder: (context, index) => data.tabPage[index], 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/module/main/widget/main_bottom_navigation.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_wan/app/typedef/function.dart'; 3 | 4 | import '../state.dart'; 5 | 6 | class MainBottomNavigation extends StatelessWidget { 7 | MainBottomNavigation({ 8 | this.data, 9 | this.onTap, 10 | }); 11 | 12 | ///数据源 13 | final MainState data; 14 | 15 | ///点击回调 16 | final ParamSingleCallback onTap; 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return _bottom(); 21 | } 22 | 23 | Widget _bottom() { 24 | return BottomNavigationBar( 25 | type: BottomNavigationBarType.fixed, 26 | items: data.tabBar, 27 | currentIndex: data.selectedIndex, 28 | selectedItemColor: Colors.lightBlue, 29 | unselectedItemColor: Colors.black54, 30 | onTap: (int index) { 31 | onTap(index); 32 | data.pageController.jumpToPage(index); 33 | }, 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/module/main/widget/main_drawer.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_wan/app/typedef/function.dart'; 5 | import 'package:flutter_wan/app/utils/ui/ui_adapter.dart'; 6 | 7 | import '../state.dart'; 8 | 9 | class MainDrawer extends StatelessWidget { 10 | MainDrawer({ 11 | this.data, 12 | this.onTap, 13 | }); 14 | 15 | ///数据源 16 | final MainState data; 17 | 18 | ///监听点击的item 19 | final ParamSingleCallback onTap; 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return _drawer(); 24 | } 25 | 26 | Widget _drawer() { 27 | return Drawer( 28 | child: Column( 29 | children: [ 30 | //顶部背景 31 | Container( 32 | height: auto(370), 33 | width: double.infinity, 34 | child: Image.asset("images/ttxs.jpg", fit: BoxFit.fill), 35 | ), 36 | 37 | //功能Item 38 | Expanded(child: functionItem()), 39 | ], 40 | ), 41 | ); 42 | } 43 | 44 | Widget functionItem() { 45 | return ListView( 46 | children: data.drawerItems.map((e) { 47 | return InkWell( 48 | child: ListTile( 49 | leading: e.icon, 50 | title: Text(e.title), 51 | ), 52 | onTap: () { 53 | onTap(e.tag); 54 | }, 55 | ); 56 | }).toList(), 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/module/navi/action.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | enum NaviAction { 4 | onRefresh 5 | } 6 | 7 | class NaviActionCreator { 8 | 9 | static Action onRefresh() { 10 | return Action(NaviAction.onRefresh); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /lib/module/navi/adapter.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | import 'navi_item/component.dart'; 4 | import 'state.dart'; 5 | 6 | class NaviAdapter extends SourceFlowAdapter { 7 | static const String navi_item = "navi_item"; 8 | 9 | NaviAdapter() 10 | : super( 11 | pool: >{ 12 | navi_item: NaviItemComponent(), 13 | }, 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /lib/module/navi/effect.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:dio/dio.dart'; 4 | import 'package:fish_redux/fish_redux.dart'; 5 | import 'package:flutter_wan/bean/navi/navi_info_bean.dart'; 6 | import 'package:flutter_wan/http/api.dart'; 7 | import 'package:flutter_wan/http/http.dart'; 8 | 9 | import 'action.dart'; 10 | import 'navi_item/state.dart'; 11 | import 'state.dart'; 12 | 13 | Effect buildEffect() { 14 | return combineEffects(>{ 15 | Lifecycle.initState: _init, 16 | }); 17 | } 18 | 19 | void _init(Action action, Context ctx) async { 20 | Response response = await Dio().get( 21 | ApiUrl.GET_NAVI_INFO, 22 | options: await getOptions(), 23 | ); 24 | NaviInfoBean naviInfoBean = 25 | NaviInfoBean.fromJson(json.decode(response.toString())); 26 | 27 | List items = List.generate(naviInfoBean.data.length, (index) { 28 | return NaviItemState(itemDetail: naviInfoBean.data[index]); 29 | }); 30 | 31 | //刷新 32 | ctx.state.items = items; 33 | ctx.dispatch(NaviActionCreator.onRefresh()); 34 | } 35 | -------------------------------------------------------------------------------- /lib/module/navi/navi_item/action.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | enum NaviItemAction { 4 | //打开文章 5 | openArticle, 6 | } 7 | 8 | class NaviItemActionCreator { 9 | 10 | 11 | static Action openArticle(var item) { 12 | return Action(NaviItemAction.openArticle, payload: item); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/module/navi/navi_item/component.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | import 'effect.dart'; 4 | import 'state.dart'; 5 | import 'view.dart'; 6 | 7 | class NaviItemComponent extends Component { 8 | NaviItemComponent() 9 | : super( 10 | effect: buildEffect(), 11 | view: buildView, 12 | dependencies: Dependencies( 13 | adapter: null, 14 | slots: >{}, 15 | ), 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /lib/module/navi/navi_item/effect.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter/material.dart' hide Action; 3 | import 'package:flutter_wan/app/config/route.dart'; 4 | import 'package:flutter_wan/bean/common/article_detail_bean.dart'; 5 | import 'package:flutter_wan/bean/navi/navi_info_bean.dart'; 6 | 7 | import 'action.dart'; 8 | import 'state.dart'; 9 | 10 | Effect buildEffect() { 11 | return combineEffects(>{ 12 | NaviItemAction.openArticle: _openArticle, 13 | }); 14 | } 15 | 16 | void _openArticle(Action action, Context ctx) { 17 | Articles item = action.payload; 18 | ArticleDetailBean articleDetailBean = ArticleDetailBean( 19 | url: item.link, 20 | title: item.title, 21 | id: item.id, 22 | isCollect: item.collect, 23 | ); 24 | 25 | Navigator.pushNamed( 26 | ctx.context, 27 | RouteConfig.webViewPage, 28 | arguments: {"articleDetail": articleDetailBean}, 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /lib/module/navi/navi_item/state.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter_wan/bean/navi/navi_info_bean.dart'; 3 | 4 | class NaviItemState implements Cloneable { 5 | Data itemDetail; 6 | 7 | NaviItemState({this.itemDetail}); 8 | 9 | @override 10 | NaviItemState clone() { 11 | return NaviItemState()..itemDetail = itemDetail; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/module/navi/navi_item/view.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_wan/bean/navi/navi_info_bean.dart'; 4 | 5 | import 'action.dart'; 6 | import 'state.dart'; 7 | import 'widget/navi_detail_Item_view.dart'; 8 | 9 | Widget buildView( 10 | NaviItemState state, Dispatch dispatch, ViewService viewService) { 11 | return NaviItemView( 12 | data: state, 13 | onTap: (Articles item) { 14 | dispatch(NaviItemActionCreator.openArticle(item)); 15 | }, 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /lib/module/navi/page.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | import 'adapter.dart'; 4 | import 'effect.dart'; 5 | import 'reducer.dart'; 6 | import 'state.dart'; 7 | import 'view.dart'; 8 | 9 | class NaviPage extends Page> { 10 | NaviPage() 11 | : super( 12 | initState: initState, 13 | effect: buildEffect(), 14 | reducer: buildReducer(), 15 | view: buildView, 16 | dependencies: Dependencies( 17 | adapter: NoneConn() + NaviAdapter(), 18 | slots: >{}), 19 | middleware: >[], 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /lib/module/navi/reducer.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | import 'action.dart'; 4 | import 'state.dart'; 5 | 6 | Reducer buildReducer() { 7 | return asReducer( 8 | >{ 9 | NaviAction.onRefresh: _onRefresh, 10 | }, 11 | ); 12 | } 13 | 14 | NaviState _onRefresh(NaviState state, Action action) { 15 | return state.clone(); 16 | } 17 | -------------------------------------------------------------------------------- /lib/module/navi/state.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | import 'adapter.dart'; 4 | import 'navi_item/state.dart'; 5 | 6 | class NaviState extends MutableSource implements Cloneable { 7 | List items; 8 | 9 | @override 10 | NaviState clone() { 11 | return NaviState()..items = items; 12 | } 13 | 14 | @override 15 | Object getItemData(int index) => items[index]; 16 | 17 | @override 18 | String getItemType(int index) => NaviAdapter.navi_item; 19 | 20 | @override 21 | int get itemCount => items.length; 22 | 23 | @override 24 | void setItemData(int index, Object data) => items[index] = data; 25 | } 26 | 27 | NaviState initState(Map args) { 28 | return NaviState(); 29 | } 30 | -------------------------------------------------------------------------------- /lib/module/navi/view.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'state.dart'; 5 | 6 | Widget buildView(NaviState state, Dispatch dispatch, ViewService viewService) { 7 | if (state.items != null) { 8 | //更新内部item数据 9 | return ListView.builder( 10 | itemBuilder: viewService.buildAdapter().itemBuilder, 11 | itemCount: viewService.buildAdapter().itemCount, 12 | ); 13 | } else { 14 | return Center(child: CircularProgressIndicator()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/module/project/action.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | enum ProjectAction { 4 | //更新project数据 5 | onRefresh, 6 | } 7 | 8 | class ProjectActionCreator { 9 | static Action onRefresh() { 10 | return Action(ProjectAction.onRefresh); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/module/project/effect.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:dio/dio.dart'; 4 | import 'package:fish_redux/fish_redux.dart'; 5 | import 'package:flutter/material.dart' hide Action; 6 | import 'package:flutter_wan/bean/project/project_bean.dart'; 7 | import 'package:flutter_wan/http/api.dart'; 8 | import 'package:flutter_wan/http/http.dart'; 9 | 10 | import 'action.dart'; 11 | import 'state.dart'; 12 | 13 | Effect buildEffect() { 14 | return combineEffects(>{ 15 | Lifecycle.initState: _init, 16 | }); 17 | } 18 | 19 | void _init(Action action, Context ctx) async { 20 | Response response = await Dio().get( 21 | ApiUrl.GET_PROJECT_INFO, 22 | options: await getOptions(), 23 | ); 24 | ProjectBean projectBean = 25 | ProjectBean.fromJson(json.decode(response.toString())); 26 | var list = projectBean.data; 27 | //处理tab 28 | List tabs = []; 29 | for (var i = 0; i < list.length; i++) { 30 | tabs.add(Tab(text: list[i].name)); 31 | } 32 | 33 | //更新页面 34 | ctx.state.tabList = tabs; 35 | ctx.state.projectBean = projectBean; 36 | ctx.dispatch(ProjectActionCreator.onRefresh()); 37 | } 38 | -------------------------------------------------------------------------------- /lib/module/project/page.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | import 'effect.dart'; 4 | import 'reducer.dart'; 5 | import 'state.dart'; 6 | import 'view.dart'; 7 | 8 | class ProjectPage extends Page> { 9 | ProjectPage() 10 | : super( 11 | initState: initState, 12 | effect: buildEffect(), 13 | reducer: buildReducer(), 14 | view: buildView, 15 | dependencies: Dependencies( 16 | adapter: null, slots: >{}), 17 | middleware: >[], 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /lib/module/project/project_tab/action.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | //TODO replace with your own action 4 | enum ProjectTabAction { 5 | //刷新数据 6 | onRefresh, 7 | } 8 | 9 | class ProjectTabActionCreator { 10 | static Action onRefresh() { 11 | return Action(ProjectTabAction.onRefresh); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/module/project/project_tab/adapter.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter_wan/module/project/project_tab_item/component.dart'; 3 | 4 | import 'state.dart'; 5 | 6 | class ProjectTabAdapter extends SourceFlowAdapter { 7 | static const String project_tab_item = "project_tab_item"; 8 | 9 | ProjectTabAdapter() 10 | : super( 11 | pool: >{ 12 | project_tab_item: ProjectTabItemComponent() 13 | }, 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /lib/module/project/project_tab/effect.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:dio/dio.dart'; 4 | import 'package:fish_redux/fish_redux.dart'; 5 | import 'package:flutter_wan/bean/project/project_detail_bean.dart'; 6 | import 'package:flutter_wan/http/api.dart'; 7 | import 'package:flutter_wan/http/http.dart'; 8 | import 'package:flutter_wan/module/project/project_tab_item/state.dart'; 9 | 10 | import 'action.dart'; 11 | import 'state.dart'; 12 | 13 | Effect buildEffect() { 14 | return combineEffects(>{ 15 | Lifecycle.initState: _init, 16 | }); 17 | } 18 | 19 | void _init(Action action, Context ctx) async { 20 | Response response = await Dio().get( 21 | ApiUrl.GET_PROJECT_DETAIL, 22 | queryParameters: {"cid": ctx.state.id}, 23 | options: await getOptions(), 24 | ); 25 | ProjectDetailBean projectDetailBean = 26 | ProjectDetailBean.fromJson(json.decode(response.toString())); 27 | List items = 28 | List.generate(projectDetailBean.data.datas.length, (index) { 29 | return ProjectTabItemState(itemDetail: projectDetailBean.data.datas[index]); 30 | }); 31 | 32 | //刷新数据 33 | ctx.state.items = items; 34 | ctx.dispatch(ProjectTabActionCreator.onRefresh()); 35 | } 36 | -------------------------------------------------------------------------------- /lib/module/project/project_tab/page.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | import 'adapter.dart'; 4 | import 'effect.dart'; 5 | import 'reducer.dart'; 6 | import 'state.dart'; 7 | import 'view.dart'; 8 | 9 | class ProjectTabPage extends Page> { 10 | ProjectTabPage() 11 | : super( 12 | initState: initState, 13 | effect: buildEffect(), 14 | reducer: buildReducer(), 15 | view: buildView, 16 | dependencies: Dependencies( 17 | adapter: NoneConn() + ProjectTabAdapter(), 18 | slots: >{}), 19 | middleware: >[], 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /lib/module/project/project_tab/reducer.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | import 'action.dart'; 4 | import 'state.dart'; 5 | 6 | Reducer buildReducer() { 7 | return asReducer( 8 | >{ 9 | ProjectTabAction.onRefresh: _onRefresh, 10 | }, 11 | ); 12 | } 13 | 14 | ProjectTabState _onRefresh(ProjectTabState state, Action action) { 15 | return state.clone(); 16 | } 17 | -------------------------------------------------------------------------------- /lib/module/project/project_tab/state.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter_wan/module/project/project_tab_item/state.dart'; 3 | 4 | import 'adapter.dart'; 5 | 6 | class ProjectTabState extends MutableSource 7 | implements Cloneable { 8 | //请求id 9 | int id; 10 | 11 | //items 12 | List items; 13 | 14 | @override 15 | ProjectTabState clone() { 16 | return ProjectTabState() 17 | ..id = id 18 | ..items = items; 19 | } 20 | 21 | @override 22 | Object getItemData(int index) => items[index]; 23 | 24 | @override 25 | String getItemType(int index) => ProjectTabAdapter.project_tab_item; 26 | 27 | @override 28 | int get itemCount => items.length; 29 | 30 | @override 31 | void setItemData(int index, Object data) => items[index] = data; 32 | } 33 | 34 | ProjectTabState initState(Map args) { 35 | int id = args["id"]; 36 | return ProjectTabState() 37 | ..items = [] 38 | ..id = id; 39 | } 40 | -------------------------------------------------------------------------------- /lib/module/project/project_tab/view.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'state.dart'; 5 | 6 | Widget buildView( 7 | ProjectTabState state, Dispatch dispatch, ViewService viewService) { 8 | if (state.items != null) { 9 | return ListView.builder( 10 | itemBuilder: viewService.buildAdapter().itemBuilder, 11 | itemCount: viewService.buildAdapter().itemCount, 12 | ); 13 | } else { 14 | return Center( 15 | child: CircularProgressIndicator(), 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/module/project/project_tab_item/action.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | enum ProjectTabItemAction { 4 | openArticle 5 | } 6 | 7 | class ProjectTabItemActionCreator { 8 | static Action openArticle(var item) { 9 | return Action(ProjectTabItemAction.openArticle, payload: item); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/module/project/project_tab_item/component.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | import 'effect.dart'; 4 | import 'state.dart'; 5 | import 'view.dart'; 6 | 7 | class ProjectTabItemComponent extends Component { 8 | ProjectTabItemComponent() 9 | : super( 10 | effect: buildEffect(), 11 | view: buildView, 12 | dependencies: Dependencies( 13 | adapter: null, slots: >{}), 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /lib/module/project/project_tab_item/effect.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter/cupertino.dart' hide Action; 3 | import 'package:flutter_wan/app/config/route.dart'; 4 | import 'package:flutter_wan/bean/common/article_detail_bean.dart'; 5 | import 'package:flutter_wan/bean/project/project_detail_bean.dart'; 6 | 7 | import 'action.dart'; 8 | import 'state.dart'; 9 | 10 | Effect buildEffect() { 11 | return combineEffects(>{ 12 | ProjectTabItemAction.openArticle: _openArticle, 13 | }); 14 | } 15 | 16 | void _openArticle(Action action, Context ctx) { 17 | Datas item = action.payload; 18 | ArticleDetailBean articleDetailBean = ArticleDetailBean( 19 | url: item.link, 20 | title: item.title, 21 | id: item.id, 22 | isCollect: item.collect, 23 | ); 24 | 25 | Navigator.pushNamed( 26 | ctx.context, 27 | RouteConfig.webViewPage, 28 | arguments: {"articleDetail": articleDetailBean}, 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /lib/module/project/project_tab_item/state.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter_wan/bean/project/project_detail_bean.dart'; 3 | 4 | class ProjectTabItemState implements Cloneable { 5 | Datas itemDetail; 6 | 7 | ProjectTabItemState({this.itemDetail}); 8 | 9 | @override 10 | ProjectTabItemState clone() { 11 | return ProjectTabItemState()..itemDetail = itemDetail; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/module/project/project_tab_item/view.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_wan/bean/project/project_detail_bean.dart'; 4 | 5 | import 'action.dart'; 6 | import 'state.dart'; 7 | import 'widget/project_item_detail.dart'; 8 | 9 | Widget buildView( 10 | ProjectTabItemState state, Dispatch dispatch, ViewService viewService) { 11 | return ProjectItemDetail( 12 | data: state, 13 | onTap: (Datas item) { 14 | dispatch(ProjectTabItemActionCreator.openArticle(item)); 15 | }, 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /lib/module/project/reducer.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter/material.dart' hide Action; 3 | 4 | import 'action.dart'; 5 | import 'state.dart'; 6 | 7 | Reducer buildReducer() { 8 | return asReducer( 9 | >{ 10 | ProjectAction.onRefresh: _onRefresh, 11 | }, 12 | ); 13 | } 14 | 15 | ProjectState _onRefresh(ProjectState state, Action action) { 16 | return state.clone(); 17 | } 18 | -------------------------------------------------------------------------------- /lib/module/project/state.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_wan/bean/project/project_bean.dart'; 4 | 5 | class ProjectState implements Cloneable { 6 | //数据源 7 | ProjectBean projectBean; 8 | 9 | //tab 10 | List tabList; 11 | 12 | //自定义TabBar控制器 13 | TabController tabController; 14 | 15 | @override 16 | ProjectState clone() { 17 | return ProjectState() 18 | ..projectBean = projectBean 19 | ..tabController = tabController 20 | ..tabList = tabList; 21 | } 22 | } 23 | 24 | ProjectState initState(Map args) { 25 | return ProjectState()..tabList = []; 26 | } 27 | -------------------------------------------------------------------------------- /lib/module/project/view.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'project_tab/page.dart'; 5 | import 'state.dart'; 6 | 7 | Widget buildView( 8 | ProjectState state, Dispatch dispatch, ViewService viewService) { 9 | if (state.projectBean != null) { 10 | return ProjectView(data: state); 11 | } else { 12 | return Center(child: CircularProgressIndicator()); 13 | } 14 | } 15 | 16 | ///为了清晰展示TabController初始化,就不单独提取成Widget放在新文件里了 17 | class _ProjectViewState extends State 18 | with SingleTickerProviderStateMixin { 19 | @override 20 | void initState() { 21 | super.initState(); 22 | //演示:初始化自定义的TabBar控制器 23 | widget.data.tabController = TabController( 24 | vsync: this, 25 | length: widget.data.projectBean.data.length, 26 | ); 27 | } 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return Column( 32 | children: [ 33 | //Tab标题 34 | buildTitle(), 35 | 36 | //Tab内容 37 | Expanded(child: buildContent()) 38 | ], 39 | ); 40 | } 41 | 42 | TabBarView buildContent() { 43 | return TabBarView( 44 | controller: widget.data.tabController, 45 | children: widget.data.projectBean.data.asMap().keys.map((index) { 46 | return ProjectTabPage().buildPage( 47 | {"id": widget.data.projectBean.data[index].id}, 48 | ); 49 | }).toList()); 50 | } 51 | 52 | Container buildTitle() { 53 | return Container( 54 | color: Colors.blue, 55 | child: TabBar( 56 | controller: widget.data.tabController, 57 | tabs: widget.data.tabList, 58 | isScrollable: true, 59 | ), 60 | ); 61 | } 62 | } 63 | 64 | class ProjectView extends StatefulWidget { 65 | ProjectView({this.data}); 66 | 67 | final ProjectState data; 68 | 69 | @override 70 | _ProjectViewState createState() => _ProjectViewState(); 71 | } 72 | -------------------------------------------------------------------------------- /lib/module/tree/action.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | //TODO replace with your own action 4 | enum TreeAction { 5 | //更新体系数据 6 | onRefresh, 7 | } 8 | 9 | class TreeActionCreator { 10 | static Action onRefresh() { 11 | return Action(TreeAction.onRefresh); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/module/tree/adapter.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | import 'state.dart'; 4 | import 'tree_item/component.dart'; 5 | 6 | class TreeAdapter extends SourceFlowAdapter { 7 | static String treeItem = "treeItem"; //体系item 8 | 9 | TreeAdapter() 10 | : super(pool: >{ 11 | treeItem: TreeItemComponent(), 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /lib/module/tree/effect.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:dio/dio.dart'; 4 | import 'package:fish_redux/fish_redux.dart'; 5 | import 'package:flutter_wan/bean/tree/tree_info_bean.dart'; 6 | import 'package:flutter_wan/http/api.dart'; 7 | import 'package:flutter_wan/http/http.dart'; 8 | 9 | import 'action.dart'; 10 | import 'state.dart'; 11 | import 'tree_item/state.dart'; 12 | 13 | Effect buildEffect() { 14 | return combineEffects(>{ 15 | Lifecycle.initState: _init, 16 | }); 17 | } 18 | 19 | void _init(Action action, Context ctx) async { 20 | Response response = await Dio().get( 21 | ApiUrl.GET_TREE, 22 | options: await getOptions(), 23 | ); 24 | TreeInfoBean treeInfoBean = 25 | TreeInfoBean.fromJson(json.decode(response.toString())); 26 | List treeItem = treeInfoBean.data; 27 | List itemList = List.generate(treeItem.length, (index) { 28 | return TreeItemState(item: treeItem[index]); 29 | }); 30 | 31 | //更新数据 32 | ctx.state.itemList = itemList; 33 | ctx.dispatch(TreeActionCreator.onRefresh()); 34 | } 35 | -------------------------------------------------------------------------------- /lib/module/tree/page.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | import 'adapter.dart'; 4 | import 'effect.dart'; 5 | import 'reducer.dart'; 6 | import 'state.dart'; 7 | import 'view.dart'; 8 | 9 | class TreePage extends Page> { 10 | TreePage() 11 | : super( 12 | initState: initState, 13 | effect: buildEffect(), 14 | reducer: buildReducer(), 15 | view: buildView, 16 | dependencies: Dependencies( 17 | adapter: NoneConn() + TreeAdapter(), 18 | slots: >{}), 19 | middleware: >[], 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /lib/module/tree/reducer.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | import 'action.dart'; 4 | import 'state.dart'; 5 | 6 | Reducer buildReducer() { 7 | return asReducer( 8 | >{ 9 | TreeAction.onRefresh: _onRefresh, 10 | }, 11 | ); 12 | } 13 | 14 | TreeState _onRefresh(TreeState state, Action action) { 15 | return state.clone(); 16 | } 17 | -------------------------------------------------------------------------------- /lib/module/tree/state.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | import 'adapter.dart'; 4 | import 'tree_item/state.dart'; 5 | 6 | class TreeState extends MutableSource implements Cloneable { 7 | //item数据源 8 | List itemList; 9 | 10 | @override 11 | TreeState clone() { 12 | return TreeState()..itemList = itemList; 13 | } 14 | 15 | @override 16 | Object getItemData(int index) => itemList[index]; 17 | 18 | @override 19 | String getItemType(int index) => TreeAdapter.treeItem; 20 | 21 | @override 22 | int get itemCount => itemList.length; 23 | 24 | @override 25 | void setItemData(int index, Object data) => itemList[index] = data; 26 | } 27 | 28 | TreeState initState(Map args) { 29 | return TreeState()..itemList = []; 30 | } 31 | -------------------------------------------------------------------------------- /lib/module/tree/tree_detail/page.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | import 'state.dart'; 4 | import 'view.dart'; 5 | 6 | class TreeDetailPage extends Page> { 7 | TreeDetailPage() 8 | : super( 9 | initState: initState, 10 | view: buildView, 11 | dependencies: Dependencies( 12 | adapter: null, slots: >{}), 13 | middleware: >[], 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /lib/module/tree/tree_detail/state.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_wan/bean/tree/tree_info_bean.dart'; 4 | 5 | class TreeDetailState implements Cloneable { 6 | //数据 7 | Data treeInfoData; 8 | 9 | //顶部tab数据 10 | List topList; 11 | 12 | //TabBar适配器 13 | TabController tabController; 14 | 15 | @override 16 | TreeDetailState clone() { 17 | return TreeDetailState() 18 | ..tabController = tabController 19 | ..treeInfoData = treeInfoData 20 | ..topList = topList; 21 | } 22 | } 23 | 24 | TreeDetailState initState(Map args) { 25 | Data tempTreeInfo = args["treeDetail"]; 26 | //设置顶部tab 27 | List tabs = List(); 28 | for (int i = 0; i < tempTreeInfo.children.length; i++) { 29 | var child = tempTreeInfo.children[i]; 30 | tabs.add(Tab(text: child.name)); 31 | } 32 | return TreeDetailState() 33 | ..topList = tabs 34 | ..treeInfoData = tempTreeInfo; 35 | } 36 | -------------------------------------------------------------------------------- /lib/module/tree/tree_detail/tree_detail_tab/page.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter_wan/view/component/article_list/component.dart'; 3 | 4 | import 'state.dart'; 5 | import 'view.dart'; 6 | 7 | class TreeDetailTabPage extends Page> { 8 | TreeDetailTabPage() 9 | : super( 10 | initState: initState, 11 | view: buildView, 12 | dependencies: Dependencies( 13 | adapter: null, 14 | slots: >{ 15 | "ArticleList": ArticleConnector() + ArticleListComponent(), 16 | }), 17 | middleware: >[], 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /lib/module/tree/tree_detail/tree_detail_tab/state.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter_wan/view/component/article_list/state.dart'; 3 | 4 | class TreeDetailTabState implements Cloneable { 5 | //文章id 6 | int id; 7 | 8 | //文章数据源 9 | ArticleListState subState; 10 | 11 | @override 12 | TreeDetailTabState clone() { 13 | return TreeDetailTabState()..subState = subState; 14 | } 15 | } 16 | 17 | TreeDetailTabState initState(Map args) { 18 | var id = args["id"]; //获取TabBar穿过来的id 19 | return TreeDetailTabState() 20 | ..id = id 21 | ..subState = ArticleListState( 22 | type: 1, 23 | articleId: id, 24 | ); 25 | } 26 | 27 | ///文章列表连接器 28 | class ArticleConnector extends ConnOp 29 | with ReselectMixin { 30 | @override 31 | ArticleListState computed(TreeDetailTabState state) { 32 | return state.subState.clone(); 33 | } 34 | 35 | @override 36 | void set(TreeDetailTabState state, ArticleListState subState) { 37 | state.subState = subState; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/module/tree/tree_detail/tree_detail_tab/view.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'state.dart'; 5 | 6 | Widget buildView( 7 | TreeDetailTabState state, Dispatch dispatch, ViewService viewService) { 8 | return viewService.buildComponent("ArticleList"); 9 | } 10 | 11 | -------------------------------------------------------------------------------- /lib/module/tree/tree_detail/view.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_wan/app/utils/ui/keep_alive_page.dart'; 4 | 5 | import 'state.dart'; 6 | import 'tree_detail_tab/page.dart'; 7 | 8 | Widget buildView( 9 | TreeDetailState state, Dispatch dispatch, ViewService viewService) { 10 | return TreeDetailView(data: state); 11 | } 12 | 13 | ///fish_redux初始化自定义TabBar控制器 14 | class _TreeDetailViewState extends State 15 | with SingleTickerProviderStateMixin { 16 | @override 17 | void initState() { 18 | super.initState(); 19 | //演示初始化自定义的TabBar控制器 20 | widget.data.tabController = TabController( 21 | vsync: this, 22 | length: widget.data.topList.length, 23 | ); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return Scaffold( 29 | appBar: AppBar( 30 | title: Text(widget.data.treeInfoData.name), 31 | bottom: TabBar( 32 | controller: widget.data.tabController, 33 | tabs: widget.data.topList, 34 | isScrollable: widget.data.topList.length < 5 ? false : true, 35 | ), 36 | ), 37 | body: TabBarView( 38 | controller: widget.data.tabController, 39 | children: widget.data.topList.asMap().keys.map( 40 | (int index) { 41 | return keepAlivePage(TreeDetailTabPage().buildPage({ 42 | "id": widget.data.treeInfoData.children[index].id, 43 | })); 44 | }, 45 | ).toList(), 46 | ), 47 | ); 48 | } 49 | } 50 | 51 | class TreeDetailView extends StatefulWidget { 52 | TreeDetailView({this.data}); 53 | 54 | final TreeDetailState data; 55 | 56 | @override 57 | _TreeDetailViewState createState() => _TreeDetailViewState(); 58 | } 59 | -------------------------------------------------------------------------------- /lib/module/tree/tree_item/action.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | enum TreeItemAction { 4 | toTreeDetail, 5 | } 6 | 7 | class TreeItemActionCreator { 8 | static Action toTreeDetail() { 9 | return Action(TreeItemAction.toTreeDetail); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/module/tree/tree_item/component.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | import 'effect.dart'; 4 | import 'state.dart'; 5 | import 'view.dart'; 6 | 7 | class TreeItemComponent extends Component { 8 | TreeItemComponent() 9 | : super( 10 | effect: buildEffect(), 11 | view: buildView, 12 | dependencies: Dependencies( 13 | adapter: null, slots: >{}), 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /lib/module/tree/tree_item/effect.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter/widgets.dart' hide Action; 3 | import 'package:flutter_wan/app/config/route.dart'; 4 | 5 | import 'action.dart'; 6 | import 'state.dart'; 7 | 8 | Effect buildEffect() { 9 | return combineEffects(>{ 10 | TreeItemAction.toTreeDetail: _toTreeDetail, 11 | }); 12 | } 13 | 14 | void _toTreeDetail(Action action, Context ctx) { 15 | Navigator.pushNamed( 16 | ctx.context, 17 | RouteConfig.treeDetailPage, 18 | arguments: {"treeDetail": ctx.state.item}, 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /lib/module/tree/tree_item/state.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter_wan/bean/tree/tree_info_bean.dart'; 3 | 4 | class TreeItemState implements Cloneable { 5 | Data item; 6 | 7 | TreeItemState({this.item}); 8 | 9 | @override 10 | TreeItemState clone() { 11 | return TreeItemState() 12 | ..item = item; 13 | } 14 | } 15 | 16 | TreeItemState initState(Map args) { 17 | return TreeItemState(); 18 | } 19 | -------------------------------------------------------------------------------- /lib/module/tree/tree_item/view.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | import 'action.dart'; 6 | import 'state.dart'; 7 | import 'widget/tree_item_style.dart'; 8 | 9 | Widget buildView( 10 | TreeItemState state, Dispatch dispatch, ViewService viewService) { 11 | return _body(state, dispatch); 12 | } 13 | 14 | //item布局总样式(处理点击效果,点击事件等) 15 | Widget _body(state, dispatch) { 16 | return TreeItemStyle( 17 | data: state, 18 | onTap: () { 19 | dispatch(TreeItemActionCreator.toTreeDetail()); 20 | }, 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /lib/module/tree/tree_item/widget/tree_item_style.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_wan/app/typedef/function.dart'; 3 | import 'package:flutter_wan/app/utils/ui/ui_adapter.dart'; 4 | 5 | import '../state.dart'; 6 | 7 | class TreeItemStyle extends StatelessWidget { 8 | TreeItemStyle({ 9 | this.data, 10 | this.onTap, 11 | }); 12 | 13 | ///数据源 14 | final TreeItemState data; 15 | 16 | ///点击事件 17 | final ParamVoidCallback onTap; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return _tree(); 22 | } 23 | 24 | Widget _tree() { 25 | return _itemBg( 26 | child: Column( 27 | children: [ 28 | //第一层标题 29 | Container( 30 | alignment: Alignment.topLeft, 31 | margin: EdgeInsets.all(auto(20)), 32 | child: Text(data.item.name), 33 | ), 34 | 35 | //第二层各种类别 36 | Container( 37 | alignment: Alignment.topLeft, 38 | margin: EdgeInsets.only( 39 | left: auto(20), 40 | right: auto(20), 41 | bottom: auto(20), 42 | ), 43 | child: Wrap( 44 | spacing: auto(25), 45 | runSpacing: auto(20), 46 | children: data.item.children.map((e) { 47 | return Text(e.name, style: TextStyle(height: 1)); 48 | }).toList(), 49 | ), 50 | ), 51 | ], 52 | ), 53 | ); 54 | } 55 | 56 | //item布局总样式(处理点击效果,点击事件等) 57 | Widget _itemBg({Widget child}) { 58 | return Card( 59 | shape: RoundedRectangleBorder( 60 | borderRadius: BorderRadius.circular(auto(20)), 61 | ), 62 | child: InkWell( 63 | borderRadius: BorderRadius.circular(auto(20)), 64 | onTap: onTap, 65 | child: child, 66 | )); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/module/tree/view.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'state.dart'; 5 | 6 | Widget buildView(TreeState state, Dispatch dispatch, ViewService viewService) { 7 | if (state.itemList.length != 0) { 8 | return ListView.builder( 9 | itemBuilder: viewService.buildAdapter().itemBuilder, 10 | itemCount: viewService.buildAdapter().itemCount, 11 | ); 12 | } else { 13 | //增加加载动画 14 | return Center(child: CircularProgressIndicator()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/module/web/action.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | enum WebViewAction { 4 | //是否呈现加载动画 5 | isLoading, 6 | //刷新 7 | onRefresh, 8 | //收藏操作 9 | collect, 10 | } 11 | 12 | class WebViewActionCreator { 13 | static Action collect() { 14 | return Action(WebViewAction.collect); 15 | } 16 | 17 | static Action isLoading(bool isLoading) { 18 | return Action(WebViewAction.isLoading, payload: isLoading); 19 | } 20 | 21 | static Action onRefresh() { 22 | return Action(WebViewAction.onRefresh); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/module/web/effect.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:dio/dio.dart'; 4 | import 'package:fish_redux/fish_redux.dart'; 5 | import 'package:flutter_wan/app/utils/show/toast_util.dart'; 6 | import 'package:flutter_wan/bean/user/common_response.dart'; 7 | import 'package:flutter_wan/http/api.dart'; 8 | import 'package:flutter_wan/http/http.dart'; 9 | 10 | import 'action.dart'; 11 | import 'state.dart'; 12 | 13 | Effect buildEffect() { 14 | return combineEffects(>{ 15 | //是否显示加载动画 16 | WebViewAction.isLoading: _isLoading, 17 | //选中和取消收藏操作 18 | WebViewAction.collect: _collect, 19 | }); 20 | } 21 | 22 | void _collect(Action action, Context ctx) async { 23 | var detail = ctx.state.detail; 24 | String url; 25 | //收藏和取消收藏 26 | if (detail.isCollect) { 27 | url = '${ApiUrl.CANCEL_COLLECT}${detail.id.toString()}/json'; 28 | } else { 29 | url = '${ApiUrl.COLLECT}${detail.id.toString()}/json'; 30 | } 31 | var result = await Dio().post( 32 | url, 33 | options: await getOptions(), 34 | ); 35 | //解析结果 36 | CommonResponse response = 37 | CommonResponse.fromJson(json.decode(result.toString())); 38 | if (response.errorCode == 0) { 39 | detail.isCollect = !detail.isCollect; 40 | showToast(detail.isCollect ? '收藏成功' : '取消收藏成功'); 41 | 42 | ctx.dispatch(WebViewActionCreator.onRefresh()); 43 | } else { 44 | showToast(response.errorMsg); 45 | } 46 | } 47 | 48 | void _isLoading(Action action, Context ctx) async { 49 | ctx.state.isLoading = action.payload; 50 | ctx.dispatch(WebViewActionCreator.onRefresh()); 51 | } 52 | -------------------------------------------------------------------------------- /lib/module/web/page.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | import 'effect.dart'; 4 | import 'reducer.dart'; 5 | import 'state.dart'; 6 | import 'view.dart'; 7 | 8 | //展示内容,统一用ArticleWebView展示 9 | class WebViewPage extends Page> { 10 | WebViewPage() 11 | : super( 12 | initState: initState, 13 | reducer: buildReducer(), 14 | effect: buildEffect(), 15 | view: buildView, 16 | dependencies: Dependencies( 17 | adapter: null, slots: >{}), 18 | middleware: >[], 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /lib/module/web/reducer.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | import 'action.dart'; 4 | import 'state.dart'; 5 | 6 | Reducer buildReducer() { 7 | return asReducer( 8 | >{ 9 | WebViewAction.onRefresh: _onRefresh, 10 | }, 11 | ); 12 | } 13 | 14 | WebViewState _onRefresh(WebViewState state, Action action) { 15 | return state.clone(); 16 | } 17 | -------------------------------------------------------------------------------- /lib/module/web/state.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:fish_redux/fish_redux.dart'; 3 | import 'package:flutter_wan/bean/common/article_detail_bean.dart'; 4 | 5 | class WebViewState implements Cloneable { 6 | ArticleDetailBean detail; 7 | bool isLoading; 8 | 9 | @override 10 | WebViewState clone() { 11 | return WebViewState() 12 | ..detail = detail 13 | ..isLoading = isLoading; 14 | } 15 | } 16 | 17 | WebViewState initState(Map args) { 18 | //获取从列表传过来的值 19 | ArticleDetailBean detail = args["articleDetail"]; 20 | return WebViewState() 21 | ..detail = detail 22 | ..isLoading = true; 23 | } 24 | -------------------------------------------------------------------------------- /lib/module/web/view.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'action.dart'; 5 | import 'state.dart'; 6 | import 'widget/common_webview.dart'; 7 | 8 | Widget buildView( 9 | WebViewState state, Dispatch dispatch, ViewService viewService) { 10 | return CommonWebView( 11 | data: state, 12 | onCollect: (){ 13 | //收藏 14 | dispatch(WebViewActionCreator.collect()); 15 | }, 16 | onPageStart: () { 17 | //页面开始加载,打开加载条 18 | dispatch(WebViewActionCreator.isLoading(true)); 19 | }, 20 | onPageEnd: () { 21 | //完成网页加载,关闭加载条 22 | dispatch(WebViewActionCreator.isLoading(false)); 23 | }, 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /lib/module/web/widget/common_webview.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_inappwebview/flutter_inappwebview.dart'; 3 | import 'package:flutter_wan/app/typedef/function.dart'; 4 | import 'package:webview_flutter/webview_flutter.dart'; 5 | 6 | import '../state.dart'; 7 | 8 | class CommonWebView extends StatelessWidget { 9 | CommonWebView({ 10 | this.data, 11 | this.onCollect, 12 | this.onPageEnd, 13 | this.onPageStart, 14 | }); 15 | 16 | ///数据源 17 | final WebViewState data; 18 | 19 | ///收藏 20 | final ParamVoidCallback onCollect; 21 | 22 | ///加载开始 23 | final ParamVoidCallback onPageStart; 24 | 25 | ///加载结束 26 | final ParamVoidCallback onPageEnd; 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return Scaffold( 31 | appBar: AppBar( 32 | title: Text(data.detail.title), 33 | actions: data.detail.isCollect == null 34 | ? null 35 | : [ 36 | IconButton( 37 | icon: Icon( 38 | Icons.favorite, 39 | color: data.detail.isCollect ?? false 40 | ? Colors.deepOrangeAccent 41 | : Colors.white, 42 | ), 43 | onPressed: onCollect, 44 | ), 45 | ], 46 | ), 47 | body: _body(), 48 | ); 49 | } 50 | 51 | Widget _body() { 52 | return Stack( 53 | children: [ 54 | // WebView( 55 | // initialUrl: data.detail.url, 56 | // javascriptMode: JavascriptMode.unrestricted, 57 | // onPageStarted: (url) { 58 | // onPageStart(); 59 | // }, 60 | // onPageFinished: (url) { 61 | // onPageEnd(); 62 | // }, 63 | // ), 64 | InAppWebView( 65 | initialUrlRequest: URLRequest(url: Uri.parse(data.detail.url)), 66 | onLoadStart: (controller, url) { 67 | onPageStart(); 68 | }, 69 | onLoadStop: (controller, url) { 70 | onPageEnd(); 71 | }, 72 | ), 73 | //加载动画 74 | Offstage( 75 | offstage: !data.isLoading, 76 | child: Center( 77 | child: CircularProgressIndicator(), //圆形加载动画 78 | ), 79 | ) 80 | ], 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/view/component/article_list/action.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | enum ArticleListAction { 4 | //打开banner文章内容 5 | openBannerContent, 6 | //上拉加载 7 | onListLoad, 8 | //下拉刷新 9 | onListRefresh, 10 | //界面刷新 11 | onRefresh, 12 | //其他模块可以使用该使用刷新列表 13 | onBoastRefresh, 14 | } 15 | 16 | class ArticleListActionCreator { 17 | static Action onBoastRefresh() { 18 | return Action(ArticleListAction.onBoastRefresh); 19 | } 20 | 21 | static Action onRefresh() { 22 | return Action(ArticleListAction.onRefresh); 23 | } 24 | 25 | static Action onListLoad() { 26 | return Action(ArticleListAction.onListLoad); 27 | } 28 | 29 | static Action onListRefresh() { 30 | return Action(ArticleListAction.onListRefresh); 31 | } 32 | 33 | static Action openBannerContent(int index) { 34 | return Action(ArticleListAction.openBannerContent, payload: index); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/view/component/article_list/adapter.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | import 'effect.dart'; 4 | import 'item/component.dart'; 5 | import 'reducer.dart'; 6 | import 'state.dart'; 7 | 8 | class ArticleListAdapter extends SourceFlowAdapter { 9 | static const String articleType = "articleType"; 10 | 11 | ArticleListAdapter() 12 | : super( 13 | pool: >{ 14 | articleType: ArticleItemComponent(), 15 | }, 16 | reducer: buildReducer(), 17 | effect: buildEffect(), 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /lib/view/component/article_list/component.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | import 'adapter.dart'; 4 | import 'effect.dart'; 5 | import 'reducer.dart'; 6 | import 'state.dart'; 7 | import 'view.dart'; 8 | 9 | class ArticleListComponent extends Component { 10 | ArticleListComponent() 11 | : super( 12 | effect: buildEffect(), 13 | view: buildView, 14 | reducer: buildReducer(), 15 | dependencies: Dependencies( 16 | adapter: NoneConn() + ArticleListAdapter(), 17 | slots: >{}, 18 | ), 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /lib/view/component/article_list/item/action.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | enum ArticleItemAction { 4 | //打开相应的文章内容 5 | openArticleContent, 6 | } 7 | 8 | class ArticleItemActionCreator { 9 | //打开新闻内容 10 | static Action openArticleContent() { 11 | return Action(ArticleItemAction.openArticleContent); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/view/component/article_list/item/component.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | import 'effect.dart'; 4 | import 'state.dart'; 5 | import 'view.dart'; 6 | 7 | class ArticleItemComponent extends Component { 8 | ArticleItemComponent() 9 | : super( 10 | effect: buildEffect(), 11 | view: buildView, 12 | dependencies: Dependencies( 13 | adapter: null, 14 | slots: >{}, 15 | ), 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /lib/view/component/article_list/item/effect.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter/material.dart' hide Action; 3 | import 'package:flutter_wan/app/config/route.dart'; 4 | import 'package:flutter_wan/bean/common/article_detail_bean.dart'; 5 | 6 | import '../action.dart'; 7 | import 'action.dart'; 8 | import 'state.dart'; 9 | 10 | Effect buildEffect() { 11 | return combineEffects(>{ 12 | ArticleItemAction.openArticleContent: _openArticleContent, 13 | }); 14 | } 15 | 16 | void _openArticleContent(Action action, Context ctx) async { 17 | //传递过来的数据 18 | var data = ctx.state.itemDetail; 19 | ArticleDetailBean articleDetailBean = ArticleDetailBean( 20 | url: data.link, 21 | title: data.title, 22 | id: data.id, 23 | isCollect: data.collect, 24 | ); 25 | 26 | await Navigator.pushNamed( 27 | ctx.context, 28 | RouteConfig.webViewPage, 29 | arguments: { 30 | "articleDetail": articleDetailBean, 31 | }, 32 | ); 33 | //广播通知刷新数据 34 | ctx.broadcast(ArticleListActionCreator.onBoastRefresh()); 35 | } 36 | -------------------------------------------------------------------------------- /lib/view/component/article_list/item/state.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter_wan/bean/home/home_article_bean.dart'; 3 | 4 | class ArticleItemState implements Cloneable { 5 | Datas itemDetail; 6 | 7 | ArticleItemState({this.itemDetail}); 8 | 9 | @override 10 | ArticleItemState clone() { 11 | return ArticleItemState()..itemDetail = itemDetail; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/view/component/article_list/item/view.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'action.dart'; 5 | import 'state.dart'; 6 | import 'widget/article_item_detail.dart'; 7 | 8 | Widget buildView( 9 | ArticleItemState state, Dispatch dispatch, ViewService viewService) { 10 | return Container( 11 | child: ArticleItemDetail( 12 | data: state, 13 | onTap: () { 14 | dispatch(ArticleItemActionCreator.openArticleContent()); 15 | }, 16 | ), 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /lib/view/component/article_list/reducer.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | 3 | import 'action.dart'; 4 | import 'state.dart'; 5 | 6 | Reducer buildReducer() { 7 | return asReducer( 8 | >{ 9 | ArticleListAction.onRefresh: _onRefresh, 10 | }, 11 | ); 12 | } 13 | 14 | ArticleListState _onRefresh(ArticleListState state, Action action) { 15 | return state.clone(); 16 | } 17 | -------------------------------------------------------------------------------- /lib/view/component/article_list/state.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter_easyrefresh/easy_refresh.dart'; 3 | 4 | import 'adapter.dart'; 5 | 6 | class ArticleListState extends MutableSource 7 | implements Cloneable { 8 | ///文章列表索引 处理分页逻辑 9 | int articleIndex; 10 | 11 | ///item数据源 12 | List items; 13 | 14 | ///获取文章id 15 | int articleId; 16 | 17 | ///第一次是否加载 18 | bool firstRefresh; 19 | 20 | ///搜索内容 21 | String searchMsg; 22 | 23 | ///刷新控制器 24 | EasyRefreshController controller = EasyRefreshController(); 25 | 26 | ///引用该组件类别 27 | /// 0:首页 1:知识体系 2:搜索 28 | int type; 29 | 30 | ArticleListState({ 31 | this.articleId, 32 | this.firstRefresh = true, 33 | this.searchMsg, 34 | this.articleIndex = 0, 35 | this.items = const [], 36 | this.type = 0, 37 | }); 38 | 39 | @override 40 | ArticleListState clone() { 41 | return ArticleListState() 42 | ..type = type 43 | ..firstRefresh = firstRefresh 44 | ..searchMsg = searchMsg 45 | ..articleId = articleId 46 | ..articleIndex = articleIndex 47 | ..items = items 48 | ..controller = controller; 49 | } 50 | 51 | @override 52 | Object getItemData(int index) => items[index]; 53 | 54 | @override 55 | String getItemType(int index) => ArticleListAdapter.articleType; 56 | 57 | @override 58 | int get itemCount => items.length ?? 0; 59 | 60 | @override 61 | void setItemData(int index, Object data) => items[index] = data; 62 | } 63 | -------------------------------------------------------------------------------- /lib/view/component/article_list/view.dart: -------------------------------------------------------------------------------- 1 | import 'package:fish_redux/fish_redux.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_easyrefresh/easy_refresh.dart'; 4 | 5 | import 'action.dart'; 6 | import 'state.dart'; 7 | 8 | Widget buildView( 9 | ArticleListState state, Dispatch dispatch, ViewService viewService) { 10 | return _body(state, dispatch, viewService); 11 | } 12 | 13 | Widget _body( 14 | ArticleListState state, Dispatch dispatch, ViewService viewService) { 15 | return EasyRefresh( 16 | controller: state.controller, 17 | header: MaterialHeader(), 18 | footer: MaterialFooter(), 19 | child: _articleList(viewService), 20 | firstRefresh: state.firstRefresh, 21 | enableControlFinishRefresh: true, 22 | enableControlFinishLoad: true, 23 | onRefresh: () async { 24 | //下拉刷新 25 | dispatch(ArticleListActionCreator.onListRefresh()); 26 | }, 27 | onLoad: state.type == 0 28 | ? () async { 29 | //上拉加载 30 | dispatch(ArticleListActionCreator.onListLoad()); 31 | } 32 | : null, 33 | ); 34 | } 35 | 36 | ///文章列表 37 | Widget _articleList(ViewService viewService) { 38 | return MediaQuery.removePadding( 39 | removeTop: true, 40 | context: viewService.context, 41 | child: ListView.builder( 42 | shrinkWrap: true, //为true可以解决子控件必须设置高度的问题 43 | physics: NeverScrollableScrollPhysics(), //禁用滑动事件 44 | itemBuilder: viewService.buildAdapter().itemBuilder, 45 | itemCount: viewService.buildAdapter().itemCount, 46 | ), 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /lib/view/widget/input/input_actions.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:focus_widget/focus_widget.dart'; 3 | import 'package:keyboard_actions/keyboard_actions.dart'; 4 | 5 | class InputActions extends StatelessWidget { 6 | InputActions({ 7 | @required this.context, 8 | @required this.focusNode, 9 | @required this.child, 10 | }); 11 | 12 | final BuildContext context; 13 | 14 | ///切记,一个界面存在多个输入控件, 焦点节点对象不能使用同一个 15 | ///每一个不同的TextField控件,都应该对应不同的FocusNode对象 16 | final FocusNode focusNode; 17 | 18 | final Widget child; 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return _body(); 23 | } 24 | 25 | Widget _body() { 26 | return KeyboardActions( 27 | config: buildIOSKeyBoardNumberConfig(context, focusNode), 28 | disableScroll: true, 29 | child: FocusWidget( 30 | child: child, 31 | focusNode: focusNode, 32 | ), 33 | ); 34 | } 35 | } 36 | 37 | /// 配置iOS数字输入框 38 | KeyboardActionsConfig buildIOSKeyBoardNumberConfig( 39 | BuildContext context, FocusNode focusNode) { 40 | return KeyboardActionsConfig( 41 | keyboardActionsPlatform: KeyboardActionsPlatform.IOS, 42 | keyboardBarColor: Colors.grey[200], 43 | nextFocus: false, 44 | actions: [ 45 | KeyboardActionsItem( 46 | focusNode: focusNode, 47 | displayDoneButton: true, 48 | ), 49 | ], 50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | 10 | void fl_register_plugins(FlPluginRegistry* registry) { 11 | } 12 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | ) 7 | 8 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 9 | ) 10 | 11 | set(PLUGIN_BUNDLED_LIBRARIES) 12 | 13 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 14 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 15 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 16 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 18 | endforeach(plugin) 19 | 20 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 21 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 22 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 23 | endforeach(ffi_plugin) 24 | -------------------------------------------------------------------------------- /linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | // Only X11 is currently supported. 5 | // Wayland support is being developed: https://github.com/flutter/flutter/issues/57932. 6 | gdk_set_allowed_backends("x11"); 7 | 8 | g_autoptr(MyApplication) app = my_application_new(); 9 | return g_application_run(G_APPLICATION(app), argc, argv); 10 | } 11 | -------------------------------------------------------------------------------- /linux/my_application.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | struct _MyApplication { 8 | GtkApplication parent_instance; 9 | }; 10 | 11 | G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) 12 | 13 | // Implements GApplication::activate. 14 | static void my_application_activate(GApplication* application) { 15 | GtkWindow* window = 16 | GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); 17 | GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); 18 | gtk_widget_show(GTK_WIDGET(header_bar)); 19 | gtk_header_bar_set_title(header_bar, "flutter_wan"); 20 | gtk_header_bar_set_show_close_button(header_bar, TRUE); 21 | gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); 22 | gtk_window_set_default_size(window, 1280, 720); 23 | gtk_widget_show(GTK_WIDGET(window)); 24 | 25 | g_autoptr(FlDartProject) project = fl_dart_project_new(); 26 | 27 | FlView* view = fl_view_new(project); 28 | gtk_widget_show(GTK_WIDGET(view)); 29 | gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); 30 | 31 | fl_register_plugins(FL_PLUGIN_REGISTRY(view)); 32 | 33 | gtk_widget_grab_focus(GTK_WIDGET(view)); 34 | } 35 | 36 | static void my_application_class_init(MyApplicationClass* klass) { 37 | G_APPLICATION_CLASS(klass)->activate = my_application_activate; 38 | } 39 | 40 | static void my_application_init(MyApplication* self) {} 41 | 42 | MyApplication* my_application_new() { 43 | return MY_APPLICATION(g_object_new(my_application_get_type(), 44 | "application-id", APPLICATION_ID, 45 | nullptr)); 46 | } 47 | -------------------------------------------------------------------------------- /linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/xcuserdata/ 7 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import shared_preferences_macos 9 | 10 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 11 | SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) 12 | } 13 | -------------------------------------------------------------------------------- /macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.11' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 34 | end 35 | 36 | post_install do |installer| 37 | installer.pods_project.targets.each do |target| 38 | flutter_additional_macos_build_settings(target) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @NSApplicationMain 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = flutter_wan 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.tao.flutterWan 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2020 com.tao. All rights reserved. 15 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController.init() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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_wan/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(createApp()); 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 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | book_web 18 | 19 | 20 | 21 | 24 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "book_web", 3 | "short_name": "book_web", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter application.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | 10 | void RegisterPlugins(flutter::PluginRegistry* registry) { 11 | } 12 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | ) 7 | 8 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 9 | ) 10 | 11 | set(PLUGIN_BUNDLED_LIBRARIES) 12 | 13 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 14 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 15 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 16 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 18 | endforeach(plugin) 19 | 20 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 21 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 22 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 23 | endforeach(ffi_plugin) 24 | -------------------------------------------------------------------------------- /windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(runner LANGUAGES CXX) 3 | 4 | add_executable(${BINARY_NAME} WIN32 5 | "flutter_window.cpp" 6 | "main.cpp" 7 | "run_loop.cpp" 8 | "utils.cpp" 9 | "win32_window.cpp" 10 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 11 | "Runner.rc" 12 | "runner.exe.manifest" 13 | ) 14 | apply_standard_settings(${BINARY_NAME}) 15 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 16 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 17 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 18 | add_dependencies(${BINARY_NAME} flutter_assemble) 19 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(RunLoop* run_loop, 8 | const flutter::DartProject& project) 9 | : run_loop_(run_loop), project_(project) {} 10 | 11 | FlutterWindow::~FlutterWindow() {} 12 | 13 | bool FlutterWindow::OnCreate() { 14 | if (!Win32Window::OnCreate()) { 15 | return false; 16 | } 17 | 18 | RECT frame = GetClientArea(); 19 | 20 | // The size here must match the window dimensions to avoid unnecessary surface 21 | // creation / destruction in the startup path. 22 | flutter_controller_ = std::make_unique( 23 | frame.right - frame.left, frame.bottom - frame.top, project_); 24 | // Ensure that basic setup of the controller was successful. 25 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 26 | return false; 27 | } 28 | RegisterPlugins(flutter_controller_->engine()); 29 | run_loop_->RegisterFlutterInstance(flutter_controller_->engine()); 30 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 31 | return true; 32 | } 33 | 34 | void FlutterWindow::OnDestroy() { 35 | if (flutter_controller_) { 36 | run_loop_->UnregisterFlutterInstance(flutter_controller_->engine()); 37 | flutter_controller_ = nullptr; 38 | } 39 | 40 | Win32Window::OnDestroy(); 41 | } 42 | 43 | LRESULT 44 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 45 | WPARAM const wparam, 46 | LPARAM const lparam) noexcept { 47 | // Give Flutter, including plugins, an opporutunity to handle window messages. 48 | if (flutter_controller_) { 49 | std::optional result = 50 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 51 | lparam); 52 | if (result) { 53 | return *result; 54 | } 55 | } 56 | 57 | switch (message) { 58 | case WM_FONTCHANGE: 59 | flutter_controller_->engine()->ReloadSystemFonts(); 60 | break; 61 | } 62 | 63 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 64 | } 65 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "run_loop.h" 10 | #include "win32_window.h" 11 | 12 | // A window that does nothing but host a Flutter view. 13 | class FlutterWindow : public Win32Window { 14 | public: 15 | // Creates a new FlutterWindow driven by the |run_loop|, hosting a 16 | // Flutter view running |project|. 17 | explicit FlutterWindow(RunLoop* run_loop, 18 | const flutter::DartProject& project); 19 | virtual ~FlutterWindow(); 20 | 21 | protected: 22 | // Win32Window: 23 | bool OnCreate() override; 24 | void OnDestroy() override; 25 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 26 | LPARAM const lparam) noexcept override; 27 | 28 | private: 29 | // The run loop driving events for this window. 30 | RunLoop* run_loop_; 31 | 32 | // The project to run. 33 | flutter::DartProject project_; 34 | 35 | // The Flutter instance hosted by this window. 36 | std::unique_ptr flutter_controller_; 37 | }; 38 | 39 | #endif // RUNNER_FLUTTER_WINDOW_H_ 40 | -------------------------------------------------------------------------------- /windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "run_loop.h" 7 | #include "utils.h" 8 | 9 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 10 | _In_ wchar_t *command_line, _In_ int show_command) { 11 | // Attach to console when present (e.g., 'flutter run') or create a 12 | // new console when running with a debugger. 13 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 14 | CreateAndAttachConsole(); 15 | } 16 | 17 | // Initialize COM, so that it is available for use in the library and/or 18 | // plugins. 19 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 20 | 21 | RunLoop run_loop; 22 | 23 | flutter::DartProject project(L"data"); 24 | FlutterWindow window(&run_loop, project); 25 | Win32Window::Point origin(10, 10); 26 | Win32Window::Size size(1280, 720); 27 | if (!window.CreateAndShow(L"flutter_wan", origin, size)) { 28 | return EXIT_FAILURE; 29 | } 30 | window.SetQuitOnClose(true); 31 | 32 | run_loop.Run(); 33 | 34 | ::CoUninitialize(); 35 | return EXIT_SUCCESS; 36 | } 37 | -------------------------------------------------------------------------------- /windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdd666t/flutter_wan/70fb16ee0b1af4394f6e59121efb4aa862715d3c/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /windows/runner/run_loop.cpp: -------------------------------------------------------------------------------- 1 | #include "run_loop.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | RunLoop::RunLoop() {} 8 | 9 | RunLoop::~RunLoop() {} 10 | 11 | void RunLoop::Run() { 12 | bool keep_running = true; 13 | TimePoint next_flutter_event_time = TimePoint::clock::now(); 14 | while (keep_running) { 15 | std::chrono::nanoseconds wait_duration = 16 | std::max(std::chrono::nanoseconds(0), 17 | next_flutter_event_time - TimePoint::clock::now()); 18 | ::MsgWaitForMultipleObjects( 19 | 0, nullptr, FALSE, static_cast(wait_duration.count() / 1000), 20 | QS_ALLINPUT); 21 | bool processed_events = false; 22 | MSG message; 23 | // All pending Windows messages must be processed; MsgWaitForMultipleObjects 24 | // won't return again for items left in the queue after PeekMessage. 25 | while (::PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) { 26 | processed_events = true; 27 | if (message.message == WM_QUIT) { 28 | keep_running = false; 29 | break; 30 | } 31 | ::TranslateMessage(&message); 32 | ::DispatchMessage(&message); 33 | // Allow Flutter to process messages each time a Windows message is 34 | // processed, to prevent starvation. 35 | next_flutter_event_time = 36 | std::min(next_flutter_event_time, ProcessFlutterMessages()); 37 | } 38 | // If the PeekMessage loop didn't run, process Flutter messages. 39 | if (!processed_events) { 40 | next_flutter_event_time = 41 | std::min(next_flutter_event_time, ProcessFlutterMessages()); 42 | } 43 | } 44 | } 45 | 46 | void RunLoop::RegisterFlutterInstance( 47 | flutter::FlutterEngine* flutter_instance) { 48 | flutter_instances_.insert(flutter_instance); 49 | } 50 | 51 | void RunLoop::UnregisterFlutterInstance( 52 | flutter::FlutterEngine* flutter_instance) { 53 | flutter_instances_.erase(flutter_instance); 54 | } 55 | 56 | RunLoop::TimePoint RunLoop::ProcessFlutterMessages() { 57 | TimePoint next_event_time = TimePoint::max(); 58 | for (auto instance : flutter_instances_) { 59 | std::chrono::nanoseconds wait_duration = instance->ProcessMessages(); 60 | if (wait_duration != std::chrono::nanoseconds::max()) { 61 | next_event_time = 62 | std::min(next_event_time, TimePoint::clock::now() + wait_duration); 63 | } 64 | } 65 | return next_event_time; 66 | } 67 | -------------------------------------------------------------------------------- /windows/runner/run_loop.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_RUN_LOOP_H_ 2 | #define RUNNER_RUN_LOOP_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | // A runloop that will service events for Flutter instances as well 10 | // as native messages. 11 | class RunLoop { 12 | public: 13 | RunLoop(); 14 | ~RunLoop(); 15 | 16 | // Prevent copying 17 | RunLoop(RunLoop const&) = delete; 18 | RunLoop& operator=(RunLoop const&) = delete; 19 | 20 | // Runs the run loop until the application quits. 21 | void Run(); 22 | 23 | // Registers the given Flutter instance for event servicing. 24 | void RegisterFlutterInstance( 25 | flutter::FlutterEngine* flutter_instance); 26 | 27 | // Unregisters the given Flutter instance from event servicing. 28 | void UnregisterFlutterInstance( 29 | flutter::FlutterEngine* flutter_instance); 30 | 31 | private: 32 | using TimePoint = std::chrono::steady_clock::time_point; 33 | 34 | // Processes all currently pending messages for registered Flutter instances. 35 | TimePoint ProcessFlutterMessages(); 36 | 37 | std::set flutter_instances_; 38 | }; 39 | 40 | #endif // RUNNER_RUN_LOOP_H_ 41 | -------------------------------------------------------------------------------- /windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | // Creates a console for the process, and redirects stdout and stderr to 5 | // it for both the runner and the Flutter library. 6 | void CreateAndAttachConsole(); 7 | 8 | #endif // RUNNER_UTILS_H_ 9 | --------------------------------------------------------------------------------