├── .DS_Store ├── .flutter-plugins ├── .flutter-plugins-dependencies ├── .gitignore ├── .metadata ├── .packages ├── LICENSE ├── README.md ├── android ├── .DS_Store ├── .gitignore ├── app │ ├── .DS_Store │ ├── build.gradle │ ├── release │ │ ├── app-release.apk │ │ └── output.json │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── xfhy │ │ │ │ └── wanandroidflutter │ │ │ │ └── MainActivity.java │ │ └── res │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── mykey.jks └── settings.gradle ├── doc ├── 1. 遇到的一些问题.md └── TODO.md ├── images ├── ic_default_avatar.webp ├── ic_launcher.png ├── ic_launcher_foreground.png └── ic_zone_background.webp ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── Runner │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── main.m ├── lib ├── common │ └── application.dart ├── constant │ ├── api.dart │ ├── app_colors.dart │ ├── constants.dart │ └── routes.dart ├── data │ ├── data_utils.dart │ ├── http_util.dart │ └── model │ │ ├── .DS_Store │ │ ├── article_data_entity.dart │ │ ├── banner_data.dart │ │ ├── hot_key_entity.dart │ │ ├── knowledge_entity.dart │ │ └── login_data_entity.dart ├── demo │ ├── bottom_navigation_bar.dart │ ├── flutter_webview_demo.dart │ ├── request_data.dart │ └── sliver_app_bar.dart ├── generated │ └── json │ │ ├── article_data_entity_helper.dart │ │ ├── base │ │ ├── json_convert_content.dart │ │ └── json_filed.dart │ │ ├── hot_key_entity_helper.dart │ │ ├── knowledge_entity_helper.dart │ │ └── login_data_entity_helper.dart ├── main.dart ├── page │ ├── about │ │ └── about_page.dart │ ├── favorite │ │ └── my_favorite_page.dart │ ├── home_list_page.dart │ ├── item │ │ └── article_item.dart │ ├── knowledge │ │ ├── knowledge_page.dart │ │ └── knowledge_page_data.dart │ ├── knowledge_system_tree_page.dart │ ├── login │ │ ├── login_event.dart │ │ └── login_page.dart │ ├── myinfo_page.dart │ ├── question │ │ └── question_page.dart │ ├── search │ │ ├── hot_search_widget.dart │ │ ├── search_list_widget.dart │ │ └── search_page.dart │ ├── wan_android_page.dart │ └── webview │ │ ├── route_web_page_data.dart │ │ └── web_view_page.dart ├── util │ ├── log_util.dart │ ├── shared_preferences.dart │ └── tool_utils.dart └── widget │ ├── home_banner.dart │ ├── refresh │ └── refresh_page.dart │ └── stroke_widget.dart ├── pic ├── image1.png ├── image2.png ├── image3.png ├── image4.png ├── image5.png └── image6.png ├── pubspec.lock ├── pubspec.yaml └── test └── widget_test.dart /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/.DS_Store -------------------------------------------------------------------------------- /.flutter-plugins: -------------------------------------------------------------------------------- 1 | # This is a generated file; do not edit or check into version control. 2 | flutter_webview_plugin=/Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/flutter_webview_plugin-0.3.10+1/ 3 | fluttertoast=/Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/fluttertoast-4.0.1/ 4 | path_provider=/Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/path_provider-1.6.5/ 5 | path_provider_macos=/Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/path_provider_macos-0.0.4/ 6 | shared_preferences=/Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences-0.5.6+3/ 7 | shared_preferences_macos=/Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences_macos-0.0.1+6/ 8 | shared_preferences_web=/Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences_web-0.1.2+4/ 9 | url_launcher=/Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/url_launcher-5.4.2/ 10 | url_launcher_macos=/Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/url_launcher_macos-0.0.1+4/ 11 | url_launcher_web=/Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/url_launcher_web-0.1.1+1/ 12 | -------------------------------------------------------------------------------- /.flutter-plugins-dependencies: -------------------------------------------------------------------------------- 1 | {"_info":"// This is a generated file; do not edit or check into version control.","dependencyGraph":[{"name":"flutter_webview_plugin","dependencies":[]},{"name":"fluttertoast","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos"]},{"name":"path_provider_macos","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_macos","shared_preferences_web"]},{"name":"shared_preferences_macos","dependencies":[]},{"name":"shared_preferences_web","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_web","url_launcher_macos"]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_web","dependencies":[]}]} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.aar 3 | *.ap_ 4 | *.aab 5 | 6 | # Files for the ART/Dalvik VM 7 | *.dex 8 | 9 | # Java class files 10 | *.class 11 | 12 | # Generated files 13 | bin/ 14 | gen/ 15 | out/ 16 | # Uncomment the following line in case you need and you don't have the release build type files in your app 17 | # release/ 18 | 19 | # Gradle files 20 | .gradle/ 21 | build/ 22 | 23 | # Local configuration file (sdk path, etc) 24 | local.properties 25 | 26 | # Proguard folder generated by Eclipse 27 | proguard/ 28 | 29 | # Log Files 30 | *.log 31 | 32 | # Android Studio Navigation editor temp files 33 | .navigation/ 34 | 35 | # Android Studio captures folder 36 | captures/ 37 | 38 | # IntelliJ 39 | *.iml 40 | .idea/ 41 | 42 | .dart_tool/ 43 | 44 | # Keystore files 45 | # Uncomment the following lines if you do not want to check your keystore files in. 46 | #*.jks 47 | #*.keystore 48 | 49 | # External native build folder generated in Android Studio 2.2 and later 50 | .externalNativeBuild 51 | .cxx/ 52 | 53 | # Google Services (e.g. APIs or Firebase) 54 | # google-services.json 55 | 56 | # Freeline 57 | freeline.py 58 | freeline/ 59 | freeline_project_description.json 60 | 61 | # fastlane 62 | fastlane/report.xml 63 | fastlane/Preview.html 64 | fastlane/screenshots 65 | fastlane/test_output 66 | fastlane/readme.md 67 | 68 | # Version control 69 | vcs.xml 70 | 71 | # lint 72 | lint/intermediates/ 73 | lint/generated/ 74 | lint/outputs/ 75 | lint/tmp/ 76 | # lint/reports/ 77 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.packages: -------------------------------------------------------------------------------- 1 | # Generated by pub on 2020-03-29 11:13:06.338022. 2 | archive:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/archive-2.0.11/lib/ 3 | args:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/args-1.5.2/lib/ 4 | async:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/async-2.4.0/lib/ 5 | boolean_selector:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/boolean_selector-1.0.5/lib/ 6 | charcode:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/charcode-1.1.2/lib/ 7 | collection:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/collection-1.14.11/lib/ 8 | convert:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/convert-2.1.1/lib/ 9 | cookie_jar:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/cookie_jar-1.0.1/lib/ 10 | crypto:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/crypto-2.1.3/lib/ 11 | cupertino_icons:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/cupertino_icons-0.1.3/lib/ 12 | dio:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/dio-3.0.9/lib/ 13 | dio_cookie_manager:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/dio_cookie_manager-1.0.0/lib/ 14 | event_bus:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/event_bus-1.1.1/lib/ 15 | flutter:file:///Users/xfhy/development/flutter/packages/flutter/lib/ 16 | flutter_spinkit:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/flutter_spinkit-4.1.2/lib/ 17 | flutter_test:file:///Users/xfhy/development/flutter/packages/flutter_test/lib/ 18 | flutter_web_plugins:file:///Users/xfhy/development/flutter/packages/flutter_web_plugins/lib/ 19 | flutter_webview_plugin:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/flutter_webview_plugin-0.3.10+1/lib/ 20 | fluttertoast:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/fluttertoast-4.0.1/lib/ 21 | http_parser:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/http_parser-3.1.3/lib/ 22 | image:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/image-2.1.4/lib/ 23 | matcher:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/matcher-0.12.6/lib/ 24 | meta:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/meta-1.1.8/lib/ 25 | path:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/path-1.6.4/lib/ 26 | path_provider:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/path_provider-1.6.5/lib/ 27 | path_provider_macos:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/path_provider_macos-0.0.4/lib/ 28 | path_provider_platform_interface:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/path_provider_platform_interface-1.0.1/lib/ 29 | pedantic:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/pedantic-1.8.0+1/lib/ 30 | petitparser:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/petitparser-2.4.0/lib/ 31 | platform:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/platform-2.2.1/lib/ 32 | plugin_platform_interface:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/plugin_platform_interface-1.0.2/lib/ 33 | quiver:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/quiver-2.0.5/lib/ 34 | shared_preferences:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences-0.5.6+3/lib/ 35 | shared_preferences_macos:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences_macos-0.0.1+6/lib/ 36 | shared_preferences_platform_interface:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences_platform_interface-1.0.3/lib/ 37 | shared_preferences_web:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences_web-0.1.2+4/lib/ 38 | sky_engine:file:///Users/xfhy/development/flutter/bin/cache/pkg/sky_engine/lib/ 39 | source_span:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/source_span-1.5.5/lib/ 40 | sprintf:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/sprintf-4.0.2/lib/ 41 | stack_trace:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/stack_trace-1.9.3/lib/ 42 | stream_channel:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/stream_channel-2.0.0/lib/ 43 | string_scanner:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/string_scanner-1.0.5/lib/ 44 | term_glyph:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/term_glyph-1.1.0/lib/ 45 | test_api:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/test_api-0.2.11/lib/ 46 | typed_data:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/typed_data-1.1.6/lib/ 47 | url_launcher:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/url_launcher-5.4.2/lib/ 48 | url_launcher_macos:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/url_launcher_macos-0.0.1+4/lib/ 49 | url_launcher_platform_interface:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/url_launcher_platform_interface-1.0.6/lib/ 50 | url_launcher_web:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/url_launcher_web-0.1.1+1/lib/ 51 | vector_math:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/vector_math-2.0.8/lib/ 52 | xml:file:///Users/xfhy/development/flutter/.pub-cache/hosted/pub.flutter-io.cn/xml-3.5.0/lib/ 53 | wanandroidflutter:lib/ 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![image](https://img.shields.io/badge/api-%2B16-blue.svg) 2 | ![image](https://img.shields.io/badge/license-Apache2.0-blue.svg) 3 | [![image](https://img.shields.io/badge/author-xfhy-orange.svg)](https://github.com/xfhy) 4 | [![image](https://img.shields.io/badge/CSDN-潇风寒月-orange.svg)](https://blog.csdn.net/xfhy_) 5 | 6 | ## 1. WanAndroid-Flutter 7 | 8 | Flutter版本 WanAndroid客户端,适合Flutter入门学习.该项目是我在学习Flutter过程中写的. 9 | 10 | ## 2. 技术点 11 | 12 | - 封装 上拉加载,下拉刷新 13 | - dio进行网络请求,统一封装get,post 14 | - 封装banner 15 | - Future 16 | - 路由,跳转界面 17 | - 事件总线 event_bus 18 | - toast 19 | - SharedPreference 20 | - .... 21 | 22 | ## 3. 项目截图 23 | 24 |

25 | 26 | 27 | 28 | 29 | 30 | 31 |

32 | 33 | release 版本[试玩地址](http://d.alphaqr.com/3s7z) 34 | 35 | ![](https://s1.ax1x.com/2020/04/05/Gr1I0I.png) 36 | 37 | ## 4. 遇到的一些问题 38 | 39 | ### 4.1 引入第三方库 40 | 41 | 1. 首先去[官网package](https://pub.dev/packages)搜索. 42 | 2. 找到相应插件,点进详情,切换到Installing tab,然后在pubspec.yaml中引入该插件. 43 | 3. 在本项目控制台,输入flutter pub get. 即引入三方库完成. 44 | 45 | ### 4.2 loading 46 | 47 | 当页面正在loading时,需要一个Widget来占位,不然Widget为空要报错. 48 | 49 | ### 4.3 运行在iOS上 50 | 51 | 1. 首先得安装Xcode(7.8G) 52 | 2. 然后安装 cocoapods `sudo gem install cocoapods` 53 | 3. 然后在ios工程下,执行`pod install`,引入那些依赖 54 | 4. 然后用AS打开ios项目里面的Info.plist,点击右上角的用Xcode打开. 55 | 5. 编辑Podfile,将顶部的`platform :ios, '9.0'` 注释放开 56 | 6. 运行到模拟器上. 57 | 58 | ### 4.4 如何快速解析json 59 | 60 | > Flutter不支持运行时反射,所以没有像Gson这样自动解析JSON的库来降低解析成本.在Flutter中解析JSON需要完全手动进行操作,麻烦. 61 | 62 | 可以在AS上装FlutterJsonBeanFactory这个插件,然后右键New->JsonToDartBeanAction,输入文件名和json数据.即可自动生成bean对象,和它所对应的解析代码. 63 | 64 | 原理是它生成了一个JsonConvert,然后这里面可以根据运行时type去选择应该解析哪一个类对象. 65 | 然后bean类在声明的时候是混入了JsonConvert的,可以直接使用JsonConvert里面的方法,完美. 66 | 67 | ### 4.5 Flutter ScrollView (滚动视图) 68 | 69 | ScrollView是一个带有滚动的视图组件,它本身由三部分组成 70 | 71 | - Scrollable - 它监听各种用户手势并实现滚动的交互设计。 72 | - Viewport - 它通过在滚动视图内仅显示一部分小部件来实现滚动的可视化设计。 73 | - Slider - 它们是可以组合以创建各种滚动效果的小部件,如列表,网格和扩展标题。 74 | 75 | Scroll是一个抽象类,通常使用CustomScrollView 76 | 77 | ``` 78 | CustomScrollView( 79 | shrinkWrap: true, 80 | // 内容 81 | slivers: [ 82 | new SliverPadding( 83 | padding: const EdgeInsets.all(20.0), 84 | sliver: new SliverList( 85 | delegate: new SliverChildListDelegate( 86 | [ 87 | const Text('A'), 88 | const Text('B'), 89 | const Text('C'), 90 | const Text('D'), 91 | ], 92 | ), 93 | ), 94 | ), 95 | ], 96 | ) 97 | ``` 98 | 99 | ### 4.6 处理Text超出问题 100 | 101 | 可以放Row或Column中,用Expanded包起来,然后用maxLines控制行数,用`overflow: 102 | TextOverflow.ellipsis,`控制超出部分的展示. 103 | 104 | ### 4.7 让一个ListView支持下拉刷新 105 | 106 | 非常简单, 107 | 使用官方自带的RefreshIndicator即可,将listview放child,然后实现一个_pullToRefresh下拉刷新时调用的方法(做下拉刷新的逻辑). 108 | 109 | ``` 110 | RefreshIndicator( 111 | child: listView, 112 | onRefresh: _pullToRefresh, 113 | ); 114 | 115 | Future _pullToRefresh() { 116 | loadData(); 117 | //这里Feature不能返回 null 118 | return Future(() => LogUtil.d("lalala")); 119 | } 120 | ``` 121 | 122 | ### 4.8 获取屏幕宽度,高度 123 | 124 | ``` 125 | MediaQuery.of(context).size.width, 126 | MediaQuery.of(context).size.height 127 | ``` 128 | 129 | ### 4.9 封装通用标题栏 130 | 131 | 标题栏,每个界面都需要,所以封装一个,取需. 132 | 133 | ```dart 134 | ///get通用状态栏 135 | static AppBar getCommonAppBar(BuildContext context, String title, {double fontSize, List actions}) { 136 | if (title == null) { 137 | title = ""; 138 | } 139 | return AppBar( 140 | leading: IconButton( 141 | icon: Icon( 142 | Icons.arrow_back, 143 | color: Colors.white, 144 | ), 145 | //点击返回 146 | onPressed: () { 147 | if (context != null) { 148 | Navigator.pop(context); 149 | } 150 | }, 151 | ), 152 | title: Text( 153 | title, 154 | style: TextStyle( 155 | color: Colors.white, 156 | fontSize: fontSize == null ? 18.0 : fontSize, 157 | ), 158 | ), 159 | //标题栏居中 160 | centerTitle: true, 161 | //右边的action 按钮 162 | actions: actions == null ? [] : actions, 163 | ); 164 | } 165 | ``` 166 | 167 | ### 4.10 格式化String 168 | 169 | dart中格式化String,需要引入三方库`sprintf`,使用方式如下: 170 | 171 | ```dart 172 | sprintf("lg/collect/%s/json", [15615]); 173 | ``` 174 | 175 | ### 4.11 获取Android/iOS本地目录 176 | 177 | 需要引入三方库`path_provider`,用于查找文件系统上的常用位置,支持Android和iOS.免得去写一原生代码,这个三方库帮我们封装好了. 178 | 179 | ```dart 180 | Directory tempDir = await getTemporaryDirectory(); 181 | String tempPath = tempDir.path; 182 | 183 | Directory appDocDir = await getApplicationDocumentsDirectory(); 184 | String appDocPath = appDocDir.path; 185 | ``` 186 | 187 | ### 4.12 展示一个Dialog 188 | 189 | 以下方法是dart的material包下面的方法. 190 | 191 | ```dart 192 | //展示对话框 193 | showDialog( 194 | context: context, 195 | barrierDismissible: false, 196 | builder: (_) { 197 | return SpinKitFadingCircle( 198 | color: AppColors.colorPrimary, 199 | ); 200 | }); 201 | 202 | //取消对话框 203 | Navigator.of(context).pop(); 204 | ``` 205 | 206 | ### 4.13 间距的简单方式 207 | 208 | 可以用Padding和margin来实现.其实还有一种方式,可以在Column和Row中快速增加一段间距,利用SizedBox,类似Android中的Space 209 | 210 | ```dart 211 | SizedBox(width: 10.0), 212 | ``` 213 | 214 | ### 4.14 收起软键盘 215 | 216 | 有时候需要在点击某些按钮时收起软键盘 217 | ```dart 218 | FocusScope.of(context).requestFocus(FocusNode()); 219 | ``` 220 | 221 | ### 4.15 让ListView的item点击时有水波纹效果 222 | 223 | 用InkWell把Item包起来 224 | 225 | 226 | -------------------------------------------------------------------------------- /android/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/android/.DS_Store -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /android/app/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/android/app/.DS_Store -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 28 29 | 30 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | 34 | defaultConfig { 35 | applicationId "com.xfhy.wanandroidflutter" 36 | minSdkVersion 16 37 | targetSdkVersion 28 38 | versionCode flutterVersionCode.toInteger() 39 | versionName flutterVersionName 40 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 41 | } 42 | 43 | signingConfigs { 44 | release { 45 | keyAlias "xfhy" 46 | keyPassword "11111111" 47 | storeFile file("../mykey.jks") 48 | storePassword "11111111" 49 | } 50 | } 51 | 52 | buildTypes { 53 | release { 54 | // Signing with the debug keys for now, so `flutter run --release` works. 55 | signingConfig signingConfigs.release 56 | 57 | ndk { // 必须加入这部分,否则可能导致编译成功的release包在真机中会闪退 58 | abiFilters "armeabi-v7a" 59 | } 60 | } 61 | 62 | debug { 63 | ndk { 64 | //这里要加上,否则debug包会出问题,后面三个为可选,x86建议加上不然部分模拟器回报错 65 | abiFilters "armeabi", "armeabi-v7a", "arm64-v8a", "x86" 66 | } 67 | } 68 | 69 | } 70 | } 71 | 72 | flutter { 73 | source '../..' 74 | } 75 | 76 | dependencies { 77 | testImplementation 'junit:junit:4.12' 78 | androidTestImplementation 'androidx.test:runner:1.1.1' 79 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 80 | } 81 | -------------------------------------------------------------------------------- /android/app/release/app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/android/app/release/app-release.apk -------------------------------------------------------------------------------- /android/app/release/output.json: -------------------------------------------------------------------------------- 1 | [{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":1,"versionName":"1.0.0","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 9 | 10 | 11 | 15 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/xfhy/wanandroidflutter/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.xfhy.wanandroidflutter; 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 | public class MainActivity extends FlutterActivity { 9 | @Override 10 | public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { 11 | GeneratedPluginRegistrant.registerWith(flutterEngine); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /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 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.5.0' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /android/mykey.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/android/mykey.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 | -------------------------------------------------------------------------------- /doc/1. 遇到的一些问题.md: -------------------------------------------------------------------------------- 1 | ### 1. 引入第三方库 2 | 3 | 1. 首先去[官网package](https://pub.dev/packages)搜索. 4 | 2. 找到相应插件,点进详情,切换到Installing tab,然后在pubspec.yaml中引入该插件. 5 | 3. 在本项目控制台,输入flutter pub get. 即引入三方库完成. 6 | 7 | ### 2. loading 8 | 9 | 当页面正在loading时,需要一个Widget来占位,不然Widget为空要报错. 10 | 11 | ### 3. 运行在iOS上 12 | 13 | 1. 首先得安装Xcode(7.8G) 14 | 2. 然后安装 cocoapods `sudo gem install cocoapods` 15 | 3. 然后在ios工程下,执行`pod install`,引入那些依赖 16 | 4. 然后用AS打开ios项目里面的Info.plist,点击右上角的用Xcode打开. 17 | 5. 编辑Podfile,将顶部的`platform :ios, '9.0'` 注释放开 18 | 6. 运行到模拟器上. 19 | 20 | ### 4. 如何快速解析json 21 | 22 | > Flutter不支持运行时反射,所以没有像Gson这样自动解析JSON的库来降低解析成本.在Flutter中解析JSON需要完全手动进行操作,麻烦. 23 | 24 | 可以在AS上装FlutterJsonBeanFactory这个插件,然后右键New->JsonToDartBeanAction,输入文件名和json数据.即可自动生成bean对象,和它所对应的解析代码. 25 | 26 | 原理是它生成了一个JsonConvert,然后这里面可以根据运行时type去选择应该解析哪一个类对象. 27 | 然后bean类在声明的时候是混入了JsonConvert的,可以直接使用JsonConvert里面的方法,完美. 28 | 29 | ### 5. Flutter ScrollView (滚动视图) 30 | 31 | ScrollView是一个带有滚动的视图组件,它本身由三部分组成 32 | 33 | - Scrollable - 它监听各种用户手势并实现滚动的交互设计。 34 | - Viewport - 它通过在滚动视图内仅显示一部分小部件来实现滚动的可视化设计。 35 | - Slider - 它们是可以组合以创建各种滚动效果的小部件,如列表,网格和扩展标题。 36 | 37 | Scroll是一个抽象类,通常使用CustomScrollView 38 | 39 | ``` 40 | CustomScrollView( 41 | shrinkWrap: true, 42 | // 内容 43 | slivers: [ 44 | new SliverPadding( 45 | padding: const EdgeInsets.all(20.0), 46 | sliver: new SliverList( 47 | delegate: new SliverChildListDelegate( 48 | [ 49 | const Text('A'), 50 | const Text('B'), 51 | const Text('C'), 52 | const Text('D'), 53 | ], 54 | ), 55 | ), 56 | ), 57 | ], 58 | ) 59 | ``` 60 | 61 | ### 6. 处理Text超出问题 62 | 63 | 可以放Row或Column中,用Expanded包起来,然后用maxLines控制行数,用`overflow: 64 | TextOverflow.ellipsis,`控制超出部分的展示. 65 | 66 | ### 7. 让一个ListView支持下拉刷新 67 | 68 | 非常简单, 69 | 使用官方自带的RefreshIndicator即可,将listview放child,然后实现一个_pullToRefresh下拉刷新时调用的方法(做下拉刷新的逻辑). 70 | 71 | ``` 72 | RefreshIndicator( 73 | child: listView, 74 | onRefresh: _pullToRefresh, 75 | ); 76 | 77 | Future _pullToRefresh() { 78 | loadData(); 79 | //这里Feature不能返回 null 80 | return Future(() => LogUtil.d("lalala")); 81 | } 82 | ``` 83 | 84 | ### 8. 获取屏幕宽度,高度 85 | 86 | ``` 87 | MediaQuery.of(context).size.width, 88 | MediaQuery.of(context).size.height 89 | ``` 90 | 91 | ### 9. 封装通用标题栏 92 | 93 | 标题栏,每个界面都需要,所以封装一个,取需. 94 | 95 | ```dart 96 | ///get通用状态栏 97 | static AppBar getCommonAppBar(BuildContext context, String title, {double fontSize, List actions}) { 98 | if (title == null) { 99 | title = ""; 100 | } 101 | return AppBar( 102 | leading: IconButton( 103 | icon: Icon( 104 | Icons.arrow_back, 105 | color: Colors.white, 106 | ), 107 | //点击返回 108 | onPressed: () { 109 | if (context != null) { 110 | Navigator.pop(context); 111 | } 112 | }, 113 | ), 114 | title: Text( 115 | title, 116 | style: TextStyle( 117 | color: Colors.white, 118 | fontSize: fontSize == null ? 18.0 : fontSize, 119 | ), 120 | ), 121 | //标题栏居中 122 | centerTitle: true, 123 | //右边的action 按钮 124 | actions: actions == null ? [] : actions, 125 | ); 126 | } 127 | ``` 128 | 129 | ### 10. 格式化String 130 | 131 | dart中格式化String,需要引入三方库`sprintf`,使用方式如下: 132 | 133 | ```dart 134 | sprintf("lg/collect/%s/json", [15615]); 135 | ``` 136 | 137 | ### 11. 获取Android/iOS本地目录 138 | 139 | 需要引入三方库`path_provider`,用于查找文件系统上的常用位置,支持Android和iOS.免得去写一原生代码,这个三方库帮我们封装好了. 140 | 141 | ```dart 142 | Directory tempDir = await getTemporaryDirectory(); 143 | String tempPath = tempDir.path; 144 | 145 | Directory appDocDir = await getApplicationDocumentsDirectory(); 146 | String appDocPath = appDocDir.path; 147 | ``` 148 | 149 | ### 12. 展示一个Dialog 150 | 151 | 以下方法是dart的material包下面的方法. 152 | 153 | ```dart 154 | //展示对话框 155 | showDialog( 156 | context: context, 157 | barrierDismissible: false, 158 | builder: (_) { 159 | return SpinKitFadingCircle( 160 | color: AppColors.colorPrimary, 161 | ); 162 | }); 163 | 164 | //取消对话框 165 | Navigator.of(context).pop(); 166 | ``` 167 | 168 | ### 13. 间距的简单方式 169 | 170 | 可以用Padding和margin来实现.其实还有一种方式,可以在Column和Row中快速增加一段间距,利用SizedBox,类似Android中的Space 171 | 172 | ```dart 173 | SizedBox(width: 10.0), 174 | ``` 175 | 176 | ### 14. 收起软键盘 177 | 178 | 有时候需要在点击某些按钮时收起软键盘 179 | ```dart 180 | FocusScope.of(context).requestFocus(FocusNode()); 181 | ``` 182 | 183 | ### 15. 让ListView的item点击时有水波纹效果 184 | 185 | 用InkWell把Item包起来 186 | -------------------------------------------------------------------------------- /doc/TODO.md: -------------------------------------------------------------------------------- 1 | 2 | ### 开发 3 | 4 | - 开屏页 5 | 6 | ### 其他 7 | 8 | 1. webview页面,打开一些网址会出现无法加载的情况,提供一个默认失败页面. 9 | 2. 开屏页 10 | -------------------------------------------------------------------------------- /images/ic_default_avatar.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/images/ic_default_avatar.webp -------------------------------------------------------------------------------- /images/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/images/ic_launcher.png -------------------------------------------------------------------------------- /images/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/images/ic_launcher_foreground.png -------------------------------------------------------------------------------- /images/ic_zone_background.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/images/ic_zone_background.webp -------------------------------------------------------------------------------- /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 parse_KV_file(file, separator='=') 14 | file_abs_path = File.expand_path(file) 15 | if !File.exists? file_abs_path 16 | return []; 17 | end 18 | generated_key_values = {} 19 | skip_line_start_symbols = ["#", "/"] 20 | File.foreach(file_abs_path) do |line| 21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 22 | plugin = line.split(pattern=separator) 23 | if plugin.length == 2 24 | podname = plugin[0].strip() 25 | path = plugin[1].strip() 26 | podpath = File.expand_path("#{path}", file_abs_path) 27 | generated_key_values[podname] = podpath 28 | else 29 | puts "Invalid plugin specification: #{line}" 30 | end 31 | end 32 | generated_key_values 33 | end 34 | 35 | target 'Runner' do 36 | # Flutter Pod 37 | 38 | copied_flutter_dir = File.join(__dir__, 'Flutter') 39 | copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') 40 | copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') 41 | unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) 42 | # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. 43 | # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. 44 | # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. 45 | 46 | generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') 47 | unless File.exist?(generated_xcode_build_settings_path) 48 | raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" 49 | end 50 | generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) 51 | cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; 52 | 53 | unless File.exist?(copied_framework_path) 54 | FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) 55 | end 56 | unless File.exist?(copied_podspec_path) 57 | FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) 58 | end 59 | end 60 | 61 | # Keep pod path relative so it can be checked into Podfile.lock. 62 | pod 'Flutter', :path => 'Flutter' 63 | 64 | # Plugin Pods 65 | 66 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 67 | # referring to absolute paths on developers' machines. 68 | system('rm -rf .symlinks') 69 | system('mkdir -p .symlinks/plugins') 70 | plugin_pods = parse_KV_file('../.flutter-plugins') 71 | plugin_pods.each do |name, path| 72 | symlink = File.join('.symlinks', 'plugins', name) 73 | File.symlink(path, symlink) 74 | pod name, :path => File.join(symlink, 'ios') 75 | end 76 | end 77 | 78 | # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. 79 | install! 'cocoapods', :disable_input_output_paths => true 80 | 81 | post_install do |installer| 82 | installer.pods_project.targets.each do |target| 83 | target.build_configurations.each do |config| 84 | config.build_settings['ENABLE_BITCODE'] = 'NO' 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - flutter_webview_plugin (0.0.1): 4 | - Flutter 5 | - fluttertoast (0.0.2): 6 | - Flutter 7 | - path_provider (0.0.1): 8 | - Flutter 9 | - path_provider_macos (0.0.1): 10 | - Flutter 11 | - shared_preferences (0.0.1): 12 | - Flutter 13 | - shared_preferences_macos (0.0.1): 14 | - Flutter 15 | - shared_preferences_web (0.0.1): 16 | - Flutter 17 | - url_launcher (0.0.1): 18 | - Flutter 19 | - url_launcher_macos (0.0.1): 20 | - Flutter 21 | - url_launcher_web (0.0.1): 22 | - Flutter 23 | 24 | DEPENDENCIES: 25 | - Flutter (from `Flutter`) 26 | - flutter_webview_plugin (from `.symlinks/plugins/flutter_webview_plugin/ios`) 27 | - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) 28 | - path_provider (from `.symlinks/plugins/path_provider/ios`) 29 | - path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`) 30 | - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) 31 | - shared_preferences_macos (from `.symlinks/plugins/shared_preferences_macos/ios`) 32 | - shared_preferences_web (from `.symlinks/plugins/shared_preferences_web/ios`) 33 | - url_launcher (from `.symlinks/plugins/url_launcher/ios`) 34 | - url_launcher_macos (from `.symlinks/plugins/url_launcher_macos/ios`) 35 | - url_launcher_web (from `.symlinks/plugins/url_launcher_web/ios`) 36 | 37 | EXTERNAL SOURCES: 38 | Flutter: 39 | :path: Flutter 40 | flutter_webview_plugin: 41 | :path: ".symlinks/plugins/flutter_webview_plugin/ios" 42 | fluttertoast: 43 | :path: ".symlinks/plugins/fluttertoast/ios" 44 | path_provider: 45 | :path: ".symlinks/plugins/path_provider/ios" 46 | path_provider_macos: 47 | :path: ".symlinks/plugins/path_provider_macos/ios" 48 | shared_preferences: 49 | :path: ".symlinks/plugins/shared_preferences/ios" 50 | shared_preferences_macos: 51 | :path: ".symlinks/plugins/shared_preferences_macos/ios" 52 | shared_preferences_web: 53 | :path: ".symlinks/plugins/shared_preferences_web/ios" 54 | url_launcher: 55 | :path: ".symlinks/plugins/url_launcher/ios" 56 | url_launcher_macos: 57 | :path: ".symlinks/plugins/url_launcher_macos/ios" 58 | url_launcher_web: 59 | :path: ".symlinks/plugins/url_launcher_web/ios" 60 | 61 | SPEC CHECKSUMS: 62 | Flutter: 0e3d915762c693b495b44d77113d4970485de6ec 63 | flutter_webview_plugin: ed9e8a6a96baf0c867e90e1bce2673913eeac694 64 | fluttertoast: b644586ef3b16f67fae9a1f8754cef6b2d6b634b 65 | path_provider: fb74bd0465e96b594bb3b5088ee4a4e7bb1f2a9d 66 | path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0 67 | shared_preferences: 430726339841afefe5142b9c1f50cb6bd7793e01 68 | shared_preferences_macos: f3f29b71ccbb56bf40c9dd6396c9acf15e214087 69 | shared_preferences_web: 141cce0c3ed1a1c5bf2a0e44f52d31eeb66e5ea9 70 | url_launcher: a1c0cc845906122c4784c542523d8cacbded5626 71 | url_launcher_macos: fd7894421cd39320dce5f292fc99ea9270b2a313 72 | url_launcher_web: e5527357f037c87560776e36436bf2b0288b965c 73 | 74 | PODFILE CHECKSUM: 49ec7d4076524b7e225c38b98147173651ac4b9d 75 | 76 | COCOAPODS: 1.9.1 77 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | #import "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/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/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/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/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/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/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/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/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/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/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/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/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/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/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/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/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/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/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/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/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/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/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/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/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/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/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/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/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/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/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/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 | NSAllowsArbitraryLoadsInWebContent 10 | 11 | 12 | 13 | CFBundleDevelopmentRegion 14 | $(DEVELOPMENT_LANGUAGE) 15 | CFBundleExecutable 16 | $(EXECUTABLE_NAME) 17 | CFBundleIdentifier 18 | $(PRODUCT_BUNDLE_IDENTIFIER) 19 | CFBundleInfoDictionaryVersion 20 | 6.0 21 | CFBundleName 22 | wanandroidflutter 23 | CFBundlePackageType 24 | APPL 25 | CFBundleShortVersionString 26 | $(FLUTTER_BUILD_NAME) 27 | CFBundleSignature 28 | ???? 29 | CFBundleVersion 30 | $(FLUTTER_BUILD_NUMBER) 31 | LSRequiresIPhoneOS 32 | 33 | UILaunchStoryboardName 34 | LaunchScreen 35 | UIMainStoryboardFile 36 | Main 37 | UISupportedInterfaceOrientations 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationLandscapeLeft 41 | UIInterfaceOrientationLandscapeRight 42 | 43 | UISupportedInterfaceOrientations~ipad 44 | 45 | UIInterfaceOrientationPortrait 46 | UIInterfaceOrientationPortraitUpsideDown 47 | UIInterfaceOrientationLandscapeLeft 48 | UIInterfaceOrientationLandscapeRight 49 | 50 | UIViewControllerBasedStatusBarAppearance 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/common/application.dart: -------------------------------------------------------------------------------- 1 | import 'package:event_bus/event_bus.dart'; 2 | import 'package:wanandroidflutter/data/data_utils.dart'; 3 | 4 | //设置一些环节变量 全局的变量之类的 5 | class Application { 6 | static bool isDebug = true; 7 | 8 | //事件总线 9 | static EventBus eventBus; 10 | 11 | //是否登录 12 | static bool isLogin = false; 13 | 14 | static void init() async { 15 | await dataUtils.isLogin().then((value) { 16 | isLogin = value; 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/constant/api.dart: -------------------------------------------------------------------------------- 1 | class Api { 2 | static const String BASE_URL = "https://www.wanandroid.com/"; 3 | 4 | //玩Android api https://www.wanandroid.com/blog/show/2 5 | 6 | ///首页banner 7 | static const String BANNER = "banner/json"; 8 | 9 | //首页文章列表 http://www.wanandroid.com/article/list/0/json 10 | // 知识体系下的文章http://www.wanandroid.com/article/list/0/json?cid=60 11 | static const String ARTICLE_LIST = "article/list/"; 12 | 13 | //置顶文章 14 | static const String ARTICLE_TO_LIST = "article/top/json"; 15 | 16 | //登录 17 | static const String LOGIN = "user/login"; 18 | 19 | //注册 20 | static const String REGISTER = "user/register"; 21 | 22 | //退出登录 23 | static const String LOGIN_OUT = "user/logout/json"; 24 | 25 | //收藏 站内文章 26 | static const String COLLECT_ARTICLE = "lg/collect/%s/json"; 27 | //取消收藏文章 28 | static const String CANCEL_COLLECT_ARTICLE = "lg/uncollect_originId/%s/json"; 29 | //取消收藏文章 我的收藏页 30 | static const String CANCEL_COLLECT_ARTICLE_FOR_MY_FAV = "lg/uncollect/%s/json"; 31 | 32 | //收藏的文章列表 33 | static const String COLLECT_ARTICLE_LIST = "lg/collect/list/%s/json"; 34 | //每日一问文章列表 35 | static const String QUESTION_ARTICLE_LIST = "wenda/list/%s/json"; 36 | //知识体系 37 | static const String KNOWLEDGE_SYSTEM = "tree/json"; 38 | //知识体系下面的文章 39 | static const String KNOWLEDGE_ARTICLE_LIST = "article/list/%s/json"; 40 | //搜索 41 | static const String SEARCH = "article/query/%s/json"; 42 | //热搜关键字 43 | static const String SEARCH_HOT_KEY = "hotkey/json"; 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /lib/constant/app_colors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | ///颜色配置 4 | class AppColors { 5 | static Color colorPrimary = const Color(0xFFFFC800); 6 | static Color accentColor = const Color(0xFFFFC800); 7 | static Color iconColor = const Color(0xFFFFC800); 8 | } 9 | -------------------------------------------------------------------------------- /lib/constant/constants.dart: -------------------------------------------------------------------------------- 1 | 2 | ///2020年03月28日16:33:13 3 | ///常量类 4 | ///xfhy 5 | 6 | class SharedPreferencesKeys { 7 | //登录状态 8 | static const String LOGIN_STATE_KEY = "login_state_key"; 9 | //登录用户名 10 | static const String LOGIN_USERNAME_KEY = "login_username_key"; 11 | } 12 | 13 | -------------------------------------------------------------------------------- /lib/constant/routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:wanandroidflutter/page/about/about_page.dart'; 3 | import 'package:wanandroidflutter/page/favorite/my_favorite_page.dart'; 4 | import 'package:wanandroidflutter/page/knowledge/knowledge_page.dart'; 5 | import 'package:wanandroidflutter/page/login/login_page.dart'; 6 | import 'package:wanandroidflutter/page/question/question_page.dart'; 7 | import 'package:wanandroidflutter/page/search/search_list_widget.dart'; 8 | import 'package:wanandroidflutter/page/search/search_page.dart'; 9 | import 'package:wanandroidflutter/page/webview/web_view_page.dart'; 10 | 11 | ///路由 12 | ///2020年03月22日12:44:43 13 | ///xfhy 14 | 15 | class Routes { 16 | static String root = "/"; 17 | 18 | //webview 19 | static String webViewPage = '/web_view_page'; 20 | 21 | //知识体系 作者文章列表 22 | static String knowledgePage = '/knowledge_page'; 23 | 24 | //登录界面 25 | static String loginPage = '/login_page'; 26 | 27 | //搜索页面 28 | static String searchPage = '/search_page'; 29 | 30 | //搜索结果页面 真正在干搜索这事儿的页面 31 | static String searchResultPage = '/search_result_page'; 32 | 33 | //收藏界面 34 | static String favoritePage = '/favorite_page'; 35 | 36 | //每日一问 37 | static String questionPage = '/question_page'; 38 | 39 | //关于 40 | static String aboutPage = '/about_page'; 41 | 42 | static Map routes = {}; 43 | 44 | static void init() { 45 | routes[webViewPage] = (context) => WebViewPage(); 46 | routes[knowledgePage] = (context) => KnowledgePage(); 47 | routes[loginPage] = (context) => LoginPage(); 48 | routes[searchPage] = (context) => SearchPage(); 49 | routes[searchResultPage] = (context) => SearchResultWidget(); 50 | routes[favoritePage] = (context) => FavoritePage(); 51 | routes[questionPage] = (context) => QuestionPage(); 52 | routes[aboutPage] = (context) => AboutUsPage(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/data/data_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:sprintf/sprintf.dart'; 4 | import 'package:wanandroidflutter/common/application.dart'; 5 | import 'package:wanandroidflutter/constant/api.dart'; 6 | import 'package:wanandroidflutter/constant/constants.dart'; 7 | import 'package:wanandroidflutter/data/http_util.dart'; 8 | import 'package:wanandroidflutter/data/model/banner_data.dart'; 9 | import 'package:wanandroidflutter/data/model/hot_key_entity.dart'; 10 | import 'package:wanandroidflutter/util/log_util.dart'; 11 | import 'package:wanandroidflutter/util/shared_preferences.dart'; 12 | 13 | import 'model/article_data_entity.dart'; 14 | import 'model/knowledge_entity.dart'; 15 | import 'model/login_data_entity.dart'; 16 | 17 | ///数据获取帮助类 18 | ///2020年03月21日15:16:55 19 | ///xfhy 20 | ///dart 单例: 使用static变量+工厂构造函数的方式,可以保证new DataUtils始终返回都是同一个实例 21 | 22 | DataUtils dataUtils = DataUtils(); 23 | 24 | class DataUtils { 25 | //私有构造函数 26 | DataUtils._internal(); 27 | 28 | //保存单例 29 | static DataUtils _singleton = new DataUtils._internal(); 30 | 31 | //工厂构造函数 32 | //当实现一个使用 factory 关键词修饰的构造函数时,这个构造函数不必创建类的新实例。 33 | //当实现构造函数但是不想每次都创建该类的一个实例的时候使用 34 | factory DataUtils() => _singleton; 35 | 36 | ///首页数据模块 37 | //获取首页banner数据 38 | //在Future一个函数内,加了async的,会同步执行的.先等前面的执行完再执行后面的. 39 | Future> getBannerData() async { 40 | //首先从服务端获取最外层的json数据的data 41 | List datas = await httpUtils.get(Api.BANNER); 42 | //然后将data(list)解析成一个一个的BannerData对象,然后组装成list 43 | return datas == null ? null : datas.map((item) => BannerData.fromJson(item)).toList(); 44 | } 45 | 46 | ///首页数据模块 47 | //获取首页最新文章数据 48 | Future getArticleData(int pageIndex) async { 49 | //首先从服务端获取最外层的json数据的data 50 | var datas = await httpUtils.get(Api.ARTICLE_LIST + "$pageIndex/json"); 51 | return datas == null ? null : ArticleDataEntity().fromJson(datas); 52 | } 53 | 54 | ///获取首页置顶文章数据 55 | Future> getTopArticleData() async { 56 | List datas = await httpUtils.get(Api.ARTICLE_TO_LIST); 57 | return datas == null ? null : datas.map((item) => ArticleData().fromJson(item)).toList(); 58 | } 59 | 60 | // 按照作者昵称搜索文章(点击文章作者头像) 61 | Future getAuthorArticleData(String author, int pageIndex) async { 62 | String path = 'article/list/$pageIndex/json'; 63 | Map params = {"author": author}; 64 | var datas = await httpUtils.get(path, params: params); 65 | return datas == null ? null : ArticleDataEntity().fromJson(datas); 66 | } 67 | 68 | // 获取分享人的列表数据 69 | Future getShareAuthorArticleData(int userId, int pageIndex) async { 70 | String path = 'user/$userId/share_articles/$pageIndex/json'; 71 | var datas = await httpUtils.get(path); 72 | var articleData = datas['shareArticles']; 73 | return articleData == null ? null : ArticleDataEntity().fromJson(articleData); 74 | } 75 | 76 | //登录 77 | Future login(String userName, String password, BuildContext context) async { 78 | FormData formData = FormData.fromMap({"username": userName, "password": password}); 79 | var data = await httpUtils.post(Api.LOGIN, formData: formData, isAddLoading: true, context: context, loadingText: "正在登录..."); 80 | //登录失败,则为null 81 | LogUtil.d(data); 82 | if (data != null) { 83 | Application.isLogin = true; 84 | } 85 | return data == null ? null : LoginDataEntity().fromJson(data); 86 | } 87 | 88 | //注册 89 | Future register(String userName, String password, BuildContext context) async { 90 | FormData formData = FormData.fromMap({"username": userName, "password": password, "repassword": password}); 91 | var data = await httpUtils.post(Api.REGISTER, formData: formData, isAddLoading: true, context: context, loadingText: "正在注册并登录..."); 92 | //登录失败,则为null 93 | LogUtil.d(data); 94 | if (data != null) { 95 | Application.isLogin = true; 96 | } 97 | return data == null ? null : LoginDataEntity().fromJson(data); 98 | } 99 | 100 | //退出登录 101 | Future loginOut() async { 102 | var data = await httpUtils.get(Api.LOGIN_OUT); 103 | //LogUtil.d(data); 104 | //return data == null ? null : LoginDataEntity().fromJson(data); 105 | Application.isLogin = false; 106 | dataUtils.setLoginUserName(""); 107 | return data; 108 | } 109 | 110 | ///收藏文章 articleId:文章id 111 | Future collectArticle(int articleId) async { 112 | //https://www.wanandroid.com/lg/collect/1165/json 113 | //格式化语法 使用了一个三方库才能格式化 print(sprintf("%s %s", ["Hello", "World"])); 114 | var data = await httpUtils.post(sprintf(Api.COLLECT_ARTICLE, [articleId])); 115 | //LogUtil.d(data); 116 | return data; 117 | } 118 | 119 | //取消收藏文章 120 | Future cancelCollectArticle(int articleId) async { 121 | var data = await httpUtils.post(sprintf(Api.CANCEL_COLLECT_ARTICLE, [articleId])); 122 | //LogUtil.d(data); 123 | return data; 124 | } 125 | 126 | //取消收藏文章 我的收藏页 127 | Future cancelCollectArticleForMyFavoritePage(int articleId, String originId) async { 128 | FormData formData = FormData.fromMap({"originId": originId}); 129 | var data = await httpUtils.post(sprintf(Api.CANCEL_COLLECT_ARTICLE_FOR_MY_FAV, [articleId]), formData: formData); 130 | //LogUtil.d(data); 131 | return data; 132 | } 133 | 134 | ///获取收藏文章列表 135 | Future getCollectArticles(int pageIndex) async { 136 | var data = await httpUtils.get(sprintf(Api.COLLECT_ARTICLE_LIST, [pageIndex])); 137 | //LogUtil.d(data); 138 | return data == null ? null : ArticleDataEntity().fromJson(data); 139 | } 140 | 141 | ///每日一问文章列表 142 | Future getQuestionArticles(int pageIndex) async { 143 | var data = await httpUtils.get(sprintf(Api.QUESTION_ARTICLE_LIST, [pageIndex])); 144 | //LogUtil.d(data); 145 | return data == null ? null : ArticleDataEntity().fromJson(data); 146 | } 147 | 148 | //知识体系下的文章 149 | Future getKnowledgeArticleData(int cid, int pageIndex) async { 150 | Map params = {"cid": cid}; 151 | var data = await httpUtils.get(sprintf(Api.KNOWLEDGE_ARTICLE_LIST, [pageIndex]), params: params); 152 | return data == null ? null : ArticleDataEntity().fromJson(data); 153 | } 154 | 155 | //搜索 156 | Future search(String key, int pageIndex, BuildContext context) async { 157 | FormData formData = FormData.fromMap({"k": key}); 158 | var data = 159 | await httpUtils.post(sprintf(Api.SEARCH, [pageIndex]), formData: formData, isAddLoading: false, loadingText: "搜索中...", context: context); 160 | return data == null ? null : ArticleDataEntity().fromJson(data); 161 | } 162 | 163 | ///热搜关键词 164 | Future> getSearchHotKeys() async { 165 | List data = await httpUtils.get(Api.SEARCH_HOT_KEY); 166 | return data == null ? null : data.map((item) => HotKeyEntity().fromJson(item)).toList(); 167 | } 168 | 169 | ///知识体系 170 | Future> getKnowledgeSystem() async { 171 | List data = await httpUtils.get(Api.KNOWLEDGE_SYSTEM); 172 | return data == null ? null : data.map((item) => KnowledgeEntity().fromJson(item)).toList(); 173 | } 174 | 175 | //--------------sp-------------- 176 | 177 | //设置登录状态 178 | Future setLoginState(bool isLogin) async { 179 | return await spUtil.putBool(SharedPreferencesKeys.LOGIN_STATE_KEY, isLogin); 180 | } 181 | 182 | ///当前是否已经登录 183 | Future isLogin() async { 184 | return await spUtil.getBool(SharedPreferencesKeys.LOGIN_STATE_KEY); 185 | } 186 | 187 | //设置登录用户名 188 | Future setLoginUserName(String username) async { 189 | return await spUtil.putString(SharedPreferencesKeys.LOGIN_USERNAME_KEY, username); 190 | } 191 | 192 | ///清除用户名信息 193 | Future clearUserName() async { 194 | return await spUtil.remove(SharedPreferencesKeys.LOGIN_USERNAME_KEY); 195 | } 196 | 197 | //获取登录用户名 198 | Future getUserName() async { 199 | return await spUtil.getString(SharedPreferencesKeys.LOGIN_USERNAME_KEY); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /lib/data/http_util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:cookie_jar/cookie_jar.dart'; 4 | import 'package:dio/dio.dart'; 5 | import 'package:dio_cookie_manager/dio_cookie_manager.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:path_provider/path_provider.dart'; 8 | import 'package:wanandroidflutter/constant/api.dart'; 9 | import 'package:wanandroidflutter/util/log_util.dart'; 10 | import 'package:wanandroidflutter/util/tool_utils.dart'; 11 | 12 | /* 13 | 通用格式 14 | { 15 | "data":{}, 16 | "errorCode":0, 17 | "errorMsg":"" 18 | } 19 | * */ 20 | 21 | HttpUtils httpUtils = HttpUtils(); 22 | 23 | ///http请求封装 24 | class HttpUtils { 25 | HttpUtils._internal() { 26 | if (null == _dio) { 27 | _dio = Dio(); 28 | //dio 也是单例,设置baseUrl等一些配置 29 | _dio.options.baseUrl = Api.BASE_URL; 30 | _dio.options.connectTimeout = 30 * 1000; 31 | _dio.options.sendTimeout = 30 * 1000; 32 | _dio.options.receiveTimeout = 30 * 1000; 33 | } 34 | } 35 | 36 | static HttpUtils _singleton = HttpUtils._internal(); 37 | 38 | factory HttpUtils() => _singleton; 39 | 40 | Dio _dio; 41 | 42 | Future get(String url, {Map params, bool isAddLoading = false, BuildContext context, String loadingText}) async { 43 | Response response; 44 | 45 | //path_provider 负责查找 iOS/Android 的目录文件,IO 模块负责对文件进行读写。 获取文档目录的路径 46 | Directory documentsDir = await getApplicationDocumentsDirectory(); 47 | String documentsPath = documentsDir.path; 48 | //新建目录 49 | var dir = Directory("$documentsPath/cookies"); 50 | await dir.create(); 51 | 52 | //LogUtil.d("文档目录path = ${documentsPath}"); 53 | 54 | //添加cookie 55 | _dio.interceptors.add(CookieManager(PersistCookieJar(dir: dir.path, ignoreExpires: true))); 56 | 57 | //loading 58 | if (isAddLoading) { 59 | ToolUtils.showLoading(context, loadingText); 60 | } 61 | 62 | try { 63 | if (params != null) { 64 | response = await _dio.get(url, queryParameters: params); 65 | } else { 66 | response = await _dio.get(url); 67 | } 68 | 69 | //隐藏loading 70 | ToolUtils.disMissLoadingDialog(isAddLoading, context); 71 | 72 | if (response.data['errorCode'] == 0) { 73 | //这里直接把data部分给搞出来,免得每次在外面去解析˛ 74 | return response.data['data']; 75 | } else { 76 | String data = response.data["errorMsg"]; 77 | ToolUtils.showToast(msg: data); 78 | LogUtil.d("请求网络错误 : $data"); 79 | } 80 | } on DioError catch (e) { 81 | if (e.response != null) { 82 | LogUtil.d(e.response.headers.toString()); 83 | LogUtil.d(e.response.request.toString()); 84 | } else { 85 | LogUtil.d(e.request.toString()); 86 | } 87 | 88 | //ToolUtils.showToast(msg: handleError(e)); 89 | ToolUtils.disMissLoadingDialog(isAddLoading, context); 90 | return null; 91 | } 92 | } 93 | 94 | ///post请求 95 | ///url : 地址 96 | ///formData : 请求参数 97 | Future post(String url, 98 | {FormData formData, Map queryParameters, bool isAddLoading = false, BuildContext context, String loadingText}) async { 99 | Response response; 100 | 101 | Directory documentsDir = await getApplicationDocumentsDirectory(); 102 | String documentsPath = documentsDir.path; 103 | var dir = Directory("$documentsPath/cookies"); 104 | await dir.create(); 105 | 106 | //cookie 107 | _dio.interceptors.add(CookieManager(PersistCookieJar(dir: dir.path))); 108 | 109 | //loading 110 | if (isAddLoading) { 111 | ToolUtils.showLoading(context, loadingText); 112 | } 113 | 114 | try { 115 | if (formData != null) { 116 | response = await _dio.post(url, data: formData); 117 | } else if (queryParameters != null) { 118 | response = await _dio.post(url, queryParameters: queryParameters); 119 | } else { 120 | response = await _dio.post(url); 121 | } 122 | 123 | //隐藏loading 124 | ToolUtils.disMissLoadingDialog(isAddLoading, context); 125 | 126 | //json 数据 127 | //LogUtil.d(response.toString()); 128 | 129 | if (response.data['errorCode'] == 0) { 130 | //这里直接把data部分给搞出来,免得每次在外面去解析˛ 131 | return response.data['data']; 132 | } else { 133 | String data = response.data["errorMsg"]; 134 | ToolUtils.showToast(msg: data); 135 | LogUtil.d("请求网络错误 : $data"); 136 | } 137 | } on DioError catch (e) { 138 | if (e.response != null) { 139 | LogUtil.d(e.response.headers.toString()); 140 | LogUtil.d(e.response.request.toString()); 141 | } else { 142 | LogUtil.d(e.request.toString()); 143 | } 144 | 145 | //ToolUtils.showToast(msg: handleError(e)); 146 | ToolUtils.disMissLoadingDialog(isAddLoading, context); 147 | return null; 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /lib/data/model/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/lib/data/model/.DS_Store -------------------------------------------------------------------------------- /lib/data/model/article_data_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:wanandroidflutter/generated/json/base/json_convert_content.dart'; 2 | 3 | ///文章数据 4 | ///mixin 混入 可以重复代码,这里ArticleDataEntity可以有JsonConvert种的方法 5 | class ArticleDataEntity with JsonConvert { 6 | int curPage; 7 | List datas; 8 | int offset; 9 | bool over; 10 | int pageCount; 11 | int size; 12 | int total; 13 | } 14 | 15 | class ArticleData with JsonConvert { 16 | String apkLink; 17 | int audit; 18 | String author; 19 | bool canEdit; 20 | int chapterId; 21 | String chapterName; 22 | bool collect; 23 | int courseId; 24 | String desc; 25 | String descMd; 26 | String envelopePic; 27 | bool fresh; 28 | int id; 29 | String link; 30 | String niceDate; 31 | String niceShareDate; 32 | String origin; 33 | String originId; 34 | String prefix; 35 | String projectLink; 36 | int publishTime; 37 | int selfVisible; 38 | int shareDate; 39 | String shareUser; 40 | int superChapterId; 41 | String superChapterName; 42 | List tags; 43 | String title; 44 | ///type=1 是置顶数据 45 | int type; 46 | int userId; 47 | int visible; 48 | int zan; 49 | 50 | @override 51 | String toString() { 52 | return 'ArticleData{apkLink: $apkLink, audit: $audit, author: $author, canEdit: $canEdit, chapterId: $chapterId, chapterName: $chapterName, collect: $collect, courseId: $courseId, desc: $desc, descMd: $descMd, envelopePic: $envelopePic, fresh: $fresh, id: $id, link: $link, niceDate: $niceDate, niceShareDate: $niceShareDate, origin: $origin, prefix: $prefix, projectLink: $projectLink, publishTime: $publishTime, selfVisible: $selfVisible, shareDate: $shareDate, shareUser: $shareUser, superChapterId: $superChapterId, superChapterName: $superChapterName, tags: $tags, title: $title, type: $type, userId: $userId, visible: $visible, zan: $zan}'; 53 | } 54 | 55 | } 56 | 57 | class ArticleTags with JsonConvert { 58 | String name; 59 | String url; 60 | } 61 | -------------------------------------------------------------------------------- /lib/data/model/banner_data.dart: -------------------------------------------------------------------------------- 1 | /** 2 | * { 3 | "desc": "Android高级进阶直播课免费学习", 4 | "id": 23, 5 | "imagePath": "https://wanandroid.com/blogimgs/d9a6f718-5011-429c-8dd5-273f02f3bf25.jpeg", 6 | "isVisible": 1, 7 | "order": 0, 8 | "title": "Android高级进阶直播课免费学习", 9 | "type": 0, 10 | "url": "https://url.163.com/4bj" 11 | }, 12 | */ 13 | 14 | ///Banner数据 15 | class BannerData { 16 | String desc; 17 | int id; 18 | String imagePath; 19 | int isVisible; 20 | int order; 21 | String title; 22 | int type; 23 | String url; 24 | 25 | BannerData( 26 | {this.desc, 27 | this.id, 28 | this.imagePath, 29 | this.isVisible, 30 | this.order, 31 | this.title, 32 | this.type, 33 | this.url}); 34 | 35 | ///入参是待解析的json map 36 | factory BannerData.fromJson(Map parsedJson) { 37 | return BannerData( 38 | desc: parsedJson['desc'], 39 | id: parsedJson['id'], 40 | imagePath: parsedJson['imagePath'], 41 | isVisible: parsedJson['isVisible'], 42 | order: parsedJson['order'], 43 | title: parsedJson['title'], 44 | type: parsedJson['type'], 45 | url: parsedJson['url'], 46 | ); 47 | } 48 | 49 | @override 50 | String toString() { 51 | return 'BannerData{desc: $desc, id: $id, imagePath: $imagePath, isVisible: $isVisible, order: $order, title: $title, type: $type, url: $url}'; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /lib/data/model/hot_key_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:wanandroidflutter/generated/json/base/json_convert_content.dart'; 2 | 3 | ///热搜关键词 4 | ///2020年03月29日15:44:54 5 | ///xfhy 6 | class HotKeyEntity with JsonConvert { 7 | int id; 8 | String link; 9 | String name; 10 | int order; 11 | int visible; 12 | } 13 | -------------------------------------------------------------------------------- /lib/data/model/knowledge_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:wanandroidflutter/generated/json/base/json_convert_content.dart'; 2 | 3 | ///知识体系 4 | class KnowledgeEntity with JsonConvert { 5 | List children; 6 | int courseId; 7 | int id; 8 | String name; 9 | int order; 10 | int parentChapterId; 11 | bool userControlSetTop; 12 | int visible; 13 | } 14 | 15 | class KnowledgeChild with JsonConvert { 16 | List children; 17 | int courseId; 18 | int id; 19 | String name; 20 | int order; 21 | int parentChapterId; 22 | bool userControlSetTop; 23 | int visible; 24 | } 25 | -------------------------------------------------------------------------------- /lib/data/model/login_data_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:wanandroidflutter/generated/json/base/json_convert_content.dart'; 2 | 3 | class LoginDataEntity with JsonConvert { 4 | bool admin; 5 | List chapterTops; 6 | List collectIds; 7 | String email; 8 | String icon; 9 | int id; 10 | String nickname; 11 | String password; 12 | String publicName; 13 | String token; 14 | int type; 15 | String username; 16 | } 17 | -------------------------------------------------------------------------------- /lib/demo/bottom_navigation_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:wanandroidflutter/constant/app_colors.dart'; 4 | 5 | class BottomNavigationBarWidget extends StatefulWidget { 6 | @override //一般是需要下划线开头,然后后面加一个State 7 | State createState() => _BottomNavigationBarWidgetState(); 8 | } 9 | 10 | class _BottomNavigationBarWidgetState extends State { 11 | var currentPage = 0; 12 | PageController _pageController; 13 | final appBarTitles = ['首页', '发现', '我的']; 14 | 15 | @override 16 | void initState() { 17 | super.initState(); 18 | _pageController = PageController(); 19 | } 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return MaterialApp( 24 | home: Scaffold( 25 | appBar: AppBar( 26 | title: Text( 27 | appBarTitles[currentPage], 28 | style: TextStyle(color: Colors.white), 29 | ), 30 | ), 31 | body: getBody(), 32 | ), 33 | ); 34 | } 35 | 36 | getBody() { 37 | return Column( 38 | children: [ 39 | //上面把空间占满 40 | Expanded( 41 | //PageView类似于Android中的ViewPager 42 | child: PageView( 43 | children: [ 44 | Container( 45 | color: Colors.deepOrange, 46 | ), 47 | Container( 48 | color: Colors.yellow, 49 | ), 50 | Container( 51 | color: Colors.blue, 52 | ), 53 | ], 54 | //设置controller,可以控制PageView的当前页 55 | controller: _pageController, 56 | //滑动的那种控制, 57 | // BouncingScrollPhysics是用来适用于 58 | // 允许滚动偏移超出内容范围,然后将内容反弹到那些范围边缘的环境的滚动物理。iOS上经常有这种效果 59 | physics: BouncingScrollPhysics(), 60 | onPageChanged: (page) { 61 | setState(() { 62 | currentPage = page; 63 | }); 64 | }, 65 | ), 66 | ), 67 | //下面的bottomNavigationBar 68 | BottomNavigationBar( 69 | items: [ 70 | BottomNavigationBarItem( 71 | icon: Icon(Icons.toys), 72 | title: Text('toys'), 73 | ), 74 | BottomNavigationBarItem( 75 | icon: Icon(Icons.tap_and_play), 76 | title: Text("play"), 77 | ), 78 | BottomNavigationBarItem( 79 | icon: Icon(Icons.landscape), 80 | title: Text("landscape"), 81 | ), 82 | ], 83 | onTap: (page) { 84 | //滑动到相应页面 curve是动画效果 85 | _pageController.animateToPage(page, 86 | duration: Duration(milliseconds: 300), curve: Curves.easeIn); 87 | }, 88 | currentIndex: currentPage, 89 | ), 90 | ], 91 | ); 92 | } 93 | } 94 | 95 | /*class BottomNavigationDemo extends StatelessWidget { 96 | final appBarTitles = ['首页', '发现', '我的']; 97 | 98 | @override 99 | Widget build(BuildContext context) { 100 | return MaterialApp( 101 | theme: ThemeData( 102 | primaryColor: AppColors.colorPrimary, accentColor: Colors.blue), 103 | home: Scaffold( 104 | appBar: AppBar( 105 | title: Text( 106 | '首页', 107 | style: TextStyle(color: Colors.white), 108 | ), 109 | ), 110 | bottomNavigationBar: BottomNavigationBar( 111 | items: [ 112 | BottomNavigationBarItem( 113 | icon: const Icon(Icons.home), 114 | title: Text(appBarTitles[0]), 115 | backgroundColor: Colors.blue, 116 | ), 117 | BottomNavigationBarItem( 118 | icon: const Icon(Icons.widgets), 119 | title: Text(appBarTitles[1]), 120 | backgroundColor: Colors.blue, 121 | ), 122 | BottomNavigationBarItem( 123 | icon: const Icon(Icons.person), 124 | title: Text(appBarTitles[2]), 125 | backgroundColor: Colors.blue, 126 | ), 127 | ], 128 | ), 129 | ), 130 | ); 131 | } 132 | }*/ 133 | -------------------------------------------------------------------------------- /lib/demo/flutter_webview_demo.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; 3 | 4 | void main() => runApp(MyApp()); 5 | 6 | class MyApp extends StatelessWidget { 7 | @override 8 | Widget build(BuildContext context) { 9 | return MaterialApp( 10 | title: 'Flutter WebView Demo', 11 | routes: { 12 | '/widget': (context) => WebViewPage(), 13 | }, 14 | home: HomeWidget(), 15 | ); 16 | } 17 | } 18 | 19 | class HomeWidget extends StatelessWidget { 20 | @override 21 | Widget build(BuildContext context) { 22 | return Scaffold( 23 | body: Center( 24 | child: RaisedButton( 25 | child: Text('跳转网页'), 26 | onPressed: () { 27 | //命名路由 且 传递参数 28 | Navigator.pushNamed(context, '/widget', 29 | arguments: WebData("https://www.baidu.com", false)); 30 | }, 31 | ), 32 | ), 33 | ); 34 | } 35 | } 36 | 37 | class WebData { 38 | String url; 39 | bool isLiked; 40 | 41 | WebData(this.url, this.isLiked); 42 | } 43 | 44 | class WebViewPage extends StatelessWidget { 45 | @override 46 | Widget build(BuildContext context) { 47 | //通过RouteSettings 获取传递过来的参数 48 | WebData webData = ModalRoute.of(context).settings.arguments as WebData; 49 | return new WebviewScaffold( 50 | url: webData.url, 51 | appBar: new AppBar( 52 | title: const Text('Widget webview'), 53 | ), 54 | withZoom: true, 55 | withLocalStorage: true, 56 | hidden: true, 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/demo/request_data.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:sprintf/sprintf.dart'; 3 | import 'package:wanandroidflutter/data/data_utils.dart'; 4 | import 'package:wanandroidflutter/data/model/article_data_entity.dart'; 5 | import 'package:wanandroidflutter/util/log_util.dart'; 6 | 7 | void main() => runApp(RequestDemo()); 8 | 9 | class RequestDemo extends StatelessWidget { 10 | @override 11 | Widget build(BuildContext context) { 12 | return MaterialApp( 13 | home: Scaffold( 14 | body: CustomScrollView( 15 | shrinkWrap: true, 16 | slivers: [ 17 | new SliverPadding( 18 | padding: EdgeInsets.all(20.0), 19 | sliver: new SliverList( 20 | delegate: new SliverChildListDelegate([ 21 | RaisedButton( 22 | child: Text('获取最新文章'), 23 | onPressed: () { 24 | dataUtils.getArticleData(0); 25 | }, 26 | ), 27 | RaisedButton( 28 | child: Text('获取置顶文章'), 29 | onPressed: () { 30 | dataUtils.getTopArticleData(); 31 | }, 32 | ), 33 | RaisedButton( 34 | child: Text('获取置顶文章和正常文章'), 35 | onPressed: () { 36 | getTopAndArticleList(); 37 | }, 38 | ), 39 | RaisedButton( 40 | child: Text('搜索作者文章'), 41 | onPressed: () { 42 | dataUtils.getAuthorArticleData("扔物线", 0); 43 | }, 44 | ), 45 | RaisedButton( 46 | child: Text('分享人列表数据'), 47 | onPressed: () { 48 | dataUtils.getShareAuthorArticleData(2, 0); 49 | }, 50 | ), 51 | RaisedButton( 52 | child: Text('登录'), 53 | onPressed: () { 54 | dataUtils.login("xxxxxxx415456465465", "xxxxxxx", context); 55 | }, 56 | ), 57 | RaisedButton( 58 | child: Text('注册'), 59 | onPressed: () { 60 | dataUtils.register("xxxxxxx415456465465qqqqq", "xxxxxxx", context); 61 | }, 62 | ), 63 | RaisedButton( 64 | child: Text('退出登录'), 65 | onPressed: () { 66 | dataUtils.loginOut(); 67 | }, 68 | ), 69 | RaisedButton( 70 | child: Text('收藏文章'), 71 | onPressed: () { 72 | LogUtil.d(sprintf("lg/collect/%s/json", [15615])); 73 | dataUtils.collectArticle(12424); 74 | }, 75 | ), 76 | RaisedButton( 77 | child: Text('取消收藏文章'), 78 | onPressed: () { 79 | dataUtils.cancelCollectArticle(12424); 80 | }, 81 | ), 82 | RaisedButton( 83 | child: Text('收藏的文章列表'), 84 | onPressed: () { 85 | //LogUtil.d(sprintf("lg/collect/%s/json", [15615])); 86 | dataUtils.getCollectArticles(0); 87 | }, 88 | ), 89 | RaisedButton( 90 | child: Text('请求知识体系'), 91 | onPressed: () { 92 | dataUtils.getKnowledgeArticleData(60, 0); 93 | }, 94 | ), 95 | RaisedButton( 96 | child: Text('搜索'), 97 | onPressed: () { 98 | dataUtils.search("控件", 0, context); 99 | }, 100 | ), 101 | RaisedButton( 102 | child: Text('热搜关键词'), 103 | onPressed: () { 104 | dataUtils.getSearchHotKeys(); 105 | }, 106 | ), 107 | RaisedButton( 108 | child: Text('知识体系'), 109 | onPressed: () { 110 | dataUtils.getKnowledgeSystem(); 111 | }, 112 | ), 113 | const Text('C'), 114 | const Text('D'), 115 | ]), 116 | ), 117 | ) 118 | ], 119 | ), 120 | ), 121 | ); 122 | } 123 | 124 | void getTopAndArticleList() async { 125 | await Future.wait([dataUtils.getTopArticleData(), dataUtils.getArticleData(0)]).then((List listData) { 126 | //需要将顶部数据List 和 正常文章数据ArticleDataEntity中的datas进行合并,组成一个新的List 127 | List articleDataList = []; 128 | for (var value in listData) { 129 | if (value is List) { 130 | articleDataList.addAll(value); 131 | } else if (value is ArticleDataEntity) { 132 | articleDataList.addAll(value.datas); 133 | } 134 | } 135 | LogUtil.d("合并数据成功了"); 136 | }); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /lib/demo/sliver_app_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | ///SliverAppBar 效果 4 | ///2020年03月28日08:00:19 5 | ///xfhy 6 | 7 | void main() => runApp(MySliverApp()); 8 | 9 | class MySliverApp extends StatefulWidget { 10 | @override 11 | State createState() { 12 | return _MySliverAppState(); 13 | } 14 | } 15 | 16 | class _MySliverAppState extends State { 17 | @override 18 | Widget build(BuildContext context) { 19 | return MaterialApp( 20 | home: Scaffold( 21 | body: CustomScrollView( 22 | slivers: [ 23 | SliverAppBar( 24 | expandedHeight: 230.0, 25 | floating: false, 26 | pinned: true, 27 | snap: false, 28 | //滑动时 标题上移效果 29 | flexibleSpace: FlexibleSpaceBar( 30 | title: Text('标题标题标题'), 31 | centerTitle: true, 32 | collapseMode: CollapseMode.pin, 33 | //背景图 34 | background: Image.asset( 35 | "images/ic_zone_background.webp", 36 | fit: BoxFit.cover, 37 | ), 38 | ), 39 | //返回按钮 40 | leading: IconButton( 41 | icon: Icon(Icons.arrow_back), 42 | onPressed: () {}, 43 | ), 44 | //右边按钮区域 45 | actions: [ 46 | IconButton( 47 | icon: Icon(Icons.add), 48 | onPressed: () { 49 | print("添加"); 50 | }, 51 | ), 52 | IconButton( 53 | icon: Icon(Icons.more_horiz), 54 | onPressed: () { 55 | print("更多"); 56 | }, 57 | ), 58 | ], 59 | ), 60 | SliverFixedExtentList( 61 | itemExtent: 50.0, 62 | delegate: SliverChildBuilderDelegate( 63 | (context, index) => ListTile( 64 | title: Text("Item $index"), 65 | ), 66 | childCount: 88, 67 | ), 68 | ), 69 | ], 70 | ), 71 | ), 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/generated/json/article_data_entity_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:wanandroidflutter/data/model/article_data_entity.dart'; 2 | 3 | articleDataEntityFromJson(ArticleDataEntity data, Map json) { 4 | if (json['curPage'] != null) { 5 | data.curPage = json['curPage']?.toInt(); 6 | } 7 | if (json['datas'] != null) { 8 | data.datas = new List(); 9 | (json['datas'] as List).forEach((v) { 10 | data.datas.add(new ArticleData().fromJson(v)); 11 | }); 12 | } 13 | if (json['offset'] != null) { 14 | data.offset = json['offset']?.toInt(); 15 | } 16 | if (json['over'] != null) { 17 | data.over = json['over']; 18 | } 19 | if (json['pageCount'] != null) { 20 | data.pageCount = json['pageCount']?.toInt(); 21 | } 22 | if (json['size'] != null) { 23 | data.size = json['size']?.toInt(); 24 | } 25 | if (json['total'] != null) { 26 | data.total = json['total']?.toInt(); 27 | } 28 | return data; 29 | } 30 | 31 | Map articleDataEntityToJson(ArticleDataEntity entity) { 32 | final Map data = new Map(); 33 | data['curPage'] = entity.curPage; 34 | if (entity.datas != null) { 35 | data['datas'] = entity.datas.map((v) => v.toJson()).toList(); 36 | } 37 | data['offset'] = entity.offset; 38 | data['over'] = entity.over; 39 | data['pageCount'] = entity.pageCount; 40 | data['size'] = entity.size; 41 | data['total'] = entity.total; 42 | return data; 43 | } 44 | 45 | articleDataFromJson(ArticleData data, Map json) { 46 | if (json['apkLink'] != null) { 47 | data.apkLink = json['apkLink']?.toString(); 48 | } 49 | if (json['audit'] != null) { 50 | data.audit = json['audit']?.toInt(); 51 | } 52 | if (json['author'] != null) { 53 | data.author = json['author']?.toString(); 54 | } 55 | if (json['canEdit'] != null) { 56 | data.canEdit = json['canEdit']; 57 | } 58 | if (json['chapterId'] != null) { 59 | data.chapterId = json['chapterId']?.toInt(); 60 | } 61 | if (json['chapterName'] != null) { 62 | data.chapterName = json['chapterName']?.toString(); 63 | } 64 | if (json['collect'] != null) { 65 | data.collect = json['collect']; 66 | } 67 | if (json['courseId'] != null) { 68 | data.courseId = json['courseId']?.toInt(); 69 | } 70 | if (json['desc'] != null) { 71 | data.desc = json['desc']?.toString(); 72 | } 73 | if (json['descMd'] != null) { 74 | data.descMd = json['descMd']?.toString(); 75 | } 76 | if (json['envelopePic'] != null) { 77 | data.envelopePic = json['envelopePic']?.toString(); 78 | } 79 | if (json['fresh'] != null) { 80 | data.fresh = json['fresh']; 81 | } 82 | if (json['id'] != null) { 83 | data.id = json['id']?.toInt(); 84 | } 85 | if (json['link'] != null) { 86 | data.link = json['link']?.toString(); 87 | } 88 | if (json['niceDate'] != null) { 89 | data.niceDate = json['niceDate']?.toString(); 90 | } 91 | if (json['niceShareDate'] != null) { 92 | data.niceShareDate = json['niceShareDate']?.toString(); 93 | } 94 | if (json['origin'] != null) { 95 | data.origin = json['origin']?.toString(); 96 | } 97 | if (json['originId'] != null) { 98 | data.origin = json['originId']?.toString(); 99 | } 100 | if (json['prefix'] != null) { 101 | data.prefix = json['prefix']?.toString(); 102 | } 103 | if (json['projectLink'] != null) { 104 | data.projectLink = json['projectLink']?.toString(); 105 | } 106 | if (json['publishTime'] != null) { 107 | data.publishTime = json['publishTime']?.toInt(); 108 | } 109 | if (json['selfVisible'] != null) { 110 | data.selfVisible = json['selfVisible']?.toInt(); 111 | } 112 | if (json['shareDate'] != null) { 113 | data.shareDate = json['shareDate']?.toInt(); 114 | } 115 | if (json['shareUser'] != null) { 116 | data.shareUser = json['shareUser']?.toString(); 117 | } 118 | if (json['superChapterId'] != null) { 119 | data.superChapterId = json['superChapterId']?.toInt(); 120 | } 121 | if (json['superChapterName'] != null) { 122 | data.superChapterName = json['superChapterName']?.toString(); 123 | } 124 | if (json['tags'] != null) { 125 | data.tags = new List(); 126 | (json['tags'] as List).forEach((v) { 127 | data.tags.add(new ArticleTags().fromJson(v)); 128 | }); 129 | } 130 | if (json['title'] != null) { 131 | data.title = json['title']?.toString(); 132 | } 133 | if (json['type'] != null) { 134 | data.type = json['type']?.toInt(); 135 | } 136 | if (json['userId'] != null) { 137 | data.userId = json['userId']?.toInt(); 138 | } 139 | if (json['visible'] != null) { 140 | data.visible = json['visible']?.toInt(); 141 | } 142 | if (json['zan'] != null) { 143 | data.zan = json['zan']?.toInt(); 144 | } 145 | return data; 146 | } 147 | 148 | Map articleDataToJson(ArticleData entity) { 149 | final Map data = new Map(); 150 | data['apkLink'] = entity.apkLink; 151 | data['audit'] = entity.audit; 152 | data['author'] = entity.author; 153 | data['canEdit'] = entity.canEdit; 154 | data['chapterId'] = entity.chapterId; 155 | data['chapterName'] = entity.chapterName; 156 | data['collect'] = entity.collect; 157 | data['courseId'] = entity.courseId; 158 | data['desc'] = entity.desc; 159 | data['descMd'] = entity.descMd; 160 | data['envelopePic'] = entity.envelopePic; 161 | data['fresh'] = entity.fresh; 162 | data['id'] = entity.id; 163 | data['link'] = entity.link; 164 | data['niceDate'] = entity.niceDate; 165 | data['niceShareDate'] = entity.niceShareDate; 166 | data['origin'] = entity.origin; 167 | data['originId'] = entity.originId; 168 | data['prefix'] = entity.prefix; 169 | data['projectLink'] = entity.projectLink; 170 | data['publishTime'] = entity.publishTime; 171 | data['selfVisible'] = entity.selfVisible; 172 | data['shareDate'] = entity.shareDate; 173 | data['shareUser'] = entity.shareUser; 174 | data['superChapterId'] = entity.superChapterId; 175 | data['superChapterName'] = entity.superChapterName; 176 | if (entity.tags != null) { 177 | data['tags'] = entity.tags.map((v) => v.toJson()).toList(); 178 | } 179 | data['title'] = entity.title; 180 | data['type'] = entity.type; 181 | data['userId'] = entity.userId; 182 | data['visible'] = entity.visible; 183 | data['zan'] = entity.zan; 184 | return data; 185 | } 186 | 187 | articleTagsFromJson(ArticleTags data, Map json) { 188 | if (json['name'] != null) { 189 | data.name = json['name']?.toString(); 190 | } 191 | if (json['url'] != null) { 192 | data.url = json['url']?.toString(); 193 | } 194 | return data; 195 | } 196 | 197 | Map articleTagsToJson(ArticleTags entity) { 198 | final Map data = new Map(); 199 | data['name'] = entity.name; 200 | data['url'] = entity.url; 201 | return data; 202 | } -------------------------------------------------------------------------------- /lib/generated/json/base/json_convert_content.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: non_constant_identifier_names 2 | // ignore_for_file: camel_case_types 3 | // ignore_for_file: prefer_single_quotes 4 | 5 | // This file is automatically generated. DO NOT EDIT, all your changes would be lost. 6 | import 'package:wanandroidflutter/data/model/login_data_entity.dart'; 7 | import 'package:wanandroidflutter/generated/json/login_data_entity_helper.dart'; 8 | import 'package:wanandroidflutter/data/model/hot_key_entity.dart'; 9 | import 'package:wanandroidflutter/generated/json/hot_key_entity_helper.dart'; 10 | import 'package:wanandroidflutter/data/model/article_data_entity.dart'; 11 | import 'package:wanandroidflutter/generated/json/article_data_entity_helper.dart'; 12 | import 'package:wanandroidflutter/data/model/knowledge_entity.dart'; 13 | import 'package:wanandroidflutter/generated/json/knowledge_entity_helper.dart'; 14 | 15 | class JsonConvert { 16 | T fromJson(Map json) { 17 | return _getFromJson(runtimeType, this, json); 18 | } 19 | 20 | Map toJson() { 21 | return _getToJson(runtimeType, this); 22 | } 23 | 24 | static _getFromJson(Type type, data, json) { 25 | switch (type) { case LoginDataEntity: 26 | return loginDataEntityFromJson(data as LoginDataEntity, json) as T; case HotKeyEntity: 27 | return hotKeyEntityFromJson(data as HotKeyEntity, json) as T; case ArticleDataEntity: 28 | return articleDataEntityFromJson(data as ArticleDataEntity, json) as T; case ArticleData: 29 | return articleDataFromJson(data as ArticleData, json) as T; case ArticleTags: 30 | return articleTagsFromJson(data as ArticleTags, json) as T; case KnowledgeEntity: 31 | return knowledgeEntityFromJson(data as KnowledgeEntity, json) as T; case KnowledgeChild: 32 | return knowledgechildFromJson(data as KnowledgeChild, json) as T; } 33 | return data as T; 34 | } 35 | 36 | static _getToJson(Type type, data) { 37 | switch (type) { case LoginDataEntity: 38 | return loginDataEntityToJson(data as LoginDataEntity); case HotKeyEntity: 39 | return hotKeyEntityToJson(data as HotKeyEntity); case ArticleDataEntity: 40 | return articleDataEntityToJson(data as ArticleDataEntity); case ArticleData: 41 | return articleDataToJson(data as ArticleData); case ArticleTags: 42 | return articleTagsToJson(data as ArticleTags); case KnowledgeEntity: 43 | return knowledgeEntityToJson(data as KnowledgeEntity); case KnowledgeChild: 44 | return knowledgechildToJson(data as KnowledgeChild); } 45 | return data as T; 46 | } 47 | //Go back to a single instance by type 48 | static _fromJsonSingle(String type, json) { 49 | switch (type) { case 'LoginDataEntity': 50 | return LoginDataEntity().fromJson(json); case 'HotKeyEntity': 51 | return HotKeyEntity().fromJson(json); case 'ArticleDataEntity': 52 | return ArticleDataEntity().fromJson(json); case 'ArticleData': 53 | return ArticleData().fromJson(json); case 'ArticleTags': 54 | return ArticleTags().fromJson(json); case 'KnowledgeEntity': 55 | return KnowledgeEntity().fromJson(json); case 'Knowledgechild': 56 | return KnowledgeChild().fromJson(json); } 57 | return null; 58 | } 59 | 60 | //empty list is returned by type 61 | static _getListFromType(String type) { 62 | switch (type) { case 'LoginDataEntity': 63 | return List(); case 'HotKeyEntity': 64 | return List(); case 'ArticleDataEntity': 65 | return List(); case 'ArticleData': 66 | return List(); case 'ArticleTags': 67 | return List(); case 'KnowledgeEntity': 68 | return List(); case 'Knowledgechild': 69 | return List(); } 70 | return null; 71 | } 72 | 73 | static M fromJsonAsT(json) { 74 | String type = M.toString(); 75 | if (json is List && type.contains("List<")) { 76 | String itemType = type.substring(5, type.length - 1); 77 | List tempList = _getListFromType(itemType); 78 | json.forEach((itemJson) { 79 | tempList 80 | .add(_fromJsonSingle(type.substring(5, type.length - 1), itemJson)); 81 | }); 82 | return tempList as M; 83 | } else { 84 | return _fromJsonSingle(M.toString(), json) as M; 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /lib/generated/json/base/json_filed.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: non_constant_identifier_names 2 | // ignore_for_file: camel_case_types 3 | // ignore_for_file: prefer_single_quotes 4 | 5 | // This file is automatically generated. DO NOT EDIT, all your changes would be lost. 6 | 7 | class JSONField { 8 | //Specify the parse field name 9 | final String name; 10 | 11 | //Specify the time resolution format 12 | final String format; 13 | 14 | //Whether to participate in toJson 15 | final bool serialize; 16 | 17 | //Whether to participate in fromMap 18 | final bool deserialize; 19 | 20 | const JSONField({this.name, this.format, this.serialize, this.deserialize}); 21 | } 22 | -------------------------------------------------------------------------------- /lib/generated/json/hot_key_entity_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:wanandroidflutter/data/model/hot_key_entity.dart'; 2 | 3 | hotKeyEntityFromJson(HotKeyEntity data, Map json) { 4 | if (json['id'] != null) { 5 | data.id = json['id']?.toInt(); 6 | } 7 | if (json['link'] != null) { 8 | data.link = json['link']?.toString(); 9 | } 10 | if (json['name'] != null) { 11 | data.name = json['name']?.toString(); 12 | } 13 | if (json['order'] != null) { 14 | data.order = json['order']?.toInt(); 15 | } 16 | if (json['visible'] != null) { 17 | data.visible = json['visible']?.toInt(); 18 | } 19 | return data; 20 | } 21 | 22 | Map hotKeyEntityToJson(HotKeyEntity entity) { 23 | final Map data = new Map(); 24 | data['id'] = entity.id; 25 | data['link'] = entity.link; 26 | data['name'] = entity.name; 27 | data['order'] = entity.order; 28 | data['visible'] = entity.visible; 29 | return data; 30 | } -------------------------------------------------------------------------------- /lib/generated/json/knowledge_entity_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:wanandroidflutter/data/model/knowledge_entity.dart'; 2 | 3 | knowledgeEntityFromJson(KnowledgeEntity data, Map json) { 4 | if (json['children'] != null) { 5 | data.children = new List(); 6 | (json['children'] as List).forEach((v) { 7 | data.children.add(new KnowledgeChild().fromJson(v)); 8 | }); 9 | } 10 | if (json['courseId'] != null) { 11 | data.courseId = json['courseId']?.toInt(); 12 | } 13 | if (json['id'] != null) { 14 | data.id = json['id']?.toInt(); 15 | } 16 | if (json['name'] != null) { 17 | data.name = json['name']?.toString(); 18 | } 19 | if (json['order'] != null) { 20 | data.order = json['order']?.toInt(); 21 | } 22 | if (json['parentChapterId'] != null) { 23 | data.parentChapterId = json['parentChapterId']?.toInt(); 24 | } 25 | if (json['userControlSetTop'] != null) { 26 | data.userControlSetTop = json['userControlSetTop']; 27 | } 28 | if (json['visible'] != null) { 29 | data.visible = json['visible']?.toInt(); 30 | } 31 | return data; 32 | } 33 | 34 | Map knowledgeEntityToJson(KnowledgeEntity entity) { 35 | final Map data = new Map(); 36 | if (entity.children != null) { 37 | data['children'] = entity.children.map((v) => v.toJson()).toList(); 38 | } 39 | data['courseId'] = entity.courseId; 40 | data['id'] = entity.id; 41 | data['name'] = entity.name; 42 | data['order'] = entity.order; 43 | data['parentChapterId'] = entity.parentChapterId; 44 | data['userControlSetTop'] = entity.userControlSetTop; 45 | data['visible'] = entity.visible; 46 | return data; 47 | } 48 | 49 | knowledgechildFromJson(KnowledgeChild data, Map json) { 50 | if (json['children'] != null) { 51 | data.children = new List(); 52 | data.children.addAll(json['children']); 53 | } 54 | if (json['courseId'] != null) { 55 | data.courseId = json['courseId']?.toInt(); 56 | } 57 | if (json['id'] != null) { 58 | data.id = json['id']?.toInt(); 59 | } 60 | if (json['name'] != null) { 61 | data.name = json['name']?.toString(); 62 | } 63 | if (json['order'] != null) { 64 | data.order = json['order']?.toInt(); 65 | } 66 | if (json['parentChapterId'] != null) { 67 | data.parentChapterId = json['parentChapterId']?.toInt(); 68 | } 69 | if (json['userControlSetTop'] != null) { 70 | data.userControlSetTop = json['userControlSetTop']; 71 | } 72 | if (json['visible'] != null) { 73 | data.visible = json['visible']?.toInt(); 74 | } 75 | return data; 76 | } 77 | 78 | Map knowledgechildToJson(KnowledgeChild entity) { 79 | final Map data = new Map(); 80 | if (entity.children != null) { 81 | data['children'] = []; 82 | } 83 | data['courseId'] = entity.courseId; 84 | data['id'] = entity.id; 85 | data['name'] = entity.name; 86 | data['order'] = entity.order; 87 | data['parentChapterId'] = entity.parentChapterId; 88 | data['userControlSetTop'] = entity.userControlSetTop; 89 | data['visible'] = entity.visible; 90 | return data; 91 | } -------------------------------------------------------------------------------- /lib/generated/json/login_data_entity_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:wanandroidflutter/data/model/login_data_entity.dart'; 2 | 3 | loginDataEntityFromJson(LoginDataEntity data, Map json) { 4 | if (json['admin'] != null) { 5 | data.admin = json['admin']; 6 | } 7 | if (json['chapterTops'] != null) { 8 | data.chapterTops = new List(); 9 | data.chapterTops.addAll(json['chapterTops']); 10 | } 11 | if (json['collectIds'] != null) { 12 | data.collectIds = json['collectIds']?.map((v) => v?.toInt())?.toList()?.cast(); 13 | } 14 | if (json['email'] != null) { 15 | data.email = json['email']?.toString(); 16 | } 17 | if (json['icon'] != null) { 18 | data.icon = json['icon']?.toString(); 19 | } 20 | if (json['id'] != null) { 21 | data.id = json['id']?.toInt(); 22 | } 23 | if (json['nickname'] != null) { 24 | data.nickname = json['nickname']?.toString(); 25 | } 26 | if (json['password'] != null) { 27 | data.password = json['password']?.toString(); 28 | } 29 | if (json['publicName'] != null) { 30 | data.publicName = json['publicName']?.toString(); 31 | } 32 | if (json['token'] != null) { 33 | data.token = json['token']?.toString(); 34 | } 35 | if (json['type'] != null) { 36 | data.type = json['type']?.toInt(); 37 | } 38 | if (json['username'] != null) { 39 | data.username = json['username']?.toString(); 40 | } 41 | return data; 42 | } 43 | 44 | Map loginDataEntityToJson(LoginDataEntity entity) { 45 | final Map data = new Map(); 46 | data['admin'] = entity.admin; 47 | if (entity.chapterTops != null) { 48 | data['chapterTops'] = []; 49 | } 50 | data['collectIds'] = entity.collectIds; 51 | data['email'] = entity.email; 52 | data['icon'] = entity.icon; 53 | data['id'] = entity.id; 54 | data['nickname'] = entity.nickname; 55 | data['password'] = entity.password; 56 | data['publicName'] = entity.publicName; 57 | data['token'] = entity.token; 58 | data['type'] = entity.type; 59 | data['username'] = entity.username; 60 | return data; 61 | } -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/services.dart'; 5 | import 'package:wanandroidflutter/page/wan_android_page.dart'; 6 | 7 | void main() async { 8 | if (Platform.isAndroid) { 9 | // 以下两行 设置android状态栏为透明的沉浸。写在组件渲染之后, 10 | // 是为了在渲染后进行set赋值,覆盖状态栏,写在渲染之前MaterialApp组件会覆盖掉这个值。 11 | SystemUiOverlayStyle systemUiOverlayStyle = SystemUiOverlayStyle(statusBarColor: Colors.transparent); 12 | SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle); 13 | } 14 | runApp(WanAndroidApp()); 15 | } 16 | -------------------------------------------------------------------------------- /lib/page/about/about_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:url_launcher/url_launcher.dart'; 3 | import 'package:wanandroidflutter/constant/app_colors.dart'; 4 | import 'package:wanandroidflutter/util/tool_utils.dart'; 5 | 6 | ///2020年04月03日21:18:12 7 | ///关于作者 8 | ///xfhy 9 | 10 | class AboutUsPage extends StatelessWidget { 11 | @override 12 | Widget build(BuildContext context) { 13 | return Scaffold( 14 | appBar: ToolUtils.getCommonAppBar(context, "关于作者"), 15 | body: ListView( 16 | children: [ 17 | //头像 18 | buildAvatar(), 19 | buildAboutItem("GitHub地址", "这个项目是我在学习Flutter之后,写的练手项目.功能还是比较完善,基本把WanAndroid核心功能都实现了", "https://github.com/xfhy/WanAndroid-Flutter"), 20 | buildAboutItem("CSDN", "一名Android菜鸟的进阶之路", "https://blog.csdn.net/xfhy_"), 21 | buildAboutItem("掘金", "一名Android菜鸟", "https://juejin.im/user/5983fc6b6fb9a03c5539c8bb"), 22 | ], 23 | ), 24 | ); 25 | } 26 | 27 | ///构建头像 28 | Widget buildAvatar() { 29 | return Center( 30 | child: Container( 31 | margin: EdgeInsets.only(top: 10, bottom: 10), 32 | width: 200, 33 | height: 200, 34 | child: Image.network("https://i.loli.net/2018/11/09/5be4f534dd326.jpg"), 35 | ), 36 | ); 37 | } 38 | 39 | buildAboutItem(String title, String content, String url) { 40 | return InkWell( 41 | child: Padding( 42 | padding: EdgeInsets.all(15.0), 43 | child: Row( 44 | children: [ 45 | Expanded( 46 | child: Column( 47 | crossAxisAlignment: CrossAxisAlignment.start, 48 | children: [ 49 | Text( 50 | title, 51 | style: TextStyle(color: Colors.black), 52 | ), 53 | Text( 54 | content, 55 | style: TextStyle(color: Colors.black54, fontSize: 13.0), 56 | ), 57 | ], 58 | ), 59 | ), 60 | Icon( 61 | Icons.chevron_right, 62 | color: AppColors.colorPrimary, 63 | ), 64 | ], 65 | ), 66 | ), 67 | onTap: () async { 68 | ///用系统浏览器打开 69 | if (await canLaunch(url)) { 70 | await launch(url); 71 | } else { 72 | ToolUtils.showToast(msg: "打开浏览器失败"); 73 | } 74 | }, 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/page/favorite/my_favorite_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:wanandroidflutter/data/data_utils.dart'; 3 | import 'package:wanandroidflutter/data/model/article_data_entity.dart'; 4 | import 'package:wanandroidflutter/page/item/article_item.dart'; 5 | import 'package:wanandroidflutter/util/log_util.dart'; 6 | import 'package:wanandroidflutter/util/tool_utils.dart'; 7 | import 'package:wanandroidflutter/widget/refresh/refresh_page.dart'; 8 | 9 | ///2020年04月03日20:20:41 10 | ///我的收藏 11 | ///xfhy 12 | 13 | class FavoritePage extends StatefulWidget { 14 | @override 15 | State createState() { 16 | return _FavoritePageState(); 17 | } 18 | } 19 | 20 | class _FavoritePageState extends State with AutomaticKeepAliveClientMixin { 21 | @override 22 | Widget build(BuildContext context) { 23 | super.build(context); 24 | return Scaffold( 25 | appBar: ToolUtils.getCommonAppBar(context, "我的收藏"), 26 | body: RefreshPage( 27 | requestApi: _getArticleData, 28 | renderItem: _buildArticleItem, 29 | ), 30 | ); 31 | } 32 | 33 | Future _getArticleData([Map params]) async { 34 | var pageIndex = (params is Map) ? params['pageIndex'] : 0; 35 | //组装一个json 方便刷新页page 那边取数据 36 | Map result = {"list": [], 'total': 0, 'pageIndex': pageIndex}; 37 | 38 | //当前是展示作者的文章 39 | await dataUtils.getCollectArticles(pageIndex).then((ArticleDataEntity articleDataEntity) { 40 | if (articleDataEntity != null && articleDataEntity.datas != null) { 41 | //收藏 都是收藏了的,,但是返回的是null 42 | for (ArticleData articleData in articleDataEntity.datas) { 43 | articleData.collect = true; 44 | } 45 | //页数+1 46 | pageIndex++; 47 | result = { 48 | "list": articleDataEntity.datas, 49 | 'total': articleDataEntity.total, 50 | 'pageIndex': pageIndex, 51 | }; 52 | } 53 | }, onError: (e) { 54 | LogUtil.d("发送错误 ${e.toString()}"); 55 | }); 56 | 57 | return result; 58 | } 59 | 60 | ///构建文章item 61 | Widget _buildArticleItem(int index, ArticleData itemData) { 62 | return ArticleItem( 63 | itemData, 64 | isHomeShow: false, 65 | isClickUser: false, 66 | isMyFavoritePage: true, 67 | ); 68 | } 69 | 70 | @override 71 | bool get wantKeepAlive { 72 | return true; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/page/home_list_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:wanandroidflutter/common/application.dart'; 3 | import 'package:wanandroidflutter/data/data_utils.dart'; 4 | import 'package:wanandroidflutter/data/model/article_data_entity.dart'; 5 | import 'package:wanandroidflutter/widget/home_banner.dart'; 6 | import 'package:wanandroidflutter/widget/refresh/refresh_page.dart'; 7 | 8 | import 'item/article_item.dart'; 9 | 10 | ///首页 11 | class HomeListPage extends StatefulWidget { 12 | @override 13 | State createState() => _HomeListPageState(); 14 | } 15 | 16 | class _HomeListPageState extends State 17 | with AutomaticKeepAliveClientMixin { 18 | HomeBanner _homeBanner = HomeBanner(); 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | getBannerData(); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | super.build(context); 29 | return Column( 30 | children: [ 31 | Expanded( 32 | child: RefreshPage( 33 | requestApi: getArticleData, 34 | renderItem: buildItem, 35 | headerView: _getHeaderView, 36 | isHaveHeader: true, 37 | ), 38 | ), 39 | ], 40 | ); 41 | } 42 | 43 | _getHeaderView() { 44 | return _homeBanner; 45 | } 46 | 47 | void getBannerData() async { 48 | var datas = await dataUtils.getBannerData(); 49 | //1. 可能这个网络请求很快就回来了,这时还没创建banner Widget,就可以直接给它赋值 50 | _homeBanner.setBannerData(datas); 51 | //封装的事件 发送到EventBus 事件总线,然后Banner那边接收到 刷新UI 52 | //2. 否则 发一个事件出去,banner那边先完成build Widget的话,也能收到. 53 | Application.eventBus.fire(BannerDataEvent(datas)); 54 | } 55 | 56 | ///获取文章数据 57 | Future getArticleData([Map params]) async { 58 | var pageIndex = (params is Map) ? params['pageIndex'] : 0; 59 | //请求列表 顶部数据+下面的文章数据 60 | List requestList = []; 61 | if (pageIndex == 0) { 62 | requestList.add(dataUtils.getTopArticleData()); 63 | } 64 | requestList.add(dataUtils.getArticleData(pageIndex)); 65 | 66 | //组装一个json 方便刷新页page 那边取数据 67 | Map result; 68 | 69 | //所有请求都回来了,才执行后面的操作 70 | await Future.wait(requestList).then((List listData) { 71 | //需要将顶部数据List 和 正常文章数据ArticleDataEntity中的datas进行合并,组成一个新的List 72 | if (listData == null) { 73 | result = {"list": listData, 'total': 0, 'pageIndex': pageIndex}; 74 | } else { 75 | List articleAllList = []; 76 | var totalCount = 0; 77 | 78 | //添加数据到集合中 79 | for (var value in listData) { 80 | if (value is List) { 81 | //标记 置顶的数据 82 | articleAllList.addAll(value); 83 | } else if (value is ArticleDataEntity) { 84 | articleAllList.addAll(value.datas); 85 | //listTotalSize = value.total; 86 | totalCount = value.total; 87 | } 88 | } 89 | 90 | //页数+1 91 | pageIndex++; 92 | 93 | result = { 94 | "list": articleAllList, 95 | 'total': totalCount, 96 | 'pageIndex': pageIndex 97 | }; 98 | } 99 | }); 100 | 101 | return result; 102 | } 103 | 104 | ///构建item 105 | Widget buildItem(int index, ArticleData itemData) { 106 | if (index == 0) { 107 | return Container( 108 | child: _homeBanner, 109 | ); 110 | } 111 | index -= 1; 112 | 113 | //构建item视图 114 | return ArticleItem(itemData); 115 | } 116 | 117 | @override 118 | bool get wantKeepAlive { 119 | return true; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /lib/page/item/article_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:wanandroidflutter/constant/app_colors.dart'; 3 | import 'package:wanandroidflutter/constant/routes.dart'; 4 | import 'package:wanandroidflutter/data/data_utils.dart'; 5 | import 'package:wanandroidflutter/data/model/article_data_entity.dart'; 6 | import 'package:wanandroidflutter/page/knowledge/knowledge_page.dart'; 7 | import 'package:wanandroidflutter/page/knowledge/knowledge_page_data.dart'; 8 | import 'package:wanandroidflutter/page/webview/route_web_page_data.dart'; 9 | import 'package:wanandroidflutter/util/log_util.dart'; 10 | import 'package:wanandroidflutter/util/tool_utils.dart'; 11 | 12 | ///2020年03月22日22:01:38 13 | ///文章的item 14 | ///xfhy 15 | 16 | class ArticleItem extends StatefulWidget { 17 | final ArticleData itemData; 18 | 19 | ///是否为首页展示 如果是则可以点击进入知识体系 20 | final bool isHomeShow; 21 | 22 | ///是否可以点击作者,跳转作者的文章 23 | final bool isClickUser; 24 | 25 | //是否为我的收藏页 调用取消收藏的API不同 26 | final bool isMyFavoritePage; 27 | 28 | ArticleItem( 29 | this.itemData, { 30 | Key key, 31 | this.isHomeShow = true, 32 | this.isClickUser = true, 33 | this.isMyFavoritePage = false, 34 | }) : super(key: key); 35 | 36 | @override 37 | State createState() { 38 | return _ArticleItemState(); 39 | } 40 | } 41 | 42 | class _ArticleItemState extends State { 43 | @override 44 | Widget build(BuildContext context) { 45 | List rightWidgetList = []; 46 | 47 | rightWidgetList.add( 48 | Text( 49 | ToolUtils.signToStr(widget.itemData.title), 50 | style: TextStyle( 51 | color: Colors.black, 52 | fontSize: 15.0, 53 | fontWeight: FontWeight.bold, 54 | ), 55 | //只展示一行 56 | maxLines: 1, 57 | //超出一行 显示... 58 | overflow: TextOverflow.ellipsis, 59 | ), 60 | ); 61 | 62 | //构建中间的tag 63 | var tagsList = _buildMiddleTags(); 64 | if (tagsList != null) { 65 | rightWidgetList.add(tagsList); 66 | } 67 | 68 | //底部的作者 时间 等信息 69 | rightWidgetList.add(_buildBottomInfo()); 70 | 71 | return Card( 72 | elevation: 4.0, 73 | margin: EdgeInsets.symmetric(horizontal: 8.0, vertical: 6.0), 74 | //加了InkWell 点击有水波纹效果 75 | child: InkWell( 76 | child: Row( 77 | children: [ 78 | Container( 79 | height: 70, 80 | width: 70, 81 | child: Center( 82 | child: IconButton( 83 | icon: Icon(ToolUtils.getNotNullBool(widget.itemData.collect) ? Icons.favorite : Icons.favorite_border), 84 | color: ToolUtils.getNotNullBool(widget.itemData.collect) ? Colors.deepOrange : Colors.grey, 85 | onPressed: collectArticle, 86 | ), 87 | ), 88 | ), 89 | 90 | //右边 写成三行,标题+tag+底部那些信息 用好看的那个wanandroid的布局 91 | Expanded( 92 | child: Column( 93 | crossAxisAlignment: CrossAxisAlignment.start, 94 | children: rightWidgetList, 95 | ), 96 | ), 97 | ], 98 | ), 99 | onTap: _onClickArticleItem, 100 | ), 101 | ); 102 | } 103 | 104 | ///文章 item 点击事件 105 | void _onClickArticleItem() { 106 | RouteWebPageData pageData = 107 | new RouteWebPageData(id: widget.itemData.id, title: widget.itemData.title, url: widget.itemData.link, collect: widget.itemData.collect); 108 | Navigator.pushNamed(context, Routes.webViewPage, arguments: pageData); 109 | } 110 | 111 | //构建中间的tag 112 | _buildMiddleTags() { 113 | List tagsList = []; 114 | //加入置顶标签 115 | if (1 == widget.itemData.type) { 116 | tagsList.add(ToolUtils.buildStrokeTagWidget('置顶', Colors.redAccent)); 117 | } 118 | //加入 新 标签 119 | if (widget.itemData.fresh != null && widget.itemData.fresh) { 120 | tagsList.add(ToolUtils.buildStrokeTagWidget('新', Colors.redAccent)); 121 | } 122 | //加入 tag 标签 123 | if (widget.itemData.tags != null && widget.itemData.tags.length > 0) { 124 | tagsList.addAll(widget.itemData.tags.map((item) => ToolUtils.buildStrokeTagWidget(item.name, Colors.green)).toList()); 125 | } 126 | if (tagsList.length > 0) { 127 | return Container( 128 | margin: EdgeInsets.symmetric(vertical: 3.0, horizontal: 0.0), 129 | child: Row( 130 | children: tagsList, 131 | ), 132 | ); 133 | } else { 134 | return Container( 135 | height: 18, 136 | ); 137 | } 138 | } 139 | 140 | Widget _buildBottomInfo() { 141 | List infoList = []; 142 | var itemData = widget.itemData; 143 | 144 | //图标 145 | infoList.add(Icon( 146 | itemData.author == "" ? Icons.folder_shared : Icons.person, 147 | color: AppColors.colorPrimary, 148 | size: 20.0, 149 | )); 150 | 151 | //作者 或者 分享者 152 | infoList.add( 153 | GestureDetector( 154 | child: Padding( 155 | padding: EdgeInsets.only( 156 | /*top: 10.0, bottom: 10.0,*/ 157 | left: 5.0, 158 | right: 6.0), 159 | child: Text( 160 | ToolUtils.isNullOrEmpty(itemData.author) ? ToolUtils.getNotEmptyStr(itemData.shareUser) : itemData.author, 161 | //只展示一行 162 | maxLines: 1, 163 | //超出 展示... 164 | overflow: TextOverflow.ellipsis, 165 | style: TextStyle( 166 | color: widget.isClickUser ? Colors.blue : Colors.black54, 167 | fontSize: 10.0, 168 | ), 169 | ), 170 | ), 171 | onTap: gotoAuthorListPage, 172 | ), 173 | ); 174 | 175 | //时间 176 | infoList.add(Expanded( 177 | child: Text( 178 | '时间: ' + ToolUtils.getFirstDate(itemData.niceDate), 179 | style: TextStyle( 180 | color: Colors.black54, 181 | fontSize: 10.0, 182 | ), 183 | maxLines: 1, 184 | overflow: TextOverflow.ellipsis, 185 | ), 186 | )); 187 | 188 | //chapter 分类 189 | infoList.add( 190 | Padding( 191 | padding: EdgeInsets.only(right: 10), 192 | child: GestureDetector( 193 | child: Text( 194 | ToolUtils.getNotEmptyStr(itemData.superChapterName) + " / " + ToolUtils.getNotEmptyStr(itemData.chapterName), 195 | maxLines: 1, 196 | style: TextStyle( 197 | color: widget.isHomeShow ? Colors.blue : Colors.black54, 198 | fontSize: 10.0, 199 | decoration: TextDecoration.none, 200 | ), 201 | ), 202 | onTap: gotoKnowledgeArticleList, 203 | ), 204 | ), 205 | ); 206 | 207 | return Row( 208 | children: infoList, 209 | ); 210 | } 211 | 212 | //收藏文章 213 | void collectArticle() async { 214 | bool isLogin = await dataUtils.isLogin(); 215 | if (!isLogin) { 216 | //未登录,跳转到登录界面 217 | Navigator.pushNamed(context, Routes.loginPage); 218 | return; 219 | } 220 | 221 | //已登录 222 | 223 | //之前已收藏 那么就是取消收藏 224 | if (ToolUtils.getNotNullBool(widget.itemData.collect)) { 225 | //这里区分一下 在我的收藏页调用取消收藏的接口 不一样 226 | if (widget.isMyFavoritePage) { 227 | await dataUtils.cancelCollectArticleForMyFavoritePage(widget.itemData.id, widget.itemData.originId == null ? "-1" : widget.itemData.originId); 228 | } else { 229 | await dataUtils.cancelCollectArticle(widget.itemData.id); 230 | } 231 | setState(() { 232 | widget.itemData.collect = false; 233 | }); 234 | ToolUtils.showToast(msg: "取消收藏成功"); 235 | } else { 236 | //收藏 237 | await dataUtils.collectArticle(widget.itemData.id); 238 | setState(() { 239 | widget.itemData.collect = true; 240 | }); 241 | ToolUtils.showToast(msg: "收藏成功"); 242 | } 243 | } 244 | 245 | ///查看作者文章 or 分享人的文章 246 | void gotoAuthorListPage() { 247 | if (widget.isClickUser) { 248 | //如果作者不为空,说明可以根据作者昵称查看文章 否则查看 分享人 列表数据 249 | if (widget.itemData.author == "") { 250 | KnowledgePageData knowledgePageData = 251 | KnowledgePageData(KnowledgePage.SHARE_AUTHOR_PAGE_TYPE, userId: widget.itemData.userId, title: widget.itemData.shareUser); 252 | Navigator.pushNamed(context, Routes.knowledgePage, arguments: knowledgePageData); 253 | } else { 254 | KnowledgePageData knowledgePageData = 255 | KnowledgePageData(KnowledgePage.AUTHOR_PAGE_TYPE, userId: widget.itemData.userId, title: widget.itemData.author); 256 | Navigator.pushNamed(context, Routes.knowledgePage, arguments: knowledgePageData); 257 | } 258 | LogUtil.d(widget.itemData.toString()); 259 | } 260 | } 261 | 262 | ///跳转到 知识体系文章列表 263 | void gotoKnowledgeArticleList() { 264 | if (widget.isHomeShow) { 265 | KnowledgePageData knowledgePageData = 266 | KnowledgePageData(KnowledgePage.KNOWLEDGE_ARTICLE_PAGE_TYPE, title: widget.itemData.chapterName, cid: widget.itemData.chapterId); 267 | Navigator.pushNamed(context, Routes.knowledgePage, arguments: knowledgePageData); 268 | } 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /lib/page/knowledge/knowledge_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:wanandroidflutter/data/data_utils.dart'; 3 | import 'package:wanandroidflutter/data/model/article_data_entity.dart'; 4 | import 'package:wanandroidflutter/page/item/article_item.dart'; 5 | import 'package:wanandroidflutter/util/log_util.dart'; 6 | import 'package:wanandroidflutter/util/tool_utils.dart'; 7 | import 'package:wanandroidflutter/widget/refresh/refresh_page.dart'; 8 | 9 | import 'knowledge_page_data.dart'; 10 | 11 | ///2020年03月26日22:53:21 12 | ///知识体系 作者的文章列表 13 | ///xfhy 14 | 15 | class KnowledgePage extends StatefulWidget { 16 | ///作者页面类型 17 | static const int AUTHOR_PAGE_TYPE = -1; 18 | 19 | ///分享人页面类型 20 | static const int SHARE_AUTHOR_PAGE_TYPE = -2; 21 | 22 | ///知识体系 文章 页面类型 23 | static const int KNOWLEDGE_ARTICLE_PAGE_TYPE = -3; 24 | 25 | @override 26 | State createState() { 27 | return _KnowledgePageState(); 28 | } 29 | } 30 | 31 | class _KnowledgePageState extends State with AutomaticKeepAliveClientMixin { 32 | int pageType; 33 | int userId; 34 | String title; 35 | int cid; 36 | 37 | @override 38 | bool get wantKeepAlive => true; 39 | 40 | ///构建文章item 41 | Widget buildArticleItem(int index, ArticleData itemData) { 42 | return ArticleItem( 43 | itemData, 44 | isHomeShow: false, 45 | isClickUser: false, 46 | ); 47 | } 48 | 49 | Future getArticleData([Map params]) async { 50 | var pageIndex = (params is Map) ? params['pageIndex'] : 0; 51 | //组装一个json 方便刷新页page 那边取数据 52 | Map result = {"list": [], 'total': 0, 'pageIndex': pageIndex}; 53 | 54 | if (KnowledgePage.AUTHOR_PAGE_TYPE == pageType) { 55 | //当前是展示作者的文章 56 | await dataUtils.getAuthorArticleData(title, pageIndex).then((ArticleDataEntity articleDataEntity) { 57 | if (articleDataEntity != null && articleDataEntity.datas != null) { 58 | //页数+1 59 | pageIndex++; 60 | result = { 61 | "list": articleDataEntity.datas, 62 | 'total': articleDataEntity.total, 63 | 'pageIndex': pageIndex, 64 | }; 65 | } 66 | }, onError: (e) { 67 | LogUtil.d("发送错误 ${e.toString()}"); 68 | }); 69 | } else if (KnowledgePage.SHARE_AUTHOR_PAGE_TYPE == pageType) { 70 | //当前是展示 分享人的文章 71 | await dataUtils.getShareAuthorArticleData(userId, pageIndex).then((ArticleDataEntity articleDataEntity) { 72 | if (articleDataEntity != null && articleDataEntity.datas != null) { 73 | //页数+1 74 | pageIndex++; 75 | result = { 76 | "list": articleDataEntity.datas, 77 | 'total': articleDataEntity.total, 78 | 'pageIndex': pageIndex, 79 | }; 80 | } 81 | }, onError: (e) { 82 | LogUtil.d("发送错误 ${e.toString()}"); 83 | }); 84 | } else if (KnowledgePage.KNOWLEDGE_ARTICLE_PAGE_TYPE == pageType) { 85 | //当前是展示 知识体系文章 86 | await dataUtils.getKnowledgeArticleData(cid, pageIndex).then((ArticleDataEntity articleDataEntity) { 87 | if (articleDataEntity != null && articleDataEntity.datas != null) { 88 | //页数+1 89 | pageIndex++; 90 | result = { 91 | "list": articleDataEntity.datas, 92 | 'total': articleDataEntity.total, 93 | 'pageIndex': pageIndex, 94 | }; 95 | } 96 | }, onError: (e) { 97 | LogUtil.d("发送错误 ${e.toString()}"); 98 | }); 99 | } 100 | return result; 101 | } 102 | 103 | @override 104 | Widget build(BuildContext context) { 105 | super.build(context); 106 | 107 | //接收传递过来的参数 108 | KnowledgePageData pageData = ModalRoute.of(context).settings.arguments as KnowledgePageData; 109 | pageType = pageData.pageType; 110 | userId = pageData.userId; 111 | title = pageData.title; 112 | cid = pageData.cid; 113 | 114 | return Scaffold( 115 | appBar: ToolUtils.getCommonAppBar(context, title), 116 | body: Column( 117 | children: [ 118 | Expanded( 119 | child: RefreshPage( 120 | requestApi: getArticleData, 121 | renderItem: buildArticleItem, 122 | ), 123 | ), 124 | ], 125 | ), 126 | ); 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /lib/page/knowledge/knowledge_page_data.dart: -------------------------------------------------------------------------------- 1 | ///2020年03月26日22:57:21 2 | ///传递给KnowledgePage的数据 3 | ///xfhy 4 | 5 | class KnowledgePageData { 6 | ///页面类型 7 | int pageType; 8 | int userId; 9 | 10 | //知识体系的id 11 | int cid; 12 | 13 | //作者名称 or 分享人名称 or 知识体系 14 | String title; 15 | 16 | KnowledgePageData(this.pageType, {this.userId, this.title, this.cid}); 17 | } 18 | -------------------------------------------------------------------------------- /lib/page/knowledge_system_tree_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_spinkit/flutter_spinkit.dart'; 3 | import 'package:wanandroidflutter/constant/app_colors.dart'; 4 | import 'package:wanandroidflutter/constant/routes.dart'; 5 | import 'package:wanandroidflutter/data/data_utils.dart'; 6 | import 'package:wanandroidflutter/data/model/knowledge_entity.dart'; 7 | 8 | import 'knowledge/knowledge_page.dart'; 9 | import 'knowledge/knowledge_page_data.dart'; 10 | 11 | ///知识体系 发现 12 | class KnowledgeSystemPage extends StatefulWidget { 13 | @override 14 | State createState() => _KnowledgeSystemPageState(); 15 | } 16 | 17 | class _KnowledgeSystemPageState extends State with AutomaticKeepAliveClientMixin { 18 | List knowledgeEntityList = []; 19 | 20 | @override 21 | bool get wantKeepAlive { 22 | return true; 23 | } 24 | 25 | @override 26 | void initState() { 27 | super.initState(); 28 | loadKnowledgeData(); 29 | } 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | super.build(context); 34 | return ListView( 35 | children: buildTreeItems(), 36 | ); 37 | } 38 | 39 | ///加载知识体系数据 40 | void loadKnowledgeData() async { 41 | await dataUtils.getKnowledgeSystem().then((list) { 42 | if (list == null || list.length == 0) { 43 | //数据加载失败 44 | } else { 45 | if (mounted) { 46 | setState(() { 47 | knowledgeEntityList = list; 48 | }); 49 | } 50 | } 51 | }); 52 | } 53 | 54 | List buildTreeItems() { 55 | if (knowledgeEntityList == null || knowledgeEntityList.length == 0) { 56 | return [ 57 | Container( 58 | height: MediaQuery.of(context).size.height, 59 | child: Center( 60 | child: Row( 61 | mainAxisAlignment: MainAxisAlignment.center, 62 | children: [ 63 | SpinKitCubeGrid( 64 | size: 55.0, 65 | color: AppColors.colorPrimary, 66 | duration: Duration(milliseconds: 800), 67 | ), 68 | Padding( 69 | padding: EdgeInsets.only(left: 10.0), 70 | child: Text( 71 | '正在加载...', 72 | style: TextStyle( 73 | color: Colors.black54, 74 | fontSize: 15.0, 75 | ), 76 | ), 77 | ), 78 | ], 79 | ), 80 | ), 81 | ), 82 | ]; 83 | } else { 84 | List widgets = []; 85 | for (KnowledgeEntity knowledgeEntity in knowledgeEntityList) { 86 | //分组的标题 87 | widgets.add(Padding( 88 | padding: EdgeInsets.all(10.0), 89 | child: Text( 90 | knowledgeEntity.name, 91 | style: TextStyle(color: AppColors.colorPrimary, fontSize: 18.0), 92 | ), 93 | )); 94 | 95 | //流式布局 96 | widgets.add(Padding( 97 | padding: EdgeInsets.only(left: 10.0), 98 | child: Wrap( 99 | //主轴方向上的间距 100 | spacing: 5.0, 101 | runSpacing: 5.0, 102 | children: buildTreeChildItems(knowledgeEntity.children), 103 | ), 104 | )); 105 | } 106 | return widgets; 107 | } 108 | } 109 | 110 | List buildTreeChildItems(List childrenList) { 111 | List widgets = []; 112 | if (childrenList != null || childrenList.length > 0) { 113 | for (KnowledgeChild knowledgeChild in childrenList) { 114 | ActionChip actionChip = getActionChip(knowledgeChild); 115 | widgets.add(actionChip); 116 | } 117 | } else { 118 | //分组内无数据 119 | var child = KnowledgeChild(); 120 | child.name = "暂无数据"; 121 | widgets.add(getActionChip(child)); 122 | } 123 | return widgets; 124 | } 125 | 126 | //构建item 标签 127 | ActionChip getActionChip(KnowledgeChild knowledgeChild) { 128 | return ActionChip( 129 | backgroundColor: Colors.blue, 130 | label: Text( 131 | knowledgeChild.name, 132 | style: TextStyle(color: Colors.white), 133 | ), 134 | onPressed: () { 135 | KnowledgePageData knowledgePageData = 136 | KnowledgePageData(KnowledgePage.KNOWLEDGE_ARTICLE_PAGE_TYPE, title: knowledgeChild.name, cid: knowledgeChild.id); 137 | Navigator.pushNamed(context, Routes.knowledgePage, arguments: knowledgePageData); 138 | }, 139 | ); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /lib/page/login/login_event.dart: -------------------------------------------------------------------------------- 1 | ///2020年04月03日07:34:47 2 | ///登录事件 3 | ///xfhy 4 | 5 | class LoginEvent { 6 | String username; 7 | 8 | LoginEvent(this.username); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /lib/page/login/login_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:wanandroidflutter/common/application.dart'; 4 | import 'package:wanandroidflutter/constant/app_colors.dart'; 5 | import 'package:wanandroidflutter/data/data_utils.dart'; 6 | import 'package:wanandroidflutter/data/model/login_data_entity.dart'; 7 | import 'package:wanandroidflutter/page/login/login_event.dart'; 8 | import 'package:wanandroidflutter/util/tool_utils.dart'; 9 | 10 | ///登录界面 11 | ///2020年03月28日10:45:04 12 | ///xfhy 13 | class LoginPage extends StatefulWidget { 14 | @override 15 | State createState() { 16 | return _LoginPagePage(); 17 | } 18 | } 19 | 20 | class _LoginPagePage extends State { 21 | bool isShowPassWord = false; 22 | 23 | TextEditingController _nameController = TextEditingController(); 24 | TextEditingController _pwdController = TextEditingController(); 25 | 26 | @override 27 | void initState() { 28 | super.initState(); 29 | //设置调试时的数据 30 | if (Application.isDebug) { 31 | _nameController.text = "xxxxxxx415456465465"; 32 | _pwdController.text = "xxxxxxx"; 33 | } 34 | } 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | return Scaffold( 39 | appBar: ToolUtils.getCommonAppBar(context, "登录"), 40 | body: SingleChildScrollView( 41 | child: Container( 42 | height: MediaQuery.of(context).size.height, 43 | color: AppColors.colorPrimary, 44 | child: Column( 45 | //自适应 大小 这里是高 46 | mainAxisSize: MainAxisSize.min, 47 | children: [ 48 | Card( 49 | //阴影 50 | elevation: 10.0, 51 | margin: EdgeInsets.only(top: MediaQuery.of(context).size.height / 5.0, left: 10.0, right: 10.0), 52 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10.0))), 53 | child: Container( 54 | width: MediaQuery.of(context).size.width, 55 | child: Padding( 56 | padding: EdgeInsets.all(10.0), 57 | child: Column( 58 | crossAxisAlignment: CrossAxisAlignment.center, 59 | children: [ 60 | //顶部图标 61 | Container( 62 | width: 70, 63 | height: 70, 64 | margin: EdgeInsets.only(top: 10.0, bottom: 10.0), 65 | child: Image.asset( 66 | "images/ic_launcher.png", 67 | fit: BoxFit.cover, 68 | ), 69 | ), 70 | 71 | //输入item 72 | buildSignInTextForm(), 73 | 74 | //有点类似于Android中的Space 75 | SizedBox(height: 15.0), 76 | 77 | //构建底部按钮 78 | buildBottomBtn(), 79 | ], 80 | ), 81 | ), 82 | ), 83 | ) 84 | ], 85 | ), 86 | ), 87 | ), 88 | ); 89 | } 90 | 91 | void _pushBack() { 92 | Navigator.pop(context); 93 | } 94 | 95 | Widget buildSignInTextForm() { 96 | return Column( 97 | children: [ 98 | TextFormField( 99 | //自动获取焦点 打开键盘 100 | autofocus: true, 101 | keyboardType: TextInputType.text, 102 | decoration: InputDecoration( 103 | icon: Icon( 104 | Icons.person, 105 | color: Colors.black, 106 | ), 107 | hintText: "WanAndroid 用户名", 108 | labelText: "用户名", 109 | border: UnderlineInputBorder(), 110 | ), 111 | controller: _nameController, 112 | validator: (username) { 113 | if (username == null || username.isEmpty) { 114 | return "用户名不能为空!"; 115 | } 116 | return null; 117 | }, 118 | ), 119 | TextFormField( 120 | autofocus: false, 121 | keyboardType: TextInputType.visiblePassword, 122 | //是否隐藏输入内容 true:隐藏 123 | obscureText: !isShowPassWord, 124 | decoration: InputDecoration( 125 | icon: Icon( 126 | Icons.lock, 127 | color: Colors.black, 128 | ), 129 | hintText: "WanAndroid 登录密码", 130 | labelText: "密码", 131 | border: UnderlineInputBorder(), 132 | suffixIcon: IconButton( 133 | icon: Icon(Icons.remove_red_eye), 134 | color: Colors.black, 135 | onPressed: showPassWord, 136 | ), 137 | ), 138 | controller: _pwdController, 139 | ), 140 | ], 141 | ); 142 | } 143 | 144 | ///构建底部按钮 145 | Widget buildBottomBtn() { 146 | return Row( 147 | children: [ 148 | SizedBox(width: 15.0), 149 | Expanded( 150 | child: RaisedButton( 151 | child: Text("登录"), 152 | color: AppColors.colorPrimary, 153 | onPressed: () { 154 | login(); 155 | }, 156 | ), 157 | ), 158 | SizedBox(width: 30.0), 159 | Expanded( 160 | child: RaisedButton( 161 | color: AppColors.colorPrimary, 162 | child: Text("注册"), 163 | onPressed: () { 164 | register(); 165 | }, 166 | ), 167 | ), 168 | SizedBox(width: 15.0), 169 | ], 170 | ); 171 | } 172 | 173 | ///控制是否展示明文密码 174 | void showPassWord() { 175 | setState(() { 176 | isShowPassWord = !isShowPassWord; 177 | }); 178 | } 179 | 180 | bool validInput() { 181 | if (_nameController.text == null || _nameController.text == "") { 182 | ToolUtils.showToast(msg: "用户名儿得填一个吧"); 183 | return false; 184 | } 185 | if (_pwdController.text == null || _pwdController.text == "") { 186 | ToolUtils.showToast(msg: "密码得填一个吧"); 187 | return false; 188 | } 189 | if (_pwdController.text.length < 6) { 190 | ToolUtils.showToast(msg: "密码长度太短了吧,兄嘚"); 191 | return false; 192 | } 193 | return true; 194 | } 195 | 196 | //登录 197 | void login() async { 198 | if (!validInput()) { 199 | return; 200 | } 201 | //收起软键盘 202 | FocusScope.of(context).requestFocus(FocusNode()); 203 | LoginDataEntity loginDataEntity = await dataUtils.login(_nameController.text, _pwdController.text, context); 204 | if (loginDataEntity == null || loginDataEntity.username == null || loginDataEntity.username == "") { 205 | //ToolUtils.showToast(msg: "登录失败"); 206 | } else { 207 | await dataUtils.setLoginState(true); 208 | await dataUtils.setLoginUserName(loginDataEntity.username); 209 | 210 | //发送事件 211 | Application.eventBus.fire(LoginEvent(loginDataEntity.username)); 212 | 213 | ToolUtils.showToast(msg: "登录成功"); 214 | Navigator.of(context).pop(); 215 | } 216 | } 217 | 218 | //注册 219 | void register() async { 220 | if (!validInput()) { 221 | return; 222 | } 223 | //收起软键盘 224 | FocusScope.of(context).requestFocus(FocusNode()); 225 | LoginDataEntity loginDataEntity = await dataUtils.register(_nameController.text, _pwdController.text, context); 226 | if (loginDataEntity == null || loginDataEntity.username == null || loginDataEntity.username == "") { 227 | //ToolUtils.showToast(msg: "注册失败"); 228 | } else { 229 | dataUtils.setLoginState(true); 230 | ToolUtils.showToast(msg: "登录成功"); 231 | Navigator.of(context).pop(); 232 | } 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /lib/page/myinfo_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:wanandroidflutter/common/application.dart'; 3 | import 'package:wanandroidflutter/constant/app_colors.dart'; 4 | import 'package:wanandroidflutter/constant/routes.dart'; 5 | import 'package:wanandroidflutter/data/data_utils.dart'; 6 | import 'package:wanandroidflutter/util/log_util.dart'; 7 | import 'package:wanandroidflutter/util/tool_utils.dart'; 8 | 9 | import 'login/login_event.dart'; 10 | 11 | ///我的 12 | class MyInfoPage extends StatefulWidget { 13 | @override 14 | State createState() => _MyInfoPageState(); 15 | } 16 | 17 | class _MyInfoPageState extends State { 18 | String userName; 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | getUserName(); 24 | 25 | //监听事件总线上数据变化 26 | Application.eventBus.on().listen((event) { 27 | if (mounted) { 28 | setState(() { 29 | userName = event.username; 30 | }); 31 | } 32 | }); 33 | } 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | return ListView( 38 | children: _buildListChild(), 39 | ); 40 | } 41 | 42 | List _buildListChild() { 43 | return [ 44 | //头像 45 | buildAvatar(), 46 | //登录按钮 47 | buildLoginBtn(), 48 | //我的收藏 49 | buildMyCollect(), 50 | //每日一问 51 | buildEveryDayQuestion(), 52 | //清楚缓存 53 | buildClearCache(), 54 | //关于作者 55 | buildAboutUs(), 56 | //退出登录 57 | buildLoginOut(), 58 | ]; 59 | } 60 | 61 | List getWidget() { 62 | if (Application.isLogin) { 63 | return [ 64 | RaisedButton( 65 | child: Text('退出登录'), 66 | onPressed: () { 67 | LogUtil.d("跳转"); 68 | setState(() { 69 | Application.isLogin = false; 70 | }); 71 | loginOut(); 72 | }, 73 | ), 74 | ]; 75 | } else { 76 | return [ 77 | RaisedButton( 78 | child: Text('跳转登录界面'), 79 | onPressed: () { 80 | LogUtil.d("跳转"); 81 | Navigator.pushNamed(context, Routes.loginPage); 82 | }, 83 | ), 84 | ]; 85 | } 86 | } 87 | 88 | ///构建头像 89 | Widget buildAvatar() { 90 | return Center( 91 | child: Container( 92 | margin: EdgeInsets.only(top: 10, bottom: 10), 93 | width: 100, 94 | height: 100, 95 | child: CircleAvatar( 96 | // backgroundColor: Colors.white, 97 | backgroundImage: AssetImage( 98 | Application.isLogin ? ToolUtils.getImage("ic_launcher_foreground") : ToolUtils.getImage("ic_default_avatar", format: "webp")), 99 | ), 100 | ), 101 | ); 102 | } 103 | 104 | //构建登录按钮 105 | Widget buildLoginBtn() { 106 | return RaisedButton( 107 | color: AppColors.colorPrimary, 108 | child: Text( 109 | (userName == null || userName == "") ? "请登录" : userName, 110 | style: TextStyle( 111 | color: Colors.white, 112 | ), 113 | ), 114 | onPressed: () { 115 | if (!Application.isLogin) { 116 | Navigator.pushNamed(context, Routes.loginPage); 117 | } 118 | }, 119 | ); 120 | } 121 | 122 | //我的收藏 123 | Widget buildMyCollect() { 124 | return buildCommonItem(Icons.favorite, "我的收藏", () { 125 | //需要登录 126 | LogUtil.d("跳转我的收藏"); 127 | if (Application.isLogin) { 128 | Navigator.pushNamed(context, Routes.favoritePage); 129 | } else { 130 | Navigator.pushNamed(context, Routes.loginPage); 131 | } 132 | }); 133 | } 134 | 135 | //构建每日一问 136 | Widget buildEveryDayQuestion() { 137 | return buildCommonItem(Icons.question_answer, "每日一问", () { 138 | //需要登录 139 | LogUtil.d("跳转每日一问"); 140 | if (Application.isLogin) { 141 | Navigator.pushNamed(context, Routes.questionPage); 142 | } else { 143 | Navigator.pushNamed(context, Routes.loginPage); 144 | } 145 | }); 146 | } 147 | 148 | //构建清除缓存 149 | Widget buildClearCache() { 150 | return buildCommonItem(Icons.clear, "清除缓存", () { 151 | //需要登录 152 | LogUtil.d("展示对话框 是否确认清除"); 153 | ToolUtils.showAlertDialog(context, "确定清除缓存么?", confirmText: "确定", confirmCallback: () { 154 | ToolUtils.clearCookie(); 155 | ToolUtils.showToast(msg: "清除成功"); 156 | }); 157 | }); 158 | } 159 | 160 | //构建关于作者 161 | Widget buildAboutUs() { 162 | return buildCommonItem(Icons.supervised_user_circle, "关于作者", () { 163 | //需要登录 164 | LogUtil.d("跳转关于作者"); 165 | Navigator.pushNamed(context, Routes.aboutPage); 166 | }); 167 | } 168 | 169 | //构建退出 170 | Widget buildLoginOut() { 171 | return buildCommonItem(Icons.exit_to_app, "退出登录", () { 172 | //需要登录 173 | LogUtil.d("退出登录"); 174 | 175 | ToolUtils.showAlertDialog(context, "确定要退出登录么?", confirmText: "确定", confirmCallback: () { 176 | ToolUtils.clearCookie(); 177 | loginOut(); 178 | setState(() { 179 | Application.isLogin = false; 180 | dataUtils.setLoginState(false); 181 | dataUtils.clearUserName(); 182 | userName = null; 183 | ToolUtils.showToast(msg: "退出登录成功"); 184 | }); 185 | }); 186 | }); 187 | } 188 | 189 | //构建通用item 190 | Widget buildCommonItem(IconData iconData, String itemContent, Function clickListener) { 191 | return InkWell( 192 | child: Padding( 193 | padding: EdgeInsets.only(left: 15.0, right: 15.0, bottom: 20.0, top: 20.0), 194 | child: Row( 195 | crossAxisAlignment: CrossAxisAlignment.center, 196 | children: [ 197 | Icon(iconData), 198 | Expanded( 199 | child: Padding( 200 | padding: EdgeInsets.only(left: 20.0), 201 | child: Text( 202 | itemContent, 203 | style: TextStyle(color: Colors.black), 204 | ), 205 | ), 206 | ), 207 | Icon(Icons.chevron_right), 208 | ], 209 | ), 210 | ), 211 | onTap: clickListener, 212 | ); 213 | } 214 | 215 | //退出登录 216 | void loginOut() async { 217 | await dataUtils.loginOut(); 218 | } 219 | 220 | //获取用户名 221 | void getUserName() async { 222 | await dataUtils.getUserName().then((value) { 223 | setState(() { 224 | LogUtil.d("userName = $userName"); 225 | userName = value; 226 | }); 227 | }); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /lib/page/question/question_page.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | import 'package:wanandroidflutter/data/data_utils.dart'; 4 | import 'package:wanandroidflutter/data/model/article_data_entity.dart'; 5 | import 'package:wanandroidflutter/page/item/article_item.dart'; 6 | import 'package:wanandroidflutter/util/log_util.dart'; 7 | import 'package:wanandroidflutter/util/tool_utils.dart'; 8 | import 'package:wanandroidflutter/widget/refresh/refresh_page.dart'; 9 | 10 | ///2020年04月03日20:50:08 11 | ///每日一问 12 | ///xfhy 13 | 14 | class QuestionPage extends StatefulWidget { 15 | @override 16 | State createState() { 17 | return _QuestionPageState(); 18 | } 19 | } 20 | 21 | class _QuestionPageState extends State with AutomaticKeepAliveClientMixin { 22 | @override 23 | Widget build(BuildContext context) { 24 | super.build(context); 25 | return Scaffold( 26 | appBar: ToolUtils.getCommonAppBar(context, "每日一问"), 27 | body: RefreshPage( 28 | requestApi: _getArticleData, 29 | renderItem: _buildArticleItem, 30 | ), 31 | ); 32 | } 33 | 34 | Future _getArticleData([Map params]) async { 35 | var pageIndex = (params is Map) ? params['pageIndex'] : 0; 36 | //组装一个json 方便刷新页page 那边取数据 37 | Map result = {"list": [], 'total': 0, 'pageIndex': pageIndex}; 38 | 39 | //当前是展示作者的文章 40 | await dataUtils.getQuestionArticles(pageIndex).then((ArticleDataEntity articleDataEntity) { 41 | if (articleDataEntity != null && articleDataEntity.datas != null) { 42 | //页数+1 43 | pageIndex++; 44 | result = { 45 | "list": articleDataEntity.datas, 46 | 'total': articleDataEntity.total, 47 | 'pageIndex': pageIndex, 48 | }; 49 | } 50 | }, onError: (e) { 51 | LogUtil.d("发送错误 ${e.toString()}"); 52 | }); 53 | 54 | return result; 55 | } 56 | 57 | ///构建文章item 58 | Widget _buildArticleItem(int index, ArticleData itemData) { 59 | return ArticleItem( 60 | itemData, 61 | isHomeShow: false, 62 | isClickUser: false, 63 | ); 64 | } 65 | 66 | @override 67 | bool get wantKeepAlive { 68 | return true; 69 | } 70 | } 71 | 72 | 73 | -------------------------------------------------------------------------------- /lib/page/search/hot_search_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:wanandroidflutter/constant/app_colors.dart'; 3 | import 'package:wanandroidflutter/constant/routes.dart'; 4 | import 'package:wanandroidflutter/data/data_utils.dart'; 5 | import 'package:wanandroidflutter/data/model/hot_key_entity.dart'; 6 | import 'package:wanandroidflutter/util/log_util.dart'; 7 | 8 | ///热搜 9 | ///2020年03月29日15:28:59 10 | ///xfhy 11 | 12 | class HotSearchWidget extends StatefulWidget { 13 | @override 14 | State createState() { 15 | return _HotSearchWidgetState(); 16 | } 17 | } 18 | 19 | class _HotSearchWidgetState extends State { 20 | List hotKeyWidgets = List(); 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | loadSearchHotKeys(); 26 | } 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return ListView( 31 | children: [ 32 | Padding( 33 | padding: EdgeInsets.all(10.0), 34 | child: Text( 35 | '热搜关键词', 36 | style: TextStyle(color: AppColors.colorPrimary, fontSize: 18.0), 37 | ), 38 | ), 39 | 40 | //流式布局 41 | Padding( 42 | padding: EdgeInsets.only(left: 10.0), 43 | child: Wrap( 44 | //主轴方向上的间距 45 | spacing: 5.0, 46 | runSpacing: 5.0, 47 | children: hotKeyWidgets, 48 | ), 49 | ), 50 | ], 51 | ); 52 | } 53 | 54 | ///获取热搜关键词 55 | void loadSearchHotKeys() async { 56 | List kotKeys = await dataUtils.getSearchHotKeys(); 57 | if (mounted) { 58 | setState(() { 59 | hotKeyWidgets.clear(); 60 | if (kotKeys == null || kotKeys.length == 0) { 61 | hotKeyWidgets.add(getActionChip("暂无数据")); 62 | } else { 63 | for (HotKeyEntity value in kotKeys) { 64 | ActionChip actionChip = getActionChip(value.name); 65 | hotKeyWidgets.add(actionChip); 66 | } 67 | } 68 | }); 69 | } 70 | } 71 | 72 | //构建item 标签 73 | ActionChip getActionChip(String text) { 74 | return ActionChip( 75 | backgroundColor: AppColors.colorPrimary, 76 | label: Text( 77 | text, 78 | style: TextStyle(color: Colors.white), 79 | ), 80 | onPressed: () { 81 | LogUtil.d("跳转搜索详情页"); 82 | Navigator.pushNamed(context, Routes.searchResultPage, arguments: text); 83 | }, 84 | ); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /lib/page/search/search_list_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:wanandroidflutter/data/data_utils.dart'; 3 | import 'package:wanandroidflutter/data/model/article_data_entity.dart'; 4 | import 'package:wanandroidflutter/page/item/article_item.dart'; 5 | import 'package:wanandroidflutter/util/log_util.dart'; 6 | import 'package:wanandroidflutter/util/tool_utils.dart'; 7 | import 'package:wanandroidflutter/widget/refresh/refresh_page.dart'; 8 | 9 | ///搜索结果列表 10 | ///2020年03月29日15:31:15 11 | ///xfhy 12 | 13 | class SearchResultWidget extends StatefulWidget { 14 | String inputKey; 15 | 16 | SearchResultWidget({this.inputKey}); 17 | 18 | @override 19 | State createState() { 20 | return _SearchResultWidgetState(); 21 | } 22 | } 23 | 24 | class _SearchResultWidgetState extends State { 25 | ///是否需要构建标题栏 如果是传入了key,则需要构建 点了关键词进来的 26 | bool needBuildAppBar = false; 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | //获取传过来的结果 31 | String key = ModalRoute.of(context).settings.arguments as String; 32 | if (key != null) { 33 | needBuildAppBar = true; 34 | widget.inputKey = key; 35 | } 36 | 37 | return buildPageWidget(widget.inputKey); 38 | } 39 | 40 | Widget buildPageWidget(String key) { 41 | Widget widget; 42 | if (needBuildAppBar) { 43 | widget = Scaffold( 44 | appBar: ToolUtils.getCommonAppBar(context, key), 45 | body: RefreshPage( 46 | requestApi: getSearchKeyData, 47 | renderItem: buildArticleItem, 48 | ), 49 | ); 50 | } else { 51 | widget = RefreshPage( 52 | requestApi: getSearchKeyData, 53 | renderItem: buildArticleItem, 54 | ); 55 | } 56 | 57 | return widget; 58 | } 59 | 60 | ///构建文章item 61 | Widget buildArticleItem(int index, ArticleData itemData) { 62 | return ArticleItem( 63 | itemData, 64 | isHomeShow: false, 65 | isClickUser: false, 66 | ); 67 | } 68 | 69 | Future getSearchKeyData([Map params]) async { 70 | var pageIndex = (params is Map) ? params['pageIndex'] : 0; 71 | //组装一个json 方便刷新页page 那边取数据 72 | Map result = {"list": [], 'total': 0, 'pageIndex': pageIndex}; 73 | 74 | //当前是展示作者的文章 75 | await dataUtils.search(widget.inputKey, pageIndex, context).then((ArticleDataEntity articleDataEntity) { 76 | if (articleDataEntity != null && articleDataEntity.datas != null) { 77 | //页数+1 78 | pageIndex++; 79 | result = { 80 | "list": articleDataEntity.datas, 81 | 'total': articleDataEntity.total, 82 | 'pageIndex': pageIndex, 83 | }; 84 | } 85 | }, onError: (e) { 86 | LogUtil.d("发送错误 ${e.toString()}"); 87 | }); 88 | 89 | return result; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /lib/page/search/search_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:wanandroidflutter/constant/app_colors.dart'; 3 | import 'package:wanandroidflutter/page/search/hot_search_widget.dart'; 4 | import 'package:wanandroidflutter/page/search/search_list_widget.dart'; 5 | 6 | ///搜索页面 7 | ///2020年03月29日11:49:08 8 | ///xfhy 9 | class SearchPage extends StatefulWidget { 10 | @override 11 | State createState() { 12 | return _SearchPageState(); 13 | } 14 | } 15 | 16 | class _SearchPageState extends State { 17 | bool inputContentEmpty = true; 18 | TextEditingController _searchController = TextEditingController(); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return Scaffold( 23 | appBar: AppBar( 24 | backgroundColor: AppColors.colorPrimary, 25 | title: _buildSearchField(), 26 | actions: _buildAppBarActions(), 27 | ), 28 | body: _buildContentBody(), 29 | ); 30 | } 31 | 32 | Widget _buildSearchField() { 33 | return TextField( 34 | autofocus: true, 35 | textInputAction: TextInputAction.search, 36 | //键盘按下enter键 37 | onSubmitted: (inputContent) { 38 | startSearch(); 39 | }, 40 | style: TextStyle(color: Colors.white), 41 | cursorColor: Colors.white, 42 | decoration: InputDecoration( 43 | border: InputBorder.none, 44 | hintText: "搜索关键词", 45 | hintStyle: TextStyle(color: Colors.white), 46 | ), 47 | controller: _searchController, 48 | ); 49 | } 50 | 51 | ///开始搜索 52 | void startSearch() { 53 | String inputContent = _searchController.text; 54 | setState(() { 55 | inputContentEmpty = (inputContent == null || inputContent == ""); 56 | }); 57 | } 58 | 59 | List _buildAppBarActions() { 60 | return [ 61 | IconButton( 62 | icon: Icon( 63 | Icons.search, 64 | color: Colors.white, 65 | ), 66 | onPressed: () { 67 | //收起软键盘 68 | FocusScope.of(context).requestFocus(FocusNode()); 69 | startSearch(); 70 | }, 71 | ), 72 | IconButton( 73 | icon: Icon( 74 | Icons.clear, 75 | color: Colors.white, 76 | ), 77 | onPressed: () { 78 | setState(() { 79 | inputContentEmpty = true; 80 | _searchController.clear(); 81 | }); 82 | }, 83 | ), 84 | ]; 85 | } 86 | 87 | Widget _buildContentBody() { 88 | return inputContentEmpty ? HotSearchWidget() : SearchResultWidget(inputKey: _searchController.text); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/page/wan_android_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:event_bus/event_bus.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:wanandroidflutter/common/application.dart'; 4 | import 'package:wanandroidflutter/constant/app_colors.dart'; 5 | import 'package:wanandroidflutter/constant/routes.dart'; 6 | import 'package:wanandroidflutter/page/knowledge_system_tree_page.dart'; 7 | 8 | import 'home_list_page.dart'; 9 | import 'myinfo_page.dart'; 10 | 11 | ///主页面 12 | 13 | class WanAndroidApp extends StatelessWidget { 14 | WanAndroidApp() { 15 | //初始化路由 16 | Routes.init(); 17 | } 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return MaterialApp(routes: Routes.routes, home: WanAndroidHomePage()); 22 | } //一般是需要下划线开头,然后后面加一个State 23 | } 24 | 25 | class WanAndroidHomePage extends StatefulWidget { 26 | @override 27 | State createState() { 28 | return _WanAndroidHomePageState(); 29 | } 30 | } 31 | 32 | class _WanAndroidHomePageState extends State { 33 | var currentPage = 0; 34 | PageController _pageController; 35 | final appBarTitles = ['首页', '知识体系', '我的']; 36 | 37 | @override 38 | void initState() { 39 | super.initState(); 40 | _pageController = PageController(); 41 | Application.eventBus = EventBus(); 42 | Application.init(); 43 | } 44 | 45 | @override 46 | void dispose() { 47 | super.dispose(); 48 | Application.eventBus.destroy(); 49 | } 50 | 51 | @override 52 | Widget build(BuildContext context) { 53 | return MaterialApp( 54 | theme: ThemeData( 55 | primaryColor: AppColors.colorPrimary, 56 | accentColor: AppColors.accentColor, 57 | textTheme: TextTheme( 58 | //设置Material的默认字体样式 59 | body1: TextStyle( 60 | color: Color(0xFF888888), 61 | fontSize: 16.0, 62 | ), 63 | ), 64 | iconTheme: IconThemeData( 65 | color: AppColors.iconColor, 66 | size: 25.0, 67 | ), 68 | ), 69 | routes: Routes.routes, 70 | home: Scaffold( 71 | appBar: AppBar( 72 | title: Text( 73 | appBarTitles[currentPage], 74 | style: TextStyle(color: Colors.white), 75 | ), 76 | actions: [ 77 | IconButton( 78 | icon: Icon( 79 | Icons.search, 80 | color: Colors.white, 81 | ), 82 | onPressed: _gotoSearchPage, 83 | ), 84 | ], 85 | ), 86 | body: getBody(), 87 | ), 88 | ); 89 | } 90 | 91 | getBody() { 92 | return Column( 93 | children: [ 94 | //上面把空间占满 95 | Expanded( 96 | //PageView类似于Android中的ViewPager 97 | child: PageView( 98 | children: [ 99 | HomeListPage(), 100 | KnowledgeSystemPage(), 101 | MyInfoPage(), 102 | ], 103 | //设置controller,可以控制PageView的当前页 104 | controller: _pageController, 105 | //滑动的那种控制, 106 | // BouncingScrollPhysics是用来适用于 107 | // 允许滚动偏移超出内容范围,然后将内容反弹到那些范围边缘的环境的滚动物理。iOS上经常有这种效果 108 | physics: BouncingScrollPhysics(), 109 | onPageChanged: (page) { 110 | setState(() { 111 | currentPage = page; 112 | }); 113 | }, 114 | ), 115 | ), 116 | //下面的bottomNavigationBar 117 | BottomNavigationBar( 118 | items: [ 119 | BottomNavigationBarItem( 120 | icon: Icon(Icons.home), 121 | title: Text(appBarTitles[0]), 122 | ), 123 | BottomNavigationBarItem( 124 | icon: Icon(Icons.widgets), 125 | title: Text(appBarTitles[1]), 126 | ), 127 | BottomNavigationBarItem( 128 | icon: Icon(Icons.person), 129 | title: Text(appBarTitles[2]), 130 | ), 131 | ], 132 | onTap: (page) { 133 | //滑动到相应页面 curve是动画效果 134 | /*_pageController.animateToPage(page, 135 | duration: Duration(milliseconds: 300), curve: Curves.easeIn);*/ 136 | //直接转到相应页面,没得啥动画效果 并且不会经过中间的页面,上面的那个会经过中间页 137 | _pageController.jumpToPage(page); 138 | }, 139 | currentIndex: currentPage, 140 | ), 141 | ], 142 | ); 143 | } 144 | 145 | void _gotoSearchPage() { 146 | Navigator.pushNamed(context, Routes.searchPage); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /lib/page/webview/route_web_page_data.dart: -------------------------------------------------------------------------------- 1 | ///2020年03月22日12:55:59 2 | ///传递给WebViewPage的数据 3 | ///xfhy 4 | 5 | class RouteWebPageData { 6 | //页面id 7 | int id; 8 | 9 | //页面标题 10 | String title; 11 | 12 | //url 13 | String url; 14 | 15 | //是否收藏 16 | bool collect; 17 | 18 | RouteWebPageData({this.id = -1, this.title, this.url, this.collect = false}); 19 | } 20 | -------------------------------------------------------------------------------- /lib/page/webview/web_view_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; 3 | import 'package:url_launcher/url_launcher.dart'; 4 | import 'package:wanandroidflutter/constant/api.dart'; 5 | import 'package:wanandroidflutter/constant/routes.dart'; 6 | import 'package:wanandroidflutter/data/data_utils.dart'; 7 | import 'package:wanandroidflutter/page/webview/route_web_page_data.dart'; 8 | import 'package:wanandroidflutter/util/log_util.dart'; 9 | import 'package:wanandroidflutter/util/tool_utils.dart'; 10 | 11 | ///网页详情 12 | ///2020年03月22日08:15:28 13 | ///xfhy 14 | 15 | class WebViewPage extends StatefulWidget { 16 | @override 17 | State createState() { 18 | return _WebViewPageState(); 19 | } 20 | } 21 | 22 | class _WebViewPageState extends State { 23 | RouteWebPageData pageData; 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | initData(); 28 | 29 | LogUtil.d("访问url : ${pageData.url}"); 30 | 31 | WebviewScaffold webviewScaffold = WebviewScaffold( 32 | url: pageData.url, 33 | appBar: getAppBar(), 34 | withZoom: true, 35 | withLocalStorage: true, 36 | hidden: true, 37 | ); 38 | return webviewScaffold; 39 | } 40 | 41 | void initData() { 42 | pageData = ModalRoute.of(context).settings.arguments as RouteWebPageData; 43 | 44 | //如果数据为空 则直接打开首页 45 | if (pageData == null) { 46 | pageData = RouteWebPageData(); 47 | } 48 | if (pageData.url == null || pageData.url == "") { 49 | pageData.title = "首页"; 50 | pageData.url = Api.BASE_URL; 51 | } 52 | } 53 | 54 | AppBar getAppBar() { 55 | //WebviewScaffold不能和PopupMenuButton一起使用,下面是原生WebView,appbar是Flutter View.会导致在appBar展示PopupMenuButton有问题,只能展示部分 56 | AppBar appBar = ToolUtils.getCommonAppBar(context, ToolUtils.signToStr(pageData.title), fontSize: 14.0, actions: [ 57 | IconButton( 58 | icon: Icon( 59 | ToolUtils.getNotNullBool(pageData.collect) ? Icons.favorite : Icons.favorite_border, 60 | color: ToolUtils.getNotNullBool(pageData.collect) ? Colors.red : Colors.white, 61 | ), 62 | onPressed: _collectArticle, 63 | ), 64 | IconButton( 65 | icon: Icon( 66 | Icons.link, 67 | color: Colors.white, 68 | ), 69 | onPressed: _launchURL, 70 | ), 71 | //WebPageMenu(pageData), 72 | ]); 73 | return appBar; 74 | } 75 | 76 | ///用系统浏览器打开 77 | _launchURL() async { 78 | String url = pageData.url; 79 | if (await canLaunch(url)) { 80 | await launch(url); 81 | } else { 82 | ToolUtils.showToast(msg: "打开浏览器失败"); 83 | } 84 | } 85 | 86 | ///收藏文章 87 | void _collectArticle() async { 88 | bool isLogin = await dataUtils.isLogin(); 89 | if (!isLogin) { 90 | //未登录,跳转到登录界面 91 | Navigator.pushNamed(context, Routes.loginPage); 92 | return; 93 | } 94 | 95 | //已登录 96 | 97 | //之前已收藏 那么就是取消收藏 98 | if (ToolUtils.getNotNullBool(pageData.collect)) { 99 | await dataUtils.cancelCollectArticle(pageData.id); 100 | setState(() { 101 | pageData.collect = false; 102 | }); 103 | ToolUtils.showToast(msg: "取消收藏成功"); 104 | } else { 105 | //收藏 106 | await dataUtils.collectArticle(pageData.id); 107 | setState(() { 108 | pageData.collect = true; 109 | }); 110 | ToolUtils.showToast(msg: "收藏成功"); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /lib/util/log_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:wanandroidflutter/common/application.dart'; 3 | 4 | ///日志打印 5 | ///可以动态生产环境和开发环境,且长度不限 6 | class LogUtil { 7 | static var _separator = "-"; 8 | static var _split = 9 | "$_separator$_separator$_separator$_separator$_separator$_separator$_separator$_separator$_separator"; 10 | static var _title = "xfhy-Log"; 11 | static var _isDebug = Application.isDebug; 12 | 13 | ///print默认是有长度限制的 14 | static int _limitLength = 800; 15 | 16 | ///开始行 =========xfhy======== 17 | static String _startLine = "$_split$_title$_split"; 18 | 19 | //末行 就是很多等号 20 | static String _endLine = "$_split$_separator$_separator$_separator$_split"; 21 | 22 | static void init({String title, @required bool isDebug, int limitLength}) { 23 | _title = title; 24 | _isDebug = isDebug; 25 | _limitLength = limitLength ??= _limitLength; 26 | _startLine = "$_split$_title$_split"; 27 | var endLineStr = StringBuffer(); 28 | var cnCharReg = RegExp("[\u4e00-\u9fa5]"); 29 | for (int i = 0; i < _startLine.length; i++) { 30 | if (cnCharReg.stringMatch(_startLine[i]) != null) { 31 | endLineStr.write(_separator); 32 | } 33 | endLineStr.write(_separator); 34 | } 35 | _endLine = endLineStr.toString(); 36 | } 37 | 38 | //仅Debug模式可见 39 | static void d(dynamic obj) { 40 | if (_isDebug) { 41 | _log(obj.toString()); 42 | } 43 | } 44 | 45 | static void v(dynamic obj) { 46 | _log(obj.toString()); 47 | } 48 | 49 | static void _log(String msg) { 50 | print("$_startLine"); 51 | //_logEmptyLine(); 52 | if (msg.length < _limitLength) { 53 | print(msg); 54 | } else { 55 | segmentationLog(msg); 56 | } 57 | //_logEmptyLine(); 58 | print("$_endLine"); 59 | } 60 | 61 | static void segmentationLog(String msg) { 62 | var outStr = StringBuffer(); 63 | for (var index = 0; index < msg.length; index++) { 64 | outStr.write(msg[index]); 65 | if (index % _limitLength == 0 && index != 0) { 66 | print(outStr); 67 | outStr.clear(); 68 | var lastIndex = index + 1; 69 | if (msg.length - lastIndex < _limitLength) { 70 | var remainderStr = msg.substring(lastIndex, msg.length); 71 | print(remainderStr); 72 | break; 73 | } 74 | } 75 | } 76 | } 77 | 78 | static void _logEmptyLine() { 79 | print(""); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/util/shared_preferences.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:shared_preferences/shared_preferences.dart'; 4 | 5 | ///sp工具类 6 | ///2020年03月28日15:28:30 7 | ///xfhy 8 | 9 | SpUtil spUtil = SpUtil(); 10 | 11 | class SpUtil { 12 | SpUtil._internal(); 13 | 14 | static SpUtil _singleton = new SpUtil._internal(); 15 | 16 | factory SpUtil() => _singleton; 17 | 18 | // 判断是否存在数据 19 | Future hasKey(String key) async { 20 | Set keys = await getKeys(); 21 | return keys.contains(key); 22 | } 23 | 24 | ///可能为空 25 | get(String key) async { 26 | SharedPreferences prefs = await SharedPreferences.getInstance(); 27 | return prefs.get(key); 28 | } 29 | 30 | Future getString(String key) async { 31 | SharedPreferences prefs = await SharedPreferences.getInstance(); 32 | var strData = prefs.getString(key); 33 | return strData == null ? "" : strData; 34 | } 35 | 36 | Future putString(String key, String value) async { 37 | SharedPreferences prefs = await SharedPreferences.getInstance(); 38 | return await prefs.setString(key, value); 39 | } 40 | 41 | Future getBool(String key) async { 42 | SharedPreferences prefs = await SharedPreferences.getInstance(); 43 | var boolData = prefs.getBool(key); 44 | return boolData == null ? false : boolData; 45 | } 46 | 47 | Future putBool(String key, bool value) async { 48 | SharedPreferences prefs = await SharedPreferences.getInstance(); 49 | return await prefs.setBool(key, value); 50 | } 51 | 52 | Future getInt(String key) async { 53 | SharedPreferences prefs = await SharedPreferences.getInstance(); 54 | var intData = prefs.getInt(key); 55 | return intData == null ? -1 : intData; 56 | } 57 | 58 | Future putInt(String key, int value) async { 59 | SharedPreferences prefs = await SharedPreferences.getInstance(); 60 | return await prefs.setInt(key, value); 61 | } 62 | 63 | Future getDouble(String key) async { 64 | SharedPreferences prefs = await SharedPreferences.getInstance(); 65 | var doubleData = prefs.getDouble(key); 66 | return doubleData == null ? 0.0 : doubleData; 67 | } 68 | 69 | Future putDouble(String key, double value) async { 70 | SharedPreferences prefs = await SharedPreferences.getInstance(); 71 | return await prefs.setDouble(key, value); 72 | } 73 | 74 | Future> getStringList(String key) async { 75 | SharedPreferences prefs = await SharedPreferences.getInstance(); 76 | var list = prefs.getStringList(key); 77 | return list == null ? [] : list; 78 | } 79 | 80 | Future putStringList(String key, List value) async { 81 | SharedPreferences prefs = await SharedPreferences.getInstance(); 82 | return await prefs.setStringList(key, value); 83 | } 84 | 85 | dynamic getDynamic(String key) async { 86 | SharedPreferences prefs = await SharedPreferences.getInstance(); 87 | return prefs.get(key); 88 | } 89 | 90 | Future remove(String key) async { 91 | SharedPreferences prefs = await SharedPreferences.getInstance(); 92 | return prefs.remove(key); 93 | } 94 | 95 | Future clear() async { 96 | SharedPreferences prefs = await SharedPreferences.getInstance(); 97 | return prefs.clear(); 98 | } 99 | 100 | Future getKeys() async { 101 | SharedPreferences prefs = await SharedPreferences.getInstance(); 102 | var data = prefs.getKeys(); 103 | return data == null ? [] : data; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /lib/util/tool_utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_spinkit/flutter_spinkit.dart'; 5 | import 'package:fluttertoast/fluttertoast.dart'; 6 | import 'package:path_provider/path_provider.dart'; 7 | import 'package:wanandroidflutter/constant/app_colors.dart'; 8 | import 'package:wanandroidflutter/widget/stroke_widget.dart'; 9 | import 'package:cookie_jar/cookie_jar.dart'; 10 | 11 | /// 一些常用的工具类方法 12 | class ToolUtils { 13 | ///判断 字符串 是否为空 14 | static bool isNullOrEmpty(String str) { 15 | return str == null || str.length <= 0; 16 | } 17 | 18 | ///标题符号转换 19 | static String signToStr(String str) { 20 | if (null == str || "" == str) { 21 | return ""; 22 | } 23 | return str 24 | .replaceAll(RegExp("(]*>)|()"), "") 25 | .replaceAll(RegExp("\n{2,}"), "\n") 26 | .replaceAll(RegExp("\s{2,}"), " ") 27 | .replaceAll("–", "–") 28 | .replaceAll("—", "—") 29 | .replaceAll("‘", "‘") 30 | .replaceAll("’", "’") 31 | .replaceAll("‚", "‚") 32 | .replaceAll("“", "“") 33 | .replaceAll("”", "”") 34 | .replaceAll("„", "„") 35 | .replaceAll("&", "&") 36 | .replaceAll("‰", "‰") 37 | .replaceAll("‹", "‹") 38 | .replaceAll("›", "›") 39 | .replaceAll("€", "€") 40 | .replaceAll(""", "'") 41 | .replaceAll("

", "") 42 | .replaceAll("·", "·") 43 | .replaceAll("…", "...") 44 | .replaceAll("

", "") 45 | .replaceAll("
", "\n") 46 | .replaceAll("
", "\n"); 47 | } 48 | 49 | ///创建 tag widget 抽取的方法 50 | static Widget buildStrokeTagWidget(String text, Color color) { 51 | return Padding( 52 | padding: EdgeInsets.only(right: 5.0), 53 | child: StrokeWidget( 54 | color: color, 55 | strokeWidth: 0.5, 56 | childWidget: Text( 57 | text, 58 | style: TextStyle( 59 | fontSize: 11.0, 60 | color: color, 61 | fontWeight: FontWeight.w100, 62 | ), 63 | ), 64 | ), 65 | ); 66 | } 67 | 68 | ///将传入的时间根据空格切割 只要前半部分 69 | static String getFirstDate(String date) { 70 | if (date == null) { 71 | return ""; 72 | } 73 | if (date.contains(" ")) { 74 | return date.split(" ")[0]; 75 | } else { 76 | return date; 77 | } 78 | } 79 | 80 | ///展示toast 81 | static void showToast({String msg}) { 82 | Fluttertoast.showToast( 83 | msg: msg, 84 | toastLength: Toast.LENGTH_SHORT, 85 | gravity: ToastGravity.BOTTOM, 86 | timeInSecForIosWeb: 1, 87 | fontSize: 16.0, 88 | backgroundColor: Colors.red, 89 | textColor: Colors.white, 90 | ); 91 | } 92 | 93 | ///get通用状态栏 94 | static AppBar getCommonAppBar(BuildContext context, String title, {double fontSize, List actions}) { 95 | if (title == null) { 96 | title = ""; 97 | } 98 | return AppBar( 99 | backgroundColor: AppColors.colorPrimary, 100 | leading: IconButton( 101 | icon: Icon( 102 | Icons.arrow_back, 103 | color: Colors.white, 104 | ), 105 | //点击返回 106 | onPressed: () { 107 | if (context != null) { 108 | Navigator.pop(context); 109 | } 110 | }, 111 | ), 112 | title: Text( 113 | title, 114 | style: TextStyle( 115 | color: Colors.white, 116 | fontSize: fontSize == null ? 18.0 : fontSize, 117 | ), 118 | ), 119 | //标题栏居中 120 | centerTitle: true, 121 | //右边的action 按钮 122 | actions: actions == null ? [] : actions, 123 | //action 颜色 124 | //actionsIconTheme: IconThemeData(color: Colors.white), 125 | ); 126 | } 127 | 128 | ///展示loading dialog 129 | static void showLoading(BuildContext context, String loadingText) { 130 | //展示一个loading dialog 131 | showDialog( 132 | context: context, 133 | barrierDismissible: false, 134 | builder: (_) { 135 | return SpinKitFadingCircle( 136 | color: AppColors.colorPrimary, 137 | ); 138 | }); 139 | } 140 | 141 | ///隐藏loading dialog 142 | static void disMissLoadingDialog(bool isAddLoading, BuildContext context) { 143 | if (isAddLoading) { 144 | Navigator.of(context, rootNavigator: true).pop(); 145 | } 146 | } 147 | 148 | //获取本地资源图片 149 | static String getImage(String imageName, {String format: 'png'}) { 150 | return "images/$imageName.$format"; 151 | } 152 | 153 | ///获取非空字符串 如果是null,则返回"" 154 | static String getNotEmptyStr(String text) { 155 | return text == null ? "" : text; 156 | } 157 | 158 | ///获取非空bool 如果是null,则返回false 159 | static bool getNotNullBool(bool value) { 160 | return value == null ? false : value; 161 | } 162 | 163 | ///清除 cookie 缓存 164 | static void clearCookie() async { 165 | Directory documentsDir = await getApplicationDocumentsDirectory(); 166 | String documentsPath = documentsDir.path; 167 | var dir = Directory("$documentsPath/coolies"); 168 | await dir.create(); 169 | PersistCookieJar(dir: dir.path).deleteAll(); 170 | } 171 | 172 | //Dialog 封装 173 | static void showAlertDialog(BuildContext context, String contentText, 174 | {Function confirmCallback, Function dismissCallback, String confirmText = ""}) async { 175 | return showDialog( 176 | context: context, 177 | barrierDismissible: true, // user must tap button! 178 | builder: (BuildContext context) { 179 | return AlertDialog( 180 | content: Text(contentText), 181 | actions: [ 182 | FlatButton( 183 | child: Text('手滑了'), 184 | onPressed: () { 185 | if (dismissCallback != null) { 186 | dismissCallback(); 187 | } 188 | Navigator.of(context).pop(); 189 | }, 190 | ), 191 | FlatButton( 192 | child: Text(confirmText == "" ? '注销' : confirmText), 193 | onPressed: () { 194 | if (confirmCallback != null) { 195 | confirmCallback(); 196 | } 197 | Navigator.of(context).pop(); 198 | }, 199 | ) 200 | ], 201 | elevation: 20, //阴影 202 | ); 203 | }, 204 | ); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /lib/widget/home_banner.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:wanandroidflutter/common/application.dart'; 5 | import 'package:wanandroidflutter/constant/routes.dart'; 6 | import 'package:wanandroidflutter/data/model/banner_data.dart'; 7 | import 'package:wanandroidflutter/page/webview/route_web_page_data.dart'; 8 | import 'package:wanandroidflutter/util/tool_utils.dart'; 9 | 10 | ///首页的banner 11 | ///2020年03月21日21:08:20 12 | ///xfhy 13 | 14 | class HomeBanner extends StatefulWidget { 15 | List bannerList; 16 | 17 | HomeBanner({this.bannerList, Key key}) : super(key: key) { 18 | if (bannerList == null) { 19 | bannerList = []; 20 | } 21 | } 22 | 23 | @override 24 | State createState() { 25 | return _HomeBannerState(); 26 | } 27 | 28 | void setBannerData(List bannerList) { 29 | this.bannerList = bannerList; 30 | if (this.bannerList == null) { 31 | this.bannerList = null; 32 | } 33 | } 34 | } 35 | 36 | ///数据 事件 37 | class BannerDataEvent { 38 | List bannerList; 39 | 40 | BannerDataEvent(this.bannerList); 41 | } 42 | 43 | class _HomeBannerState extends State { 44 | int _realIndex = 1; 45 | int virtualIndex = 0; 46 | PageController _pageController; 47 | Timer _timer; 48 | 49 | @override 50 | void initState() { 51 | super.initState(); 52 | // LogUtil.d('initState'); 53 | _pageController = PageController(initialPage: _realIndex); 54 | //周期性的计时 55 | _timer = createAndStartTimer(); 56 | 57 | //监听事件总线上数据变化 58 | Application.eventBus.on().listen((event) { 59 | if (mounted) { 60 | setState(() { 61 | widget.bannerList = event.bannerList; 62 | if (widget.bannerList == null) { 63 | widget.bannerList = []; 64 | } 65 | }); 66 | } 67 | }); 68 | } 69 | 70 | @override 71 | void dispose() { 72 | super.dispose(); 73 | _pageController.dispose(); 74 | stopTimer(); 75 | } 76 | 77 | @override 78 | Widget build(BuildContext context) { 79 | if (widget.bannerList.length == 0) { 80 | stopTimer(); 81 | return Container(); 82 | } else { 83 | if (_timer == null) { 84 | _timer = createAndStartTimer(); 85 | } 86 | return Container( 87 | height: 226.0, 88 | child: Stack( 89 | alignment: Alignment.bottomCenter, 90 | children: [ 91 | //一个类似ViewPager的PageView+小圆点+右上角索引 92 | //1. PageView 93 | PageView( 94 | controller: _pageController, 95 | //当从一个page滑动到另一个page的时候会回调 96 | onPageChanged: _onPageChanged, 97 | children: _buildItems(), 98 | ), 99 | 100 | //2. 小圆点 101 | _buildIndicator(), 102 | 103 | //3. 定位 104 | Positioned( 105 | top: 0.0, 106 | right: 0.0, 107 | child: _numberIndicator(context, virtualIndex, widget.bannerList.length), 108 | ), 109 | ], 110 | ), 111 | ); 112 | } 113 | } 114 | 115 | ///当从一个page滑动到另一个page的时候会回调 116 | ///模拟无限滚动 d abcd a 117 | void _onPageChanged(int index) { 118 | _realIndex = index; 119 | int count = widget.bannerList.length; 120 | if (index == 0) { 121 | virtualIndex = count - 1; 122 | _pageController.jumpToPage(count); 123 | } else if (index == count + 1) { 124 | virtualIndex = 0; 125 | _pageController.jumpToPage(1); 126 | } else { 127 | virtualIndex = index - 1; 128 | } 129 | setState(() {}); 130 | } 131 | 132 | ///构建PageView的childs 133 | _buildItems() { 134 | if (widget.bannerList.length == 0) { 135 | return [Container()]; 136 | } 137 | List childWidget = []; 138 | //头部添加一个尾部Item,模拟循环 139 | childWidget.add(_buildBannerItem(widget.bannerList[widget.bannerList.length - 1])); 140 | for (var bannerData in widget.bannerList) { 141 | childWidget.add(_buildBannerItem(bannerData)); 142 | } 143 | // 尾部 添加上第一个 144 | childWidget.add(_buildBannerItem(widget.bannerList[0])); 145 | return childWidget; 146 | } 147 | 148 | ///构建某一个item 149 | Widget _buildBannerItem(BannerData bannerData) { 150 | //用GestureDetector包装一下,才能设置点击事件 151 | return GestureDetector( 152 | onTap: () { 153 | _onClickBanner(bannerData); 154 | }, 155 | child: Stack( 156 | //未定位widget占满Stack整个空间 157 | fit: StackFit.expand, 158 | children: [ 159 | //banner图片 160 | Image.network( 161 | bannerData.imagePath, 162 | fit: BoxFit.cover, 163 | ), 164 | _buildItemTitle(bannerData.title), 165 | ], 166 | ), 167 | ); 168 | } 169 | 170 | ///构建banner中的标题和背景 171 | _buildItemTitle(String title) { 172 | return Container( 173 | //整个banner的渐变色 174 | decoration: BoxDecoration( 175 | gradient: LinearGradient( 176 | begin: Alignment.bottomCenter, 177 | end: const Alignment(0.0, -0.8), 178 | colors: [const Color(0xa0000000), Colors.transparent], 179 | )), 180 | //标题对齐方式 181 | alignment: Alignment.bottomCenter, 182 | child: Container( 183 | //底部标题的margin 184 | margin: EdgeInsets.symmetric(vertical: 22.0, horizontal: 16.0), 185 | //标题 186 | child: Text( 187 | ToolUtils.signToStr(title), 188 | style: TextStyle( 189 | color: Colors.white, 190 | fontSize: 18.0, 191 | ), 192 | ), 193 | ), 194 | ); 195 | } 196 | 197 | ///banner底部小圆点 198 | _buildIndicator() { 199 | List indicators = []; 200 | 201 | for (int i = 0; i < widget.bannerList.length; i++) { 202 | indicators.add(Container( 203 | width: 6.0, 204 | height: 6.0, 205 | margin: EdgeInsets.symmetric(horizontal: 2.5, vertical: 10.0), 206 | //修饰 207 | decoration: BoxDecoration( 208 | //shape 209 | shape: BoxShape.circle, 210 | color: i == virtualIndex ? Colors.white : Colors.grey), 211 | )); 212 | } 213 | //小圆点 放成一行,放中间 214 | return Row( 215 | //主轴 216 | mainAxisAlignment: MainAxisAlignment.center, 217 | children: indicators, 218 | ); 219 | } 220 | 221 | //构建右上角的角标 222 | _numberIndicator(BuildContext context, int index, int itemCount) { 223 | //展示: 1/4 这种效果 224 | return Container( 225 | //一般用于背景的效果 226 | decoration: BoxDecoration( 227 | color: Colors.black45, 228 | borderRadius: BorderRadius.circular(20.0), 229 | ), 230 | margin: EdgeInsets.only(top: 10.0, right: 5.0), 231 | padding: EdgeInsets.symmetric(horizontal: 6.0, vertical: 2.0), 232 | child: Text( 233 | "${++index}/${itemCount}", 234 | style: TextStyle( 235 | color: Colors.white70, 236 | fontSize: 12.0, 237 | fontWeight: FontWeight.bold, 238 | ), 239 | ), 240 | ); 241 | } 242 | 243 | ///banner item 点击事件 244 | void _onClickBanner(BannerData bannerData) { 245 | RouteWebPageData pageData = 246 | new RouteWebPageData(id: bannerData.id, title: bannerData.title, url: bannerData.url); 247 | Navigator.pushNamed(context, Routes.webViewPage, arguments: pageData); 248 | } 249 | 250 | void stopTimer() { 251 | if (_timer != null && _timer.isActive) { 252 | _timer.cancel(); 253 | _timer = null; 254 | } 255 | } 256 | 257 | Timer createAndStartTimer() { 258 | return Timer.periodic(Duration(seconds: 5), (timer) { 259 | //计时然后滑动 260 | _pageController.animateToPage(_realIndex + 1, 261 | //线性的动画 262 | duration: Duration(milliseconds: 300), 263 | curve: Curves.linear); 264 | }); 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /lib/widget/stroke_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | ///圆角框 widget 4 | ///2020年03月23日07:17:33 5 | ///xfhy 6 | 7 | class StrokeWidget extends StatelessWidget { 8 | final Color color; 9 | final Widget childWidget; 10 | final EdgeInsets edgeInsets; 11 | final double strokeWidth; 12 | final double strokeRadius; 13 | 14 | StrokeWidget( 15 | {this.color = Colors.black, 16 | @required this.childWidget, 17 | this.edgeInsets = 18 | const EdgeInsets.symmetric(horizontal: 2.0, vertical: 0.0), 19 | this.strokeWidth = 1.0, 20 | this.strokeRadius = 5.0, 21 | Key key}) 22 | : super(key: key); 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return Container( 27 | padding: edgeInsets, 28 | //修饰 背景,边框啥的 29 | decoration: BoxDecoration( 30 | border: Border.all(color: color, width: strokeWidth), 31 | borderRadius: BorderRadius.circular(strokeRadius), 32 | ), 33 | child: childWidget, 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /pic/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/pic/image1.png -------------------------------------------------------------------------------- /pic/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/pic/image2.png -------------------------------------------------------------------------------- /pic/image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/pic/image3.png -------------------------------------------------------------------------------- /pic/image4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/pic/image4.png -------------------------------------------------------------------------------- /pic/image5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/pic/image5.png -------------------------------------------------------------------------------- /pic/image6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xfhy/WanAndroid-Flutter/b119e15cd5342a45dc1e2e246856e655ee757ea1/pic/image6.png -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | archive: 5 | dependency: transitive 6 | description: 7 | name: archive 8 | url: "https://pub.flutter-io.cn" 9 | source: hosted 10 | version: "2.0.11" 11 | args: 12 | dependency: transitive 13 | description: 14 | name: args 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "1.5.2" 18 | async: 19 | dependency: transitive 20 | description: 21 | name: async 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "2.4.0" 25 | boolean_selector: 26 | dependency: transitive 27 | description: 28 | name: boolean_selector 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "1.0.5" 32 | charcode: 33 | dependency: transitive 34 | description: 35 | name: charcode 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "1.1.2" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.flutter-io.cn" 44 | source: hosted 45 | version: "1.14.11" 46 | convert: 47 | dependency: transitive 48 | description: 49 | name: convert 50 | url: "https://pub.flutter-io.cn" 51 | source: hosted 52 | version: "2.1.1" 53 | cookie_jar: 54 | dependency: transitive 55 | description: 56 | name: cookie_jar 57 | url: "https://pub.flutter-io.cn" 58 | source: hosted 59 | version: "1.0.1" 60 | crypto: 61 | dependency: transitive 62 | description: 63 | name: crypto 64 | url: "https://pub.flutter-io.cn" 65 | source: hosted 66 | version: "2.1.3" 67 | cupertino_icons: 68 | dependency: "direct main" 69 | description: 70 | name: cupertino_icons 71 | url: "https://pub.flutter-io.cn" 72 | source: hosted 73 | version: "0.1.3" 74 | dio: 75 | dependency: "direct main" 76 | description: 77 | name: dio 78 | url: "https://pub.flutter-io.cn" 79 | source: hosted 80 | version: "3.0.9" 81 | dio_cookie_manager: 82 | dependency: "direct main" 83 | description: 84 | name: dio_cookie_manager 85 | url: "https://pub.flutter-io.cn" 86 | source: hosted 87 | version: "1.0.0" 88 | event_bus: 89 | dependency: "direct main" 90 | description: 91 | name: event_bus 92 | url: "https://pub.flutter-io.cn" 93 | source: hosted 94 | version: "1.1.1" 95 | flutter: 96 | dependency: "direct main" 97 | description: flutter 98 | source: sdk 99 | version: "0.0.0" 100 | flutter_spinkit: 101 | dependency: "direct main" 102 | description: 103 | name: flutter_spinkit 104 | url: "https://pub.flutter-io.cn" 105 | source: hosted 106 | version: "4.1.2" 107 | flutter_test: 108 | dependency: "direct dev" 109 | description: flutter 110 | source: sdk 111 | version: "0.0.0" 112 | flutter_web_plugins: 113 | dependency: transitive 114 | description: flutter 115 | source: sdk 116 | version: "0.0.0" 117 | flutter_webview_plugin: 118 | dependency: "direct main" 119 | description: 120 | name: flutter_webview_plugin 121 | url: "https://pub.flutter-io.cn" 122 | source: hosted 123 | version: "0.3.10+1" 124 | fluttertoast: 125 | dependency: "direct main" 126 | description: 127 | name: fluttertoast 128 | url: "https://pub.flutter-io.cn" 129 | source: hosted 130 | version: "4.0.1" 131 | http_parser: 132 | dependency: transitive 133 | description: 134 | name: http_parser 135 | url: "https://pub.flutter-io.cn" 136 | source: hosted 137 | version: "3.1.3" 138 | image: 139 | dependency: transitive 140 | description: 141 | name: image 142 | url: "https://pub.flutter-io.cn" 143 | source: hosted 144 | version: "2.1.4" 145 | matcher: 146 | dependency: transitive 147 | description: 148 | name: matcher 149 | url: "https://pub.flutter-io.cn" 150 | source: hosted 151 | version: "0.12.6" 152 | meta: 153 | dependency: transitive 154 | description: 155 | name: meta 156 | url: "https://pub.flutter-io.cn" 157 | source: hosted 158 | version: "1.1.8" 159 | path: 160 | dependency: transitive 161 | description: 162 | name: path 163 | url: "https://pub.flutter-io.cn" 164 | source: hosted 165 | version: "1.6.4" 166 | path_provider: 167 | dependency: "direct main" 168 | description: 169 | name: path_provider 170 | url: "https://pub.flutter-io.cn" 171 | source: hosted 172 | version: "1.6.5" 173 | path_provider_macos: 174 | dependency: transitive 175 | description: 176 | name: path_provider_macos 177 | url: "https://pub.flutter-io.cn" 178 | source: hosted 179 | version: "0.0.4" 180 | path_provider_platform_interface: 181 | dependency: transitive 182 | description: 183 | name: path_provider_platform_interface 184 | url: "https://pub.flutter-io.cn" 185 | source: hosted 186 | version: "1.0.1" 187 | pedantic: 188 | dependency: transitive 189 | description: 190 | name: pedantic 191 | url: "https://pub.flutter-io.cn" 192 | source: hosted 193 | version: "1.8.0+1" 194 | petitparser: 195 | dependency: transitive 196 | description: 197 | name: petitparser 198 | url: "https://pub.flutter-io.cn" 199 | source: hosted 200 | version: "2.4.0" 201 | platform: 202 | dependency: transitive 203 | description: 204 | name: platform 205 | url: "https://pub.flutter-io.cn" 206 | source: hosted 207 | version: "2.2.1" 208 | plugin_platform_interface: 209 | dependency: transitive 210 | description: 211 | name: plugin_platform_interface 212 | url: "https://pub.flutter-io.cn" 213 | source: hosted 214 | version: "1.0.2" 215 | quiver: 216 | dependency: transitive 217 | description: 218 | name: quiver 219 | url: "https://pub.flutter-io.cn" 220 | source: hosted 221 | version: "2.0.5" 222 | shared_preferences: 223 | dependency: "direct main" 224 | description: 225 | name: shared_preferences 226 | url: "https://pub.flutter-io.cn" 227 | source: hosted 228 | version: "0.5.6+3" 229 | shared_preferences_macos: 230 | dependency: transitive 231 | description: 232 | name: shared_preferences_macos 233 | url: "https://pub.flutter-io.cn" 234 | source: hosted 235 | version: "0.0.1+6" 236 | shared_preferences_platform_interface: 237 | dependency: transitive 238 | description: 239 | name: shared_preferences_platform_interface 240 | url: "https://pub.flutter-io.cn" 241 | source: hosted 242 | version: "1.0.3" 243 | shared_preferences_web: 244 | dependency: transitive 245 | description: 246 | name: shared_preferences_web 247 | url: "https://pub.flutter-io.cn" 248 | source: hosted 249 | version: "0.1.2+4" 250 | sky_engine: 251 | dependency: transitive 252 | description: flutter 253 | source: sdk 254 | version: "0.0.99" 255 | source_span: 256 | dependency: transitive 257 | description: 258 | name: source_span 259 | url: "https://pub.flutter-io.cn" 260 | source: hosted 261 | version: "1.5.5" 262 | sprintf: 263 | dependency: "direct main" 264 | description: 265 | name: sprintf 266 | url: "https://pub.flutter-io.cn" 267 | source: hosted 268 | version: "4.0.2" 269 | stack_trace: 270 | dependency: transitive 271 | description: 272 | name: stack_trace 273 | url: "https://pub.flutter-io.cn" 274 | source: hosted 275 | version: "1.9.3" 276 | stream_channel: 277 | dependency: transitive 278 | description: 279 | name: stream_channel 280 | url: "https://pub.flutter-io.cn" 281 | source: hosted 282 | version: "2.0.0" 283 | string_scanner: 284 | dependency: transitive 285 | description: 286 | name: string_scanner 287 | url: "https://pub.flutter-io.cn" 288 | source: hosted 289 | version: "1.0.5" 290 | term_glyph: 291 | dependency: transitive 292 | description: 293 | name: term_glyph 294 | url: "https://pub.flutter-io.cn" 295 | source: hosted 296 | version: "1.1.0" 297 | test_api: 298 | dependency: transitive 299 | description: 300 | name: test_api 301 | url: "https://pub.flutter-io.cn" 302 | source: hosted 303 | version: "0.2.11" 304 | typed_data: 305 | dependency: transitive 306 | description: 307 | name: typed_data 308 | url: "https://pub.flutter-io.cn" 309 | source: hosted 310 | version: "1.1.6" 311 | url_launcher: 312 | dependency: "direct main" 313 | description: 314 | name: url_launcher 315 | url: "https://pub.flutter-io.cn" 316 | source: hosted 317 | version: "5.4.2" 318 | url_launcher_macos: 319 | dependency: transitive 320 | description: 321 | name: url_launcher_macos 322 | url: "https://pub.flutter-io.cn" 323 | source: hosted 324 | version: "0.0.1+4" 325 | url_launcher_platform_interface: 326 | dependency: transitive 327 | description: 328 | name: url_launcher_platform_interface 329 | url: "https://pub.flutter-io.cn" 330 | source: hosted 331 | version: "1.0.6" 332 | url_launcher_web: 333 | dependency: transitive 334 | description: 335 | name: url_launcher_web 336 | url: "https://pub.flutter-io.cn" 337 | source: hosted 338 | version: "0.1.1+1" 339 | vector_math: 340 | dependency: transitive 341 | description: 342 | name: vector_math 343 | url: "https://pub.flutter-io.cn" 344 | source: hosted 345 | version: "2.0.8" 346 | xml: 347 | dependency: transitive 348 | description: 349 | name: xml 350 | url: "https://pub.flutter-io.cn" 351 | source: hosted 352 | version: "3.5.0" 353 | sdks: 354 | dart: ">=2.5.0 <3.0.0" 355 | flutter: ">=1.12.13+hotfix.4 <2.0.0" 356 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: wanandroidflutter 2 | description: A new Flutter application. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # In Android, build-name is used as versionName while build-number used as versionCode. 10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 12 | # Read more about iOS versioning at 13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 14 | version: 1.0.0+1 15 | 16 | environment: 17 | sdk: ">=2.1.0 <3.0.0" 18 | 19 | dependencies: 20 | # 网络请求 21 | dio: ^3.0.9 22 | # cookie管理 23 | dio_cookie_manager: 1.0.0 24 | # path_provider负责插桩iOS/Android的目录文件,IO模块负责对文件进行读写 25 | path_provider: ^1.6.5 26 | # 包装了与原生WebView通信的WebView 27 | flutter_webview_plugin: ^0.3.10+1 28 | # loading 各种菊花 29 | flutter_spinkit: ^4.1.2 30 | # 事件总线 31 | event_bus: ^1.1.1 32 | # toast 33 | fluttertoast: ^4.0.1 34 | # String 格式化 35 | sprintf: ^4.0.2 36 | # SharedPreference 37 | shared_preferences: ^0.5.6+3 38 | # 用系统浏览器打开url 39 | url_launcher: ^5.4.2 40 | flutter: 41 | sdk: flutter 42 | 43 | # The following adds the Cupertino Icons font to your application. 44 | # Use with the CupertinoIcons class for iOS style icons. 45 | cupertino_icons: ^0.1.2 46 | 47 | dev_dependencies: 48 | flutter_test: 49 | sdk: flutter 50 | 51 | 52 | # For information on the generic Dart part of this file, see the 53 | # following page: https://dart.dev/tools/pub/pubspec 54 | 55 | # The following section is specific to Flutter. 56 | flutter: 57 | 58 | # The following line ensures that the Material Icons font is 59 | # included with your application, so that you can use the icons in 60 | # the material Icons class. 61 | uses-material-design: true 62 | 63 | # 图片资源 64 | assets: 65 | - images/ 66 | - images/ic_launcher.png 67 | - images/ic_launcher_foreground.png 68 | 69 | # To add assets to your application, add an assets section, like this: 70 | # assets: 71 | # - images/a_dot_burr.jpeg 72 | # - images/a_dot_ham.jpeg 73 | 74 | # An image asset can refer to one or more resolution-specific "variants", see 75 | # https://flutter.dev/assets-and-images/#resolution-aware. 76 | 77 | # For details regarding adding assets from package dependencies, see 78 | # https://flutter.dev/assets-and-images/#from-packages 79 | 80 | # To add custom fonts to your application, add a fonts section here, 81 | # in this "flutter" section. Each entry in this list should have a 82 | # "family" key with the font family name, and a "fonts" key with a 83 | # list giving the asset and other descriptors for the font. For 84 | # example: 85 | # fonts: 86 | # - family: Schyler 87 | # fonts: 88 | # - asset: fonts/Schyler-Regular.ttf 89 | # - asset: fonts/Schyler-Italic.ttf 90 | # style: italic 91 | # - family: Trajan Pro 92 | # fonts: 93 | # - asset: fonts/TrajanPro.ttf 94 | # - asset: fonts/TrajanPro_Bold.ttf 95 | # weight: 700 96 | # 97 | # For details regarding fonts from package dependencies, 98 | # see https://flutter.dev/custom-fonts/#from-packages 99 | -------------------------------------------------------------------------------- /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:wanandroidflutter/page/wan_android_page.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(WanAndroidApp()); 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 | --------------------------------------------------------------------------------