├── lib ├── locale │ ├── i18n_en.json │ └── i18n_zh.json ├── utils │ ├── login_event.dart │ ├── collect_event.dart │ ├── refresh_event.dart │ ├── loginout_event.dart │ ├── error_event.dart │ ├── clipboard_utils.dart │ ├── Config.dart │ ├── cookie_util.dart │ ├── widget_utils.dart │ ├── sp_util.dart │ └── common.dart ├── page │ ├── input │ │ ├── page.dart │ │ └── single_tag.dart │ ├── feedback │ │ └── network_error.dart │ ├── todo │ │ └── todo_fragment.dart │ ├── account │ │ └── login_fragment.dart │ ├── system │ │ ├── system_fragment.dart │ │ ├── knowledge │ │ │ ├── KnowledgeListFragment.dart │ │ │ └── KnowledgeFragment.dart │ │ └── navigation │ │ │ └── NavigationFragment.dart │ ├── tabs.dart │ ├── wechat │ │ ├── wechat_fragment.dart │ │ └── wechat_list_fragment.dart │ ├── project │ │ ├── project_fragment.dart │ │ └── project_list_fragment.dart │ ├── wenda │ │ └── wenda_fragment.dart │ ├── home │ │ └── search_result_fragment.dart │ ├── rank │ │ └── rank_fragment.dart │ ├── share │ │ └── share_article_fragment.dart │ ├── square │ │ └── square_fragment.dart │ └── collect │ │ └── collect_fragment.dart ├── application.dart ├── theme │ ├── theme_model.dart │ ├── dark_model.dart │ ├── font_model.dart │ ├── theme_colors.dart │ └── locale_model.dart ├── widget │ ├── article_title.dart │ ├── icon_text.dart │ ├── animate_provider.dart │ ├── load_fail_widget.dart │ ├── favourite_animation.dart │ ├── custom_refresh.dart │ ├── login_widget.dart │ ├── coin_item.dart │ ├── marquee_widget.dart │ ├── title_bar.dart │ ├── page_widget.dart │ ├── share_item.dart │ └── collect_item.dart ├── http │ ├── base_response.dart │ ├── api.dart │ └── http_request.dart ├── data │ ├── hot_key.dart │ ├── rank.dart │ ├── banner.dart │ ├── article_response.dart │ ├── project_tab.dart │ ├── wechat_tab.dart │ ├── todo.dart │ ├── knowledge.dart │ ├── login.dart │ ├── navigation.dart │ └── article.dart ├── constant │ └── Constants.dart ├── routes │ └── routes.dart ├── l10n │ ├── intl_zh.arb │ └── intl_en.arb ├── splash.dart ├── generated │ └── intl │ │ ├── messages_all.dart │ │ ├── messages_zh.dart │ │ └── messages_en.dart ├── main_page.dart └── main.dart ├── android ├── settings_aar.gradle ├── key.properties ├── .gitignore ├── app │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── 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 │ │ │ │ └── drawable │ │ │ │ │ └── launch_background.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── wanandroidflutter │ │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ └── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle ├── build.gradle └── wanandroidflutter_android.iml ├── ios ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner │ ├── Runner-Bridging-Header.h │ ├── Assets.xcassets │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── 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-1024x1024@1x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Runner.xcworkspace │ └── contents.xcworkspacedata ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme └── .gitignore ├── assets ├── flrs │ └── like.flr ├── img │ ├── ic_back.png │ ├── ic_avatar.png │ ├── load_fail.png │ ├── ic_default.png │ └── load_no_data.png └── fonts │ ├── NotoSans-Regular.ttf │ └── ZCOOLKuaiLe-Regular.ttf ├── .gitignore ├── wanandroidflutter.iml ├── pubspec.yaml └── README.md /lib/locale/i18n_en.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/locale/i18n_zh.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /android/settings_aar.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /lib/utils/login_event.dart: -------------------------------------------------------------------------------- 1 | class LoginEvent { 2 | LoginEvent(); 3 | } 4 | -------------------------------------------------------------------------------- /lib/utils/collect_event.dart: -------------------------------------------------------------------------------- 1 | class CollectEvent { 2 | CollectEvent(); 3 | } 4 | -------------------------------------------------------------------------------- /lib/utils/refresh_event.dart: -------------------------------------------------------------------------------- 1 | class RefreshEvent { 2 | RefreshEvent(); 3 | } 4 | -------------------------------------------------------------------------------- /lib/utils/loginout_event.dart: -------------------------------------------------------------------------------- 1 | class LoginOutEvent { 2 | LoginOutEvent(); 3 | } 4 | -------------------------------------------------------------------------------- /assets/flrs/like.flr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangjianxiandev/WanAndroidFlutter/HEAD/assets/flrs/like.flr -------------------------------------------------------------------------------- /assets/img/ic_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangjianxiandev/WanAndroidFlutter/HEAD/assets/img/ic_back.png -------------------------------------------------------------------------------- /assets/img/ic_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangjianxiandev/WanAndroidFlutter/HEAD/assets/img/ic_avatar.png -------------------------------------------------------------------------------- /assets/img/load_fail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangjianxiandev/WanAndroidFlutter/HEAD/assets/img/load_fail.png -------------------------------------------------------------------------------- /assets/img/ic_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangjianxiandev/WanAndroidFlutter/HEAD/assets/img/ic_default.png -------------------------------------------------------------------------------- /assets/img/load_no_data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangjianxiandev/WanAndroidFlutter/HEAD/assets/img/load_no_data.png -------------------------------------------------------------------------------- /android/key.properties: -------------------------------------------------------------------------------- 1 | storePassword=wjx102519 2 | keyPassword=wjx102519 3 | keyAlias=key0 4 | storeFile=D:\\Android\\wjx.jks -------------------------------------------------------------------------------- /assets/fonts/NotoSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangjianxiandev/WanAndroidFlutter/HEAD/assets/fonts/NotoSans-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/ZCOOLKuaiLe-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangjianxiandev/WanAndroidFlutter/HEAD/assets/fonts/ZCOOLKuaiLe-Regular.ttf -------------------------------------------------------------------------------- /lib/page/input/page.dart: -------------------------------------------------------------------------------- 1 | class Page { 2 | final String labelId; 3 | final int labelIndex; 4 | 5 | Page(this.labelId, this.labelIndex); 6 | } 7 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /lib/utils/error_event.dart: -------------------------------------------------------------------------------- 1 | class ErrorEvent { 2 | int errorCode; 3 | String errorMessage; 4 | 5 | ErrorEvent(this.errorCode, this.errorMessage); 6 | } -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangjianxiandev/WanAndroidFlutter/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangjianxiandev/WanAndroidFlutter/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangjianxiandev/WanAndroidFlutter/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangjianxiandev/WanAndroidFlutter/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | android.enableAapt2=false 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangjianxiandev/WanAndroidFlutter/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangjianxiandev/WanAndroidFlutter/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangjianxiandev/WanAndroidFlutter/HEAD/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/wangjianxiandev/WanAndroidFlutter/HEAD/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/wangjianxiandev/WanAndroidFlutter/HEAD/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/wangjianxiandev/WanAndroidFlutter/HEAD/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/wangjianxiandev/WanAndroidFlutter/HEAD/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/wangjianxiandev/WanAndroidFlutter/HEAD/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/wangjianxiandev/WanAndroidFlutter/HEAD/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/wangjianxiandev/WanAndroidFlutter/HEAD/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/wangjianxiandev/WanAndroidFlutter/HEAD/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/wangjianxiandev/WanAndroidFlutter/HEAD/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/wangjianxiandev/WanAndroidFlutter/HEAD/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/wangjianxiandev/WanAndroidFlutter/HEAD/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/wangjianxiandev/WanAndroidFlutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangjianxiandev/WanAndroidFlutter/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangjianxiandev/WanAndroidFlutter/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangjianxiandev/WanAndroidFlutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangjianxiandev/WanAndroidFlutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/application.dart: -------------------------------------------------------------------------------- 1 | import 'package:event_bus/event_bus.dart'; 2 | import 'package:wanandroidflutter/utils/sp_util.dart'; 3 | 4 | /// 配置全局变量 5 | class Application { 6 | static EventBus eventBus; 7 | static SpUtil sp; 8 | } 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /lib/page/feedback/network_error.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | class NetWorkErrorPage extends StatelessWidget { 3 | @override 4 | Widget build(BuildContext context) { 5 | return Scaffold( 6 | appBar: AppBar( 7 | title: Text('Error'), 8 | ), 9 | ); 10 | } 11 | } -------------------------------------------------------------------------------- /lib/theme/theme_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ThemeModel with ChangeNotifier { 4 | Color _themeColor; 5 | 6 | get themeColor => _themeColor; 7 | 8 | void updateThemeColor(Color color) { 9 | this._themeColor = color; 10 | notifyListeners(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/page/todo/todo_fragment.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class TodoPage extends StatefulWidget { 4 | @override 5 | _TodoPageState createState() => _TodoPageState(); 6 | } 7 | 8 | class _TodoPageState extends State { 9 | @override 10 | Widget build(BuildContext context) { 11 | return Container(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /lib/utils/clipboard_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | 3 | class ClipboardUtil { 4 | static saveData2Clipboard(String text) { 5 | ClipboardData data = new ClipboardData(text: text); 6 | Clipboard.setData(data); 7 | } 8 | 9 | static Future getClipboardContents() async { 10 | var clipboardData = await Clipboard.getData(Clipboard.kTextPlain); 11 | return Future.value(clipboardData.text); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/theme/dark_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:wanandroidflutter/application.dart'; 3 | import 'package:wanandroidflutter/utils/Config.dart'; 4 | 5 | class DarkMode with ChangeNotifier { 6 | bool _isDark = false; 7 | 8 | get isDark => _isDark; 9 | 10 | void updateDarkMode(isDark) { 11 | _isDark = isDark; 12 | notifyListeners(); 13 | Application.sp.putBool(Config.SP_DARK_MODEL, isDark); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/theme/font_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:wanandroidflutter/application.dart'; 3 | import 'package:wanandroidflutter/utils/Config.dart'; 4 | 5 | class FontModel with ChangeNotifier { 6 | int _fontIndex; 7 | 8 | get fontIndex => _fontIndex; 9 | 10 | void updateFontIndex(int fontIndex) { 11 | this._fontIndex = fontIndex; 12 | notifyListeners(); 13 | Application.sp.putInt(Config.SP_FONT_INDEX, fontIndex); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/widget/article_title.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_html/flutter_html.dart'; 3 | 4 | class ArticleTitleWidget extends StatelessWidget { 5 | final String title; 6 | 7 | ArticleTitleWidget(this.title); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Html( 12 | padding: EdgeInsets.symmetric(vertical: 5), 13 | data: title, 14 | defaultTextStyle: Theme.of(context).textTheme.subtitle, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/utils/Config.dart: -------------------------------------------------------------------------------- 1 | class Config { 2 | static final SP_PWD = "pwd"; 3 | static final SP_USER_INFO = "userInfo"; 4 | static final SP_SEARCH_HISTORY = "search_history"; 5 | static final SP_COIN = "coin_info"; 6 | static final SP_THEME_COLOR = "theme_color"; 7 | static final SP_BEFORE_CHANGE_DARK_MODE = "color_before"; 8 | static final SP_DARK_MODEL = "dark_model"; 9 | static final SP_FONT_INDEX = "font_index"; 10 | static final SP_LOCALE_INDEX = "locale_idnex"; 11 | static final SP_AVATAR = "avatar"; 12 | } 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/wanandroidflutter/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.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 | class MainActivity: FlutterActivity() { 9 | override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { 10 | GeneratedPluginRegistrant.registerWith(flutterEngine); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/http/base_response.dart: -------------------------------------------------------------------------------- 1 | class BaseResponse { 2 | Object data; 3 | int errorCode; 4 | String errorMessage; 5 | 6 | BaseResponse.fromJson(Map json) { 7 | data = json["data"]; 8 | errorCode = json["errorCode"]; 9 | errorMessage = json["errorMessage"]; 10 | } 11 | 12 | Map toJson() { 13 | final Map data = new Map(); 14 | data["data"] = this.data; 15 | data["errorCode"] = this.errorCode; 16 | data["errorMessage"] = this.errorMessage; 17 | return data; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | .idea/ 8 | .vagrant/ 9 | .sconsign.dblite 10 | .svn/ 11 | 12 | *.swp 13 | profile 14 | 15 | DerivedData/ 16 | 17 | .generated/ 18 | 19 | *.pbxuser 20 | *.mode1v3 21 | *.mode2v3 22 | *.perspectivev3 23 | 24 | !default.pbxuser 25 | !default.mode1v3 26 | !default.mode2v3 27 | !default.perspectivev3 28 | 29 | xcuserdata 30 | 31 | *.moved-aside 32 | 33 | *.pyc 34 | *sync/ 35 | Icon? 36 | .tags* 37 | 38 | build/ 39 | .android/ 40 | .ios/ 41 | .flutter-plugins 42 | .flutter-plugins-dependencies 43 | .gitattributes 44 | .metadata 45 | LICENSE 46 | captures/ 47 | 48 | -------------------------------------------------------------------------------- /lib/utils/cookie_util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'package:cookie_jar/cookie_jar.dart'; 4 | import 'package:path_provider/path_provider.dart'; 5 | 6 | class CookieUtil { 7 | // 获取cookie地址 8 | static Future getCookiePath() async { 9 | Directory tempDir = await getTemporaryDirectory(); 10 | String tempPath = tempDir.path; 11 | return "${tempPath}/cookies"; 12 | } 13 | 14 | //清除所有cookie 15 | static Future deleteAllCookies() async { 16 | await getCookiePath().then((path) { 17 | PersistCookieJar cookieJar = PersistCookieJar(dir: path); 18 | cookieJar.deleteAll(); 19 | }); 20 | } 21 | } -------------------------------------------------------------------------------- /lib/data/hot_key.dart: -------------------------------------------------------------------------------- 1 | class HotKey { 2 | int visible; 3 | String link; 4 | String name; 5 | int id; 6 | int order; 7 | 8 | HotKey.fromJson(Map json) { 9 | visible = json['visible']; 10 | link = json['link']; 11 | name = json['name']; 12 | id = json['id']; 13 | order = json['order']; 14 | } 15 | 16 | Map toJson() { 17 | final Map data = new Map(); 18 | data['visible'] = this.visible; 19 | data['link'] = this.link; 20 | data['name'] = this.name; 21 | data['id'] = this.id; 22 | data['order'] = this.order; 23 | return data; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/widget/icon_text.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class IconTextWidget extends StatelessWidget { 4 | Widget icon; 5 | Widget text; 6 | double padding; 7 | 8 | IconTextWidget({@required this.icon, @required this.text, this.padding=0}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Center( 13 | child: Row( 14 | children: [ 15 | Padding( 16 | padding: EdgeInsets.only(right: padding), 17 | child: icon, 18 | ), 19 | Expanded( 20 | flex: 1, 21 | child: text, 22 | ), 23 | ], 24 | ), 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/data/rank.dart: -------------------------------------------------------------------------------- 1 | class RankData { 2 | int level; 3 | int rank; 4 | int userId; 5 | int coinCount; 6 | String username; 7 | 8 | RankData.fromJson(Map json) { 9 | level = json['level']; 10 | rank = json['rank']; 11 | userId = json['userId']; 12 | coinCount = json['coinCount']; 13 | username = json['username']; 14 | } 15 | 16 | Map toJson() { 17 | final Map data = new Map(); 18 | data['level'] = this.level; 19 | data['rank'] = this.rank; 20 | data['userId'] = this.userId; 21 | data['coinCount'] = this.coinCount; 22 | data['username'] = this.username; 23 | return data; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.3' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /lib/theme/theme_colors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/painting.dart'; 2 | 3 | List getThemeColors() { 4 | List themeColors = new List(); 5 | themeColors 6 | ..add(Color(0xffc54945)) // 7 | ..add(Color(0xfffc5e38)) // 8 | ..add(Color(0xfffd742d)) // 9 | ..add(Color(0xfff6b816)) // 10 | ..add(Color(0xffcae053)) // 11 | ..add(Color(0xff81c842)) // 12 | ..add(Color(0xff5cc095)) // 13 | ..add(Color(0xff1e88e5)) // 14 | ..add(Color(0xff5978e9)) // 15 | ..add(Color(0xff7668f6)) // 16 | ..add(Color(0xffa674e6)) // 17 | ..add(Color(0xffd477e6)) // 18 | ..add(Color(0xffec7ec5)) // 19 | ..add(Color(0xffed698b)) // 20 | ..add(Color(0xfff19fb4)) // 21 | ..add(Color(0xff323638)); 22 | return themeColors; 23 | } -------------------------------------------------------------------------------- /lib/constant/Constants.dart: -------------------------------------------------------------------------------- 1 | import 'package:wanandroidflutter/page/input/page.dart'; 2 | 3 | class Constants { 4 | /// 网络错误 5 | static const NETWORK_ERROR = -1; 6 | 7 | /// 网络超时 8 | static const NETWORK_TIMEOUT = -2; 9 | 10 | /// 返回数据格式化 11 | static const NETWORK_JSON_EXCEPTION = -3; 12 | 13 | /// 成功 14 | static const SUCCESS = 200; 15 | 16 | //主题颜色 17 | static const String THEME_COLOR_KEY = 'theme_color_key'; 18 | 19 | //是否为夜间模式 20 | static const String THEME_DARK_MODE_KEY = 'theme_dark_mode_key'; 21 | 22 | // 字体列表 23 | static const FontList = ['normal', 'kuaile']; 24 | 25 | // 语言列表 26 | static const LocaleList = ['', 'zh-CN', 'en']; 27 | 28 | static final List todoTypes = [ 29 | Page('只用这一个', 0), 30 | Page('工作', 1), 31 | Page('学习', 2), 32 | Page('生活', 3) 33 | ]; 34 | } 35 | -------------------------------------------------------------------------------- /lib/widget/animate_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ScaleAnimatedSwitcher extends StatelessWidget { 4 | final Widget child; 5 | 6 | ScaleAnimatedSwitcher({this.child}); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return AnimatedSwitcher( 11 | duration: Duration(milliseconds: 300), 12 | transitionBuilder: (child, animated) => ScaleTransition( 13 | scale: animated, 14 | child: child, 15 | ), 16 | child: child, 17 | ); 18 | } 19 | } 20 | 21 | class EmptyAnimatedSwitcher extends StatelessWidget { 22 | final bool display; 23 | final Widget child; 24 | const EmptyAnimatedSwitcher({Key key, this.display, this.child}) 25 | : super(key: key); 26 | @override 27 | Widget build(BuildContext context) { 28 | return ScaleAnimatedSwitcher(child: display ? child : SizedBox.shrink()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/data/banner.dart: -------------------------------------------------------------------------------- 1 | class BannerData { 2 | String desc; 3 | int id; 4 | String imagePath; 5 | int isVisible; 6 | int order; 7 | String title; 8 | int type; 9 | String url; 10 | 11 | BannerData.fromJson(Map map) { 12 | desc = map["desc"]; 13 | id = map["id"]; 14 | imagePath = map["imagePath"]; 15 | isVisible = map["isVisible"]; 16 | order = map["order"]; 17 | title = map["title"]; 18 | type = map["type"]; 19 | url = map["url"]; 20 | } 21 | 22 | Map toJson() { 23 | final Map data = new Map(); 24 | data["desc"] = this.desc; 25 | data["id"] = this.id; 26 | data["imagePath"] = this.imagePath; 27 | data["isVisible"] = this.isVisible; 28 | data["order"] = this.order; 29 | data["title"] = this.title; 30 | data["type"] = this.type; 31 | data["url"] = this.url; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /wanandroidflutter.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /lib/routes/routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:wanandroidflutter/page/tabs.dart'; 4 | 5 | // 增加要跳转的页面只需要在此处添加 6 | final routes = { 7 | // 配置根路由 8 | '/': (context, {argumment}) => Tabs(), 9 | }; 10 | 11 | // 配置路由, 在官方代码上的优化,为固定写法 12 | var onGenerateRoute = (RouteSettings settings) { 13 | // ignore: missing_return 14 | final String name = settings.name; 15 | final Function pageContentBuilder = routes[name]; 16 | if (pageContentBuilder != null) { 17 | // 路由配置不为空 18 | if (settings.arguments != null) { 19 | final Route route = MaterialPageRoute( 20 | // 进行跳转,传参 21 | builder: (context) => 22 | pageContentBuilder(context, arguments: settings.arguments)); 23 | return route; 24 | } else { 25 | final Route route = 26 | MaterialPageRoute(builder: (context) => pageContentBuilder(context)); 27 | return route; 28 | } 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /lib/widget/load_fail_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:wanandroidflutter/generated/l10n.dart'; 3 | 4 | //加载失败widget 5 | class LoadFailWidget extends StatelessWidget { 6 | Function onTap; 7 | 8 | LoadFailWidget({this.onTap}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return GestureDetector( 13 | onTap: () { 14 | onTap(); 15 | }, 16 | child: Center( 17 | child: Column( 18 | mainAxisAlignment: MainAxisAlignment.center, 19 | children: [ 20 | ImageIcon( 21 | AssetImage("assets/img/load_fail.png"), 22 | size: 50, 23 | ), 24 | SizedBox( 25 | height: 10, 26 | ), 27 | Text( 28 | S.of(context).network_error, 29 | style: Theme.of(context).textTheme.caption, 30 | ), 31 | ], 32 | ), 33 | ), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/data/article_response.dart: -------------------------------------------------------------------------------- 1 | class ArticleResponse { 2 | int curPage; 3 | int offset; 4 | int pageCount; 5 | int size; 6 | int total; 7 | bool over; 8 | var datas; 9 | 10 | ArticleResponse.fromJson(Map json) 11 | : curPage = json["curPage"], 12 | offset = json["offset"], 13 | size = json["size"], 14 | total = json["total"], 15 | over = json["over"], 16 | pageCount = json["pageCount"], 17 | datas = json["datas"]; 18 | 19 | Map toJson() => { 20 | "curPage": curPage, 21 | "offset": offset, 22 | "size": size, 23 | "total": total, 24 | "over": over, 25 | "pageCount": pageCount, 26 | "datas": datas, 27 | }; 28 | 29 | @override 30 | String toString() { 31 | return 'ArticleResponse{curPage: $curPage, offset: $offset, pageCount: $pageCount, size: $size, total: $total, over: $over, datas: $datas}'; 32 | } 33 | 34 | bool get hasNoMore => curPage == pageCount; 35 | } 36 | -------------------------------------------------------------------------------- /lib/data/project_tab.dart: -------------------------------------------------------------------------------- 1 | class ProjectTab { 2 | int visible; 3 | List children; 4 | String name; 5 | bool userControlSetTop; 6 | int id; 7 | int courseId; 8 | int parentChapterId; 9 | int order; 10 | 11 | ProjectTab.fromJson(Map json) { 12 | visible = json['visible']; 13 | if (json['children'] != null) { 14 | children = new List(); 15 | } 16 | name = json['name']; 17 | userControlSetTop = json['userControlSetTop']; 18 | id = json['id']; 19 | courseId = json['courseId']; 20 | parentChapterId = json['parentChapterId']; 21 | order = json['order']; 22 | } 23 | 24 | Map toJson() { 25 | final Map data = new Map(); 26 | data['visible'] = this.visible; 27 | if (this.children != null) { 28 | data['children'] = []; 29 | } 30 | data['name'] = this.name; 31 | data['userControlSetTop'] = this.userControlSetTop; 32 | data['id'] = this.id; 33 | data['courseId'] = this.courseId; 34 | data['parentChapterId'] = this.parentChapterId; 35 | data['order'] = this.order; 36 | return data; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/data/wechat_tab.dart: -------------------------------------------------------------------------------- 1 | class WeChatTab { 2 | int visible; 3 | List children; 4 | String name; 5 | bool userControlSetTop; 6 | int id; 7 | int courseId; 8 | int parentChapterId; 9 | int order; 10 | 11 | WeChatTab.fromJson(Map json) { 12 | visible = json['visible']; 13 | if (json['children'] != null) { 14 | children = new List(); 15 | } 16 | name = json['name']; 17 | userControlSetTop = json['userControlSetTop']; 18 | id = json['id']; 19 | courseId = json['courseId']; 20 | parentChapterId = json['parentChapterId']; 21 | order = json['order']; 22 | } 23 | 24 | Map toJson() { 25 | final Map data = new Map(); 26 | data['visible'] = this.visible; 27 | if (this.children != null) { 28 | data['children'] = []; 29 | } 30 | data['name'] = this.name; 31 | data['userControlSetTop'] = this.userControlSetTop; 32 | data['id'] = this.id; 33 | data['courseId'] = this.courseId; 34 | data['parentChapterId'] = this.parentChapterId; 35 | data['order'] = this.order; 36 | return data; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/page/input/single_tag.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SingleSelectTagView extends StatelessWidget { 4 | 5 | final int index; //为标识选中的 6 | final parent; //父控件 7 | final String choiceText; 8 | 9 | 10 | const SingleSelectTagView( 11 | {@required this.index, 12 | @required this.parent, 13 | @required this.choiceText}) 14 | : super(); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return Padding( 19 | padding: const EdgeInsets.all(8.0), 20 | child: ChoiceChip( 21 | label: Text(choiceText,style: TextStyle(color: Colors.white,fontWeight: FontWeight.bold)), 22 | //选定的时候背景 23 | selectedColor: Theme.of(context).primaryColor, 24 | //未选用得时候背景 25 | disabledColor: Colors.grey[300], 26 | labelStyle: TextStyle(fontWeight: FontWeight.w200, fontSize: 15.0), 27 | labelPadding: EdgeInsets.only(left: 8.0, right: 8.0), 28 | materialTapTargetSize: MaterialTapTargetSize.padded, 29 | onSelected: (bool value) { 30 | parent.onSelectedChanged(index); 31 | }, 32 | selected: parent.selectedType == index), 33 | ); 34 | } 35 | } -------------------------------------------------------------------------------- /lib/data/todo.dart: -------------------------------------------------------------------------------- 1 | class TodoData { 2 | int completeDate; 3 | String completeDateStr; 4 | String content; 5 | int date; 6 | String dateStr; 7 | int id; 8 | int priority; 9 | int status; 10 | String title; 11 | int type; 12 | int userId; 13 | 14 | TodoData.fromJson(Map json) { 15 | completeDate = json["completeDate"]; 16 | completeDateStr = json["completeDateStr"]; 17 | content = json["content"]; 18 | date = json["date"]; 19 | dateStr = json["dateStr"]; 20 | id = json["id"]; 21 | priority = json["priority"]; 22 | status = json["status"]; 23 | title = json["title"]; 24 | type = json["type"]; 25 | userId = json["userId"]; 26 | } 27 | 28 | Map toJson() { 29 | final Map data = Map(); 30 | data["completeDate"] = this.completeDate; 31 | data["completeDateStr"] = this.completeDateStr; 32 | data["content"] = this.content; 33 | data["date"] = this.date; 34 | data["dateStr"] = this.dateStr; 35 | data["id"] = this.id; 36 | data["prority"] = this.priority; 37 | data["status"] = this.status; 38 | data["title"] = this.title; 39 | data["type"] = this.type; 40 | data["userId"] = this.userId; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/theme/locale_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:wanandroidflutter/application.dart'; 3 | import 'package:wanandroidflutter/constant/Constants.dart'; 4 | import 'package:wanandroidflutter/generated/l10n.dart'; 5 | import 'package:wanandroidflutter/utils/Config.dart'; 6 | 7 | class LocaleModel with ChangeNotifier { 8 | int _localeIndex; 9 | 10 | get localeIndex => _localeIndex; 11 | 12 | Locale get locale { 13 | if (_localeIndex > 0) { 14 | var value = Constants.LocaleList[_localeIndex].split("-"); 15 | return Locale(value[0], value.length == 2 ? value[1] : ''); 16 | } 17 | // 跟随系统 18 | return null; 19 | } 20 | 21 | LocaleModel() { 22 | _localeIndex = Application.sp.getInt(Config.SP_LOCALE_INDEX) ?? 0; 23 | } 24 | 25 | void updateLocaleIndex(int localeIndex) { 26 | this._localeIndex = localeIndex; 27 | notifyListeners(); 28 | Application.sp.putInt(Config.SP_LOCALE_INDEX, localeIndex); 29 | } 30 | 31 | static String localeName(index, context) { 32 | switch (index) { 33 | case 0: 34 | return S.of(context).autoBySystem; 35 | case 1: 36 | return '中文'; 37 | case 2: 38 | return 'English'; 39 | default: 40 | return ''; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/data/knowledge.dart: -------------------------------------------------------------------------------- 1 | class KnowledgeData { 2 | int visible; 3 | List children; 4 | String name; 5 | bool userControlSetTop; 6 | int id; 7 | int courseId; 8 | int parentChapterId; 9 | int order; 10 | 11 | KnowledgeData.fromJson(Map json) { 12 | visible = json['visible']; 13 | if (json['children'] != null) { 14 | children = new List(); 15 | (json['children'] as List).forEach((v) { 16 | children.add(new KnowledgeData.fromJson(v)); 17 | }); 18 | } 19 | name = json['name']; 20 | userControlSetTop = json['userControlSetTop']; 21 | id = json['id']; 22 | courseId = json['courseId']; 23 | parentChapterId = json['parentChapterId']; 24 | order = json['order']; 25 | } 26 | 27 | Map toJson() { 28 | final Map data = new Map(); 29 | data['visible'] = this.visible; 30 | if (this.children != null) { 31 | data['children'] = this.children.map((v) => v.toJson()).toList(); 32 | } 33 | data['name'] = this.name; 34 | data['userControlSetTop'] = this.userControlSetTop; 35 | data['id'] = this.id; 36 | data['courseId'] = this.courseId; 37 | data['parentChapterId'] = this.parentChapterId; 38 | data['order'] = this.order; 39 | return data; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/data/login.dart: -------------------------------------------------------------------------------- 1 | class LoginData { 2 | String password; 3 | String publicName; 4 | List chapterTops; 5 | String icon; 6 | String nickname; 7 | bool admin; 8 | List collectIds; 9 | int id; 10 | int type; 11 | String email; 12 | String token; 13 | String username; 14 | 15 | LoginData.fromJson(Map json) { 16 | password = json['password']; 17 | publicName = json['publicName']; 18 | chapterTops = json['chapterTops']?.cast(); 19 | icon = json['icon']; 20 | nickname = json['nickname']; 21 | admin = json['admin']; 22 | collectIds = json['collectIds']?.cast(); 23 | id = json['id']; 24 | type = json['type']; 25 | email = json['email']; 26 | token = json['token']; 27 | username = json['username']; 28 | } 29 | 30 | Map toJson() { 31 | final Map data = new Map(); 32 | data['password'] = this.password; 33 | data['publicName'] = this.publicName; 34 | data['chapterTops'] = this.chapterTops; 35 | data['icon'] = this.icon; 36 | data['nickname'] = this.nickname; 37 | data['admin'] = this.admin; 38 | data['collectIds'] = this.collectIds; 39 | data['id'] = this.id; 40 | data['type'] = this.type; 41 | data['email'] = this.email; 42 | data['token'] = this.token; 43 | data['username'] = this.username; 44 | return data; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /android/wanandroidflutter_android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/utils/widget_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class WidgetUtils{ 4 | 5 | static Widget buildStrokeWidget(String text , Color color, FontWeight fontWeight, double fontSize) { 6 | return Padding( 7 | child: StrokeWidget( 8 | strokeWidth: 0.5, 9 | edgeInsets: EdgeInsets.symmetric(horizontal: 2.0, vertical: 0.0), 10 | color: color, 11 | childWidget: Text( 12 | text, 13 | style: TextStyle( 14 | fontSize: fontSize, 15 | color: color, 16 | fontWeight: fontWeight), 17 | ) 18 | ), 19 | padding: EdgeInsets.only(right: 5.0), 20 | ); 21 | } 22 | } 23 | 24 | class StrokeWidget extends StatelessWidget { 25 | final Color color; 26 | final Widget childWidget; 27 | EdgeInsets edgeInsets; 28 | final double strokeWidth; 29 | final double strokeRadius; 30 | 31 | StrokeWidget( 32 | {Key key, 33 | @required this.childWidget, 34 | this.color = Colors.black, 35 | this.edgeInsets, 36 | this.strokeWidth = 1.0, 37 | this.strokeRadius = 5.0}) 38 | : super(key: key) { 39 | if (null == edgeInsets) 40 | edgeInsets = EdgeInsets.symmetric(horizontal: 2.0, vertical: 0.0); 41 | } 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | return Container( 46 | padding: edgeInsets, 47 | decoration: BoxDecoration( 48 | border: Border.all(color: color, width: strokeWidth), 49 | borderRadius: BorderRadius.circular(strokeRadius)), 50 | child: childWidget); 51 | } 52 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/l10n/intl_zh.arb: -------------------------------------------------------------------------------- 1 | { 2 | "tab_home": "首页", 3 | "tab_system": "体系", 4 | "tab_wechat": "公众号", 5 | "tab_project": "项目", 6 | "tab_navigation": "导航", 7 | "share_to_square": "分享到广场", 8 | "collect": "收藏", 9 | "copy_link": "复制链接", 10 | "copy_tip": "复制成功", 11 | "exit": "退出", 12 | "level": "排名: ", 13 | "integral": "积分: ", 14 | "login_tip": "点击头像登录", 15 | "crop_image" : "裁剪头像", 16 | "square": "广场", 17 | "share": "分享", 18 | "me_share": "我的分享", 19 | "me_collect": "我的收藏", 20 | "wenda": "问答", 21 | "theme": "主题", 22 | "rank": "积分排行榜", 23 | "integral_rank": "积分排行", 24 | "loginout": "退出登录", 25 | "loginoutTip": "登出成功", 26 | "setting": "设置", 27 | "confirm": "确认", 28 | "cancel": "取消", 29 | "theme_choose": "主题颜色选择", 30 | "theme_tips": "夜间模式下不可以更改主题嗷~", 31 | "night_mode": "夜间模式", 32 | "switching_fonts": "切换字体", 33 | "normol_font": "正常字体", 34 | "kuaile_font": "喵趣字体", 35 | "language_setting": "语言设置", 36 | "autoBySystem": "跟随系统", 37 | "clear_cache": "清空缓存", 38 | "clear_cache_tip": "确定清除缓存吗?", 39 | "about": "关于", 40 | "my_blog": "我的博客", 41 | "register": "注册", 42 | "username": "请输入用户名", 43 | "password": "请输入密码", 44 | "repassword": "请输入密码", 45 | "login": "登录", 46 | "input_search": "请输入搜索关键字", 47 | "search_tip": "输入字段为空", 48 | "search": "搜索", 49 | "hot_search": "热门搜索", 50 | "search_history": "搜索记录", 51 | "clear_history": "清空", 52 | "new_article": "新", 53 | "top": "置顶", 54 | "Stranger": "匿名", 55 | "down_refresh" : "下拉刷新", 56 | "up_refresh" : "上拉刷新", 57 | "refreshing" : "刷新中", 58 | "update_time" : "更新时间: ", 59 | "release_refresh" : "释放刷新", 60 | "complete_refresh" : "刷新完成", 61 | "network_error": "网络异常,点击重试!", 62 | "no_data": "没有数据,请重试!" 63 | } -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | wanandroidflutter 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /lib/l10n/intl_en.arb: -------------------------------------------------------------------------------- 1 | { 2 | "tab_home": "Home", 3 | "tab_system": "System", 4 | "tab_wechat": "WX", 5 | "tab_project": "Project", 6 | "tab_navigation": "Navigation", 7 | "share_to_square": "Share", 8 | "collect": "Collect", 9 | "copy_link": "CopyLink", 10 | "copy_tip": "CopySuccess", 11 | "exit": "Exit", 12 | "level": "level: ", 13 | "integral": "Integral: ", 14 | "login_tip": "Click avatar login", 15 | "crop_image" : "Crop image", 16 | "square": "Square", 17 | "me_share": "MeShare", 18 | "me_collect": "MeCollect", 19 | "wenda": "Q&A", 20 | "theme": "Theme", 21 | "rank": "Ranking", 22 | "integral_rank": "ranking", 23 | "loginout": "LoginOut", 24 | "loginoutTip": "Loginout Success", 25 | "setting": "Setting", 26 | "confirm": "Confirm", 27 | "cancel": "Cancel", 28 | "theme_choose": "Theme Choose", 29 | "theme_tips": "Cannot be changed in night mode~", 30 | "night_mode": "NightMode", 31 | "switching_fonts": "Fonts", 32 | "normol_font": "Normal", 33 | "kuaile_font": "MiaoQu", 34 | "language_setting": "Language", 35 | "autoBySystem": "Auto", 36 | "clear_cache": "ClearCache", 37 | "clear_cache_tip": "Are you sure?", 38 | "about": "About", 39 | "my_blog": "Blog", 40 | "register": "Register", 41 | "username": "Input username", 42 | "password": "Input password", 43 | "repassword": "Confirm password", 44 | "login": "Login", 45 | "input_search": "Input search key", 46 | "search_tip": "Input Empty", 47 | "search": "go", 48 | "hot_search": "HotSearch", 49 | "search_history": "SearchHistory", 50 | "clear_history": "Clear", 51 | "new_article": "New", 52 | "top": "Top", 53 | "stranger": "Stranger", 54 | "down_refresh" : "Down refresh", 55 | "up_refresh" : "Up refresh", 56 | "refreshing" : "refreshing", 57 | "update_time" : "update time: ", 58 | "release_refresh" : "release refresh", 59 | "complete_refresh" : "complete", 60 | "network_error": "Network error, please try again!", 61 | "no_data": "No data, please try again!" 62 | } -------------------------------------------------------------------------------- /lib/page/account/login_fragment.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | import 'package:wanandroidflutter/page/account/login_form.dart'; 4 | import 'package:wanandroidflutter/page/account/register_form.dart'; 5 | import 'package:wanandroidflutter/theme/theme_model.dart'; 6 | import 'package:wanandroidflutter/widget/login_widget.dart'; 7 | 8 | class LoginPage extends StatelessWidget { 9 | var _pageController = new PageController(initialPage: 0); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | var appTheme = Provider.of(context); 14 | return Scaffold( 15 | appBar: AppBar( 16 | backgroundColor: appTheme.themeColor, 17 | leading: IconButton( 18 | icon: Icon(Icons.arrow_back), 19 | onPressed: () { 20 | Navigator.of(context).pop(); 21 | }, 22 | ), 23 | ), 24 | body: Stack(children: [ 25 | Column( 26 | crossAxisAlignment: CrossAxisAlignment.center, 27 | children: [ 28 | Expanded( 29 | flex: 2, 30 | child: 31 | Stack( 32 | children: [ 33 | LoginTopPanel(), 34 | Align( 35 | alignment: Alignment.center, 36 | child: LoginLogo(), 37 | ) 38 | ], 39 | ), 40 | ), 41 | Expanded( 42 | child: new PageView.builder( 43 | itemBuilder: (BuildContext context, int index) { 44 | return index == 0 45 | ? new LoginForm(_pageController) 46 | : new RegisterForm(_pageController); 47 | }, 48 | itemCount: 2, 49 | controller: _pageController, 50 | ), 51 | flex: 4, 52 | ) 53 | ]), 54 | ])); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/http/api.dart: -------------------------------------------------------------------------------- 1 | class Api { 2 | static const String BASE_URL = "https://www.wanandroid.com/"; 3 | 4 | //首页banner 5 | static const String BANNER_URL = "banner/json"; 6 | 7 | //置顶文章 8 | static const String ARTICLE_TOP = "article/top/json"; 9 | 10 | //首页文章 11 | static const String HOME_ARTICLE_LIST = "article/list/"; 12 | 13 | //搜索热词 14 | static const String HOT_KEY = "hotkey/json"; 15 | 16 | //搜索 17 | static const String SEARCH_RESULT_LIST = "article/query/"; 18 | 19 | //收藏站内文章 20 | static const String COLLECT = "lg/collect/"; 21 | 22 | //取消收藏-文章列表 23 | static const String UN_COLLECT_ORIGIN_ID = "lg/uncollect_originId/"; 24 | 25 | //登录 26 | static const String LOGIN = "user/login"; 27 | 28 | //退出登录 29 | static const String LOGIN_OUT_JSON = 'user/logout/json'; 30 | 31 | //注册 32 | static const String REGISTER = "user/register"; 33 | 34 | //获取公众号Tab 35 | static const String WECHAT_TAB = "wxarticle/chapters/json"; 36 | 37 | //获取公众号文章 38 | static const String WECHAT_LIST = "wxarticle/list/"; 39 | 40 | //获取项目分类 41 | static const String PROJECT_TAB = "project/tree/json"; 42 | 43 | //获取项目列表数据 44 | static const String PROJECT_LIST = "project/list/"; 45 | 46 | //获取体系数据 47 | static const String TREE = "tree/json"; 48 | 49 | //获取导航数据 50 | static const String NAVIGATION = "navi/json"; 51 | 52 | //获取用户积分数据 53 | static const String COIN_INFO = "lg/coin/userinfo/json"; 54 | 55 | //获取收藏文章列表 56 | static const String COLLECT_LIST = "lg/collect/list/"; 57 | 58 | //取消收藏 59 | static const String UN_COLLECT = "lg/uncollect/"; 60 | 61 | //添加站外收藏 62 | static const String ADD_COLLECT_ARTICLE = "lg/collect/add/json"; 63 | 64 | //获取广场数据 65 | static const String SQUARE_LIST = "user_article/list/"; 66 | 67 | //获取积分列表 68 | static const String RANK_LIST = "coin/rank/"; 69 | 70 | //获取问答列表 71 | static const String WENDA_LIST = "wenda/list/"; 72 | 73 | //分享文章 74 | static const String SHARE_ARTICLE = "lg/user_article/add/json"; 75 | 76 | //分享文章列表 77 | static const String SHARE_ARTICLE_LIST = "user/lg/private_articles/"; 78 | } 79 | -------------------------------------------------------------------------------- /lib/page/system/system_fragment.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | import 'package:wanandroidflutter/generated/l10n.dart'; 4 | import 'package:wanandroidflutter/page/system/knowledge/KnowledgeFragment.dart'; 5 | import 'package:wanandroidflutter/page/system/navigation/NavigationFragment.dart'; 6 | import 'package:wanandroidflutter/theme/theme_model.dart'; 7 | 8 | class SystemFragment extends StatefulWidget { 9 | @override 10 | _SystemFragmentState createState() => _SystemFragmentState(); 11 | } 12 | 13 | class _SystemFragmentState extends State 14 | with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin { 15 | TabController mTabController; 16 | int index = 0; 17 | 18 | @override 19 | void initState() { 20 | // TODO: implement initState 21 | super.initState(); 22 | mTabController = 23 | TabController(initialIndex: 0, length: 2, vsync: this); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | var appTheme = Provider.of(context); 29 | return Scaffold( 30 | appBar: AppBar( 31 | backgroundColor: appTheme.themeColor, 32 | centerTitle: true, 33 | title: TabBar( 34 | controller: mTabController, 35 | //可以和TabBarView使用同一个TabController 36 | tabs: [ 37 | Tab( 38 | text: S.of(context).tab_system, 39 | ), 40 | Tab( 41 | text: S.of(context).tab_navigation, 42 | ) 43 | ], 44 | isScrollable: true, 45 | indicatorColor: Colors.white, 46 | labelColor: Colors.white, 47 | labelStyle: Theme.of(context).textTheme.subtitle, 48 | unselectedLabelColor: Colors.grey, 49 | unselectedLabelStyle: Theme.of(context).textTheme.caption, 50 | ), 51 | ), 52 | body: TabBarView( 53 | controller: mTabController, 54 | children: [KnowledgeFragment(), NavigationFragment()], 55 | ), 56 | ); 57 | } 58 | 59 | @override 60 | // TODO: implement wantKeepAlive 61 | bool get wantKeepAlive => true; 62 | } 63 | -------------------------------------------------------------------------------- /lib/page/tabs.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | import 'package:wanandroidflutter/generated/l10n.dart'; 4 | import 'package:wanandroidflutter/page/home/home_fragment.dart'; 5 | import 'package:wanandroidflutter/page/project/project_fragment.dart'; 6 | import 'package:wanandroidflutter/page/system/system_fragment.dart'; 7 | import 'package:wanandroidflutter/page/wechat/wechat_fragment.dart'; 8 | import 'package:wanandroidflutter/theme/theme_model.dart'; 9 | 10 | class Tabs extends StatefulWidget { 11 | final index; 12 | 13 | Tabs({Key key, this.index = 0}) : super(key: key); 14 | 15 | _TabsState createState() => _TabsState(this.index); 16 | } 17 | 18 | class _TabsState extends State { 19 | int _currentIndex = 0; 20 | 21 | _TabsState(index) { 22 | this._currentIndex = index; 23 | } 24 | 25 | List _pageList = [ 26 | HomeFragment(), 27 | SystemFragment(), 28 | WeChatFragment(), 29 | ProjectFragment() 30 | ]; 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | var appTheme = Provider.of(context); 35 | return Scaffold( 36 | body: this._pageList[this._currentIndex], 37 | bottomNavigationBar: BottomNavigationBar( 38 | currentIndex: this._currentIndex, 39 | //配置对应的索引值选中 40 | onTap: (int index) { 41 | setState(() { 42 | //改变状态 43 | this._currentIndex = index; 44 | }); 45 | }, 46 | fixedColor: appTheme.themeColor, 47 | //选中的颜色 48 | type: BottomNavigationBarType.fixed, 49 | //配置底部tabs可以有多个按钮 50 | items: [ 51 | BottomNavigationBarItem(icon: Icon(Icons.home), title: Text(S.of(context).tab_home)), 52 | BottomNavigationBarItem( 53 | icon: Icon(Icons.category), title: Text(S.of(context).tab_system)), 54 | BottomNavigationBarItem( 55 | icon: Icon(Icons.settings), title: Text(S.of(context).tab_wechat)), 56 | BottomNavigationBarItem( 57 | icon: Icon(Icons.category), title: Text(S.of(context).tab_project)) 58 | ], 59 | ), 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /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: 2.0+1 15 | 16 | environment: 17 | sdk: ">=2.1.0 <3.0.0" 18 | 19 | dependencies: 20 | flutter: 21 | sdk: flutter 22 | cupertino_icons: ^0.1.2 23 | flutter_swiper: ^1.1.6 24 | dio: ^3.0.9 25 | cookie_jar: ^1.0.1 26 | dio_cookie_manager: ^1.0.0 27 | flutter_easyrefresh: ^1.2.7 28 | event_bus: ^1.1.1 29 | path_provider: ^0.5.0+1 30 | shared_preferences: ^0.5.6 31 | webview_flutter: ^0.3.19+9 32 | flutter_html: ^0.8.2 33 | image_picker: ^0.6.5+2 34 | image_cropper: ^1.2.1 35 | flare_flutter: ^2.0.2 36 | fluttertoast: ^4.0.0 37 | url_launcher: ^5.4.2 38 | date_format: ^1.0.8 39 | provider: 3.2.0 40 | permission_handler: ^5.0.0+hotfix.3 41 | 42 | dev_dependencies: 43 | flutter_test: 44 | sdk: flutter 45 | flutter_localizations: 46 | sdk: flutter 47 | 48 | 49 | 50 | flutter: 51 | uses-material-design: true 52 | 53 | fonts: 54 | - family: normal 55 | fonts: 56 | - asset: assets/fonts/NotoSans-Regular.ttf 57 | - family: kuaile 58 | fonts: 59 | - asset: assets/fonts/ZCOOLKuaiLe-Regular.ttf 60 | 61 | assets: 62 | - assets/flrs/ 63 | - assets/img/ic_back.png 64 | - assets/img/load_fail.png 65 | - assets/img/load_no_data.png 66 | - assets/img/ic_default.png 67 | - assets/img/ic_avatar.png 68 | flutter_intl: 69 | enabled: true -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 19 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | 36 | 37 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /lib/widget/favourite_animation.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flare_flutter/flare_actor.dart'; 3 | 4 | class FavouriteAnimation extends StatefulWidget { 5 | // hero动画唯一标识 6 | final Object tag; 7 | 8 | final bool isAdded; 9 | 10 | const FavouriteAnimation({Key key, this.tag, this.isAdded}) : super(key: key); 11 | 12 | @override 13 | _FavouriteAnimationState createState() => _FavouriteAnimationState(); 14 | } 15 | 16 | class _FavouriteAnimationState extends State { 17 | bool isPlaying = false; 18 | 19 | @override 20 | void initState() { 21 | // TODO: implement initState 22 | super.initState(); 23 | 24 | // addPostFrameCallback 是 StatefulWidge 渲染结束的回调,只会被调用一次, 25 | // 之后 StatefulWidget 需要刷新 UI 也不会被调用,addPostFrameCallback 的使用方法是在 initState 里添加回调 26 | WidgetsBinding.instance.addPostFrameCallback((_) { 27 | setState(() { 28 | isPlaying = true; 29 | }); 30 | }); 31 | } 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return Hero( 36 | tag: widget.tag, 37 | child: FlareActor( 38 | "assets/flrs/like.flr", 39 | alignment: Alignment.center, 40 | fit: BoxFit.contain, 41 | animation: widget.isAdded ? 'like' : 'unLike', 42 | shouldClip: false, 43 | isPaused: !isPlaying, 44 | callback: (name) { 45 | Navigator.pop(context); 46 | isPlaying = false; 47 | }, 48 | ), 49 | ); 50 | } 51 | } 52 | 53 | class HeroDialogRoute extends PageRoute { 54 | final WidgetBuilder builder; 55 | 56 | HeroDialogRoute({this.builder}) : super(); 57 | 58 | @override 59 | Color get barrierColor => Colors.black12; 60 | 61 | @override 62 | String get barrierLabel => null; 63 | 64 | // barrierDismissible若为false,点击对话框周围,对话框不会关闭;若为true,点击对话框周围,对话框自动关闭 65 | @override 66 | bool get barrierDismissible => true; 67 | 68 | // 是否透明 69 | @override 70 | bool get opaque => false; 71 | 72 | // 创建转场动画 73 | @override 74 | Widget buildPage(BuildContext context, Animation animation, 75 | Animation secondaryAnimation) { 76 | return builder(context); 77 | } 78 | 79 | @override 80 | bool get maintainState => true; 81 | 82 | // 转场动画持续时间 83 | @override 84 | Duration get transitionDuration => const Duration(milliseconds: 800); 85 | } 86 | -------------------------------------------------------------------------------- /lib/splash.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:wanandroidflutter/application.dart'; 5 | import 'package:wanandroidflutter/data/login.dart'; 6 | import 'package:wanandroidflutter/http/api.dart'; 7 | import 'package:wanandroidflutter/http/http_request.dart'; 8 | import 'package:wanandroidflutter/utils/Config.dart'; 9 | import 'package:wanandroidflutter/utils/login_event.dart'; 10 | import 'main_page.dart'; 11 | 12 | class SplashView extends StatefulWidget { 13 | @override 14 | _SplashViewState createState() => _SplashViewState(); 15 | } 16 | 17 | class _SplashViewState extends State { 18 | getUserInfo() async { 19 | String info = Application.sp.getString(Config.SP_USER_INFO); 20 | if (info != null && info.isNotEmpty) { 21 | Map userMap = json.decode(info); 22 | LoginData userEntity = new LoginData.fromJson(userMap); 23 | String _name = userEntity.username; 24 | String _pwd = Application.sp.getString(Config.SP_PWD); 25 | if (_pwd != null && _pwd.isNotEmpty) { 26 | doLogin(_name, _pwd); 27 | } 28 | } 29 | } 30 | 31 | // 登录 32 | doLogin(String _name, String _pwd) { 33 | var data; 34 | data = {'username': _name, 'password': _pwd}; 35 | HttpRequest.getInstance().post(Api.LOGIN, data: data, 36 | successCallBack: (data) { 37 | Application.eventBus.fire(LoginEvent()); 38 | saveInfo(data); 39 | Navigator.of(context).pop(); 40 | }, errorCallBack: (code, msg) {}); 41 | } 42 | 43 | // 保存用户信息 44 | void saveInfo(data) async { 45 | await Application.sp.putString(Config.SP_USER_INFO, data); 46 | } 47 | 48 | void countdown() { 49 | Timer(new Duration(seconds: 2), () { 50 | Navigator.pushAndRemoveUntil( 51 | context, 52 | MaterialPageRoute(builder: (context) => MainPage()), 53 | (route) => route == null, 54 | ); 55 | }); 56 | } 57 | 58 | @override 59 | void initState() { 60 | super.initState(); 61 | getUserInfo(); 62 | countdown(); 63 | } 64 | 65 | @override 66 | Widget build(BuildContext context) { 67 | return Scaffold( 68 | body: MediaQuery.removePadding( 69 | context: context, 70 | removeTop: true, 71 | child: Container( 72 | alignment: Alignment.center, 73 | child: Icon(Icons.android), 74 | ))); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/widget/custom_refresh.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_easyrefresh/easy_refresh.dart'; 3 | import 'package:wanandroidflutter/generated/l10n.dart'; 4 | 5 | ///自定义刷新控件头部尾部 6 | class CustomRefresh extends StatefulWidget { 7 | Widget child; 8 | GlobalKey easyRefreshKey; 9 | 10 | Function onRefresh; 11 | Function loadMore; 12 | 13 | CustomRefresh({ 14 | @required this.child, 15 | this.onRefresh, 16 | this.loadMore, 17 | @required this.easyRefreshKey, 18 | }); 19 | 20 | @override 21 | State createState() { 22 | return _CustomRefreshState(); 23 | } 24 | } 25 | 26 | class _CustomRefreshState extends State { 27 | GlobalKey headerKey; 28 | GlobalKey footerKey; 29 | 30 | @override 31 | void initState() { 32 | super.initState(); 33 | headerKey = new GlobalKey(); 34 | 35 | footerKey = new GlobalKey(); 36 | } 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | return EasyRefresh( 41 | key: widget.easyRefreshKey, 42 | autoLoad: false, 43 | onRefresh: widget.onRefresh, 44 | loadMore: widget.loadMore, 45 | behavior: ScrollOverBehavior(), 46 | refreshHeader: ClassicsHeader( 47 | key: headerKey, 48 | refreshText: S.of(context).down_refresh, 49 | refreshReadyText: S.of(context).release_refresh, 50 | refreshingText: S.of(context).refreshing, 51 | refreshedText: S.of(context).complete_refresh, 52 | moreInfo: S.of(context).update_time + "%T", 53 | bgColor: Colors.transparent, 54 | textColor: Colors.black87, 55 | moreInfoColor: Colors.black54, 56 | isFloat: false, 57 | showMore: true, 58 | ), 59 | refreshFooter: ClassicsFooter( 60 | key: footerKey, 61 | loadText: S.of(context).up_refresh, 62 | loadReadyText: S.of(context).release_refresh, 63 | loadingText: S.of(context).refreshing, 64 | loadedText: S.of(context).complete_refresh, 65 | noMoreText: S.of(context).complete_refresh, 66 | moreInfo: S.of(context).update_time + "%T", 67 | bgColor: Colors.transparent, 68 | textColor: Colors.black87, 69 | moreInfoColor: Colors.black54, 70 | isFloat: false, 71 | showMore: true, 72 | ), 73 | child: widget.child, 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/generated/intl/messages_all.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart 2 | // This is a library that looks up messages for specific locales by 3 | // delegating to the appropriate library. 4 | 5 | // Ignore issues from commonly used lints in this file. 6 | // ignore_for_file:implementation_imports, file_names, unnecessary_new 7 | // ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering 8 | // ignore_for_file:argument_type_not_assignable, invalid_assignment 9 | // ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases 10 | // ignore_for_file:comment_references 11 | 12 | import 'dart:async'; 13 | 14 | import 'package:intl/intl.dart'; 15 | import 'package:intl/message_lookup_by_library.dart'; 16 | import 'package:intl/src/intl_helpers.dart'; 17 | 18 | import 'messages_en.dart' as messages_en; 19 | import 'messages_zh.dart' as messages_zh; 20 | 21 | typedef Future LibraryLoader(); 22 | Map _deferredLibraries = { 23 | 'en': () => new Future.value(null), 24 | 'zh': () => new Future.value(null), 25 | }; 26 | 27 | MessageLookupByLibrary _findExact(String localeName) { 28 | switch (localeName) { 29 | case 'en': 30 | return messages_en.messages; 31 | case 'zh': 32 | return messages_zh.messages; 33 | default: 34 | return null; 35 | } 36 | } 37 | 38 | /// User programs should call this before using [localeName] for messages. 39 | Future initializeMessages(String localeName) async { 40 | var availableLocale = Intl.verifiedLocale( 41 | localeName, 42 | (locale) => _deferredLibraries[locale] != null, 43 | onFailure: (_) => null); 44 | if (availableLocale == null) { 45 | return new Future.value(false); 46 | } 47 | var lib = _deferredLibraries[availableLocale]; 48 | await (lib == null ? new Future.value(false) : lib()); 49 | initializeInternalMessageLookup(() => new CompositeMessageLookup()); 50 | messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor); 51 | return new Future.value(true); 52 | } 53 | 54 | bool _messagesExistFor(String locale) { 55 | try { 56 | return _findExact(locale) != null; 57 | } catch (e) { 58 | return false; 59 | } 60 | } 61 | 62 | MessageLookupByLibrary _findGeneratedMessagesFor(String locale) { 63 | var actualLocale = Intl.verifiedLocale(locale, _messagesExistFor, 64 | onFailure: (_) => null); 65 | if (actualLocale == null) return null; 66 | return _findExact(actualLocale); 67 | } 68 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/widget/login_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:provider/provider.dart'; 4 | import 'package:wanandroidflutter/theme/theme_model.dart'; 5 | 6 | class LoginTopPanel extends StatelessWidget { 7 | @override 8 | Widget build(BuildContext context) { 9 | var appTheme = Provider.of(context); 10 | return ClipPath( 11 | clipper: BottomClipper(), 12 | child: Container( 13 | height: 200, 14 | color: appTheme.themeColor, 15 | ), 16 | ); 17 | } 18 | } 19 | 20 | class LoginLogo extends StatelessWidget { 21 | @override 22 | Widget build(BuildContext context) { 23 | var appTheme = Provider.of(context); 24 | return Hero( 25 | tag: "loginlogo", 26 | child: Image.asset( 27 | "assets/img/ic_avatar.png", 28 | width: 230, 29 | height: 200, 30 | fit: BoxFit.fitWidth, 31 | color: Colors.white, 32 | colorBlendMode: BlendMode.srcIn, 33 | ), 34 | ); 35 | } 36 | } 37 | 38 | class LoginFormContainer extends StatelessWidget { 39 | final Widget child; 40 | 41 | LoginFormContainer({this.child}); 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | var appTheme = Provider.of(context); 46 | return Container( 47 | margin: EdgeInsets.symmetric(horizontal: 30, vertical: 30), 48 | padding: EdgeInsets.symmetric(horizontal: 10, vertical: 10), 49 | decoration: ShapeDecoration( 50 | shape: RoundedRectangleBorder(), 51 | color: appTheme.themeColor, 52 | shadows: [ 53 | BoxShadow( 54 | color: appTheme.themeColor.withAlpha(20), 55 | offset: Offset(1.0, 1.0), 56 | blurRadius: 10.0, 57 | spreadRadius: 3.0, 58 | ) 59 | ]), 60 | child: child, 61 | ); 62 | } 63 | } 64 | 65 | class BottomClipper extends CustomClipper { 66 | @override 67 | getClip(Size size) { 68 | var path = Path(); 69 | path.lineTo(0, 0); 70 | path.lineTo(0, size.height - 50); 71 | 72 | var p1 = Offset(size.width / 2, size.height); 73 | var p2 = Offset(size.width, size.height - 50); 74 | path.quadraticBezierTo(p1.dx, p1.dy, p2.dx, p2.dy); 75 | path.lineTo(size.width, size.height - 50); 76 | path.lineTo(size.width, 0); 77 | return path; 78 | } 79 | 80 | @override 81 | bool shouldReclip(CustomClipper oldClipper) { 82 | return false; 83 | } 84 | } -------------------------------------------------------------------------------- /lib/widget/coin_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | import 'package:wanandroidflutter/data/rank.dart'; 4 | import 'package:wanandroidflutter/theme/dark_model.dart'; 5 | import 'package:wanandroidflutter/theme/theme_model.dart'; 6 | 7 | class CoinRankWidget extends StatefulWidget { 8 | RankData rankData; 9 | 10 | CoinRankWidget(this.rankData); 11 | 12 | @override 13 | _CoinRankWidgetState createState() => _CoinRankWidgetState(); 14 | } 15 | 16 | class _CoinRankWidgetState extends State { 17 | RankData rankData; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | var appTheme = Provider.of(context); 22 | var isDarkMode = Provider.of(context).isDark; 23 | rankData = widget.rankData; 24 | return ListTile( 25 | dense: true, 26 | contentPadding: EdgeInsets.symmetric(horizontal: 25, vertical: 10), 27 | onTap: () {}, 28 | leading: buildIcon(rankData, isDarkMode), 29 | title: Text( 30 | rankData == null ? "--" : (rankData.username), 31 | style: TextStyle(fontSize: 16), 32 | ), 33 | trailing: Text( 34 | rankData == null ? "--" : rankData.coinCount.toString(), 35 | style: TextStyle( 36 | color: !isDarkMode ? Colors.black : Colors.white, fontSize: 15.0, fontWeight: FontWeight.bold), 37 | ), 38 | ); 39 | } 40 | 41 | Widget buildIcon(RankData rankData, bool isDarkMode) { 42 | if (rankData == null) { 43 | return Padding( 44 | padding: EdgeInsets.only(left: 8), 45 | child: Text( 46 | "--", 47 | style: TextStyle( 48 | color: !isDarkMode ? Colors.black : Colors.white.withAlpha(120), fontSize: 15.0, fontWeight: FontWeight.bold), 49 | ), 50 | ); 51 | } 52 | if (rankData.rank == 1 || rankData.rank == 2 || rankData.rank == 3) { 53 | switch (rankData.rank) { 54 | case 1: 55 | return Icon(Icons.looks_one, color: Colors.yellow); 56 | break; 57 | case 2: 58 | return Icon(Icons.looks_two, color: Colors.blueGrey); 59 | break; 60 | case 3: 61 | return Icon(Icons.looks_3, color: Colors.orangeAccent); 62 | break; 63 | } 64 | } else { 65 | return Padding( 66 | padding: EdgeInsets.only(left: 8), 67 | child: Text( 68 | rankData.rank.toString(), 69 | style: TextStyle( 70 | color: Colors.black, fontSize: 15.0, fontWeight: FontWeight.bold), 71 | ), 72 | ); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/widget/marquee_widget.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | //跑马灯效果 参考 https://github.com/baoolong/MarqueeWidget/blob/master/lib/marquee_flutter.dart 6 | class MarqueeWidget extends StatefulWidget { 7 | var width = 200.0; 8 | var height = 20.0; 9 | TextStyle style; 10 | String text; 11 | 12 | MarqueeWidget({this.width, this.height, @required this.text, style}) 13 | : style = style == null ? TextStyle(fontSize: 16) : style; 14 | 15 | @override 16 | State createState() { 17 | return _MarqueeWidgetState(); 18 | } 19 | } 20 | 21 | class _MarqueeWidgetState extends State { 22 | var _controller = ScrollController(); 23 | double position = 0.0; 24 | Timer timer; 25 | 26 | @override 27 | void initState() { 28 | super.initState(); 29 | if (getCenterWidgetWidth() > widget.width) { 30 | timer = Timer.periodic(Duration(milliseconds: 100), (timer) { 31 | double pixels = _controller.position.pixels; 32 | double maxScrollExtent = _controller.position.maxScrollExtent; 33 | if (pixels + 3.0 >= maxScrollExtent) { 34 | position = 0; 35 | _controller.jumpTo(position); 36 | } 37 | position += 3.0; 38 | _controller.animateTo(position, 39 | duration: new Duration(milliseconds: 90), curve: Curves.linear); 40 | }); 41 | } 42 | } 43 | 44 | Widget getStartEndWidget() { 45 | return Container( 46 | width: widget.width, 47 | ); 48 | } 49 | 50 | Widget getCenterWidget() { 51 | return Align( 52 | alignment: Alignment.center, 53 | child: Text( 54 | widget.text, 55 | maxLines: 1, 56 | textAlign: TextAlign.center, 57 | style: widget.style, 58 | ), 59 | ); 60 | } 61 | 62 | double getCenterWidgetWidth() { 63 | return widget.text.length * widget.style.fontSize; 64 | } 65 | 66 | @override 67 | Widget build(BuildContext context) { 68 | return Center( 69 | child: getCenterWidgetWidth() > widget.width 70 | ? ListView( 71 | physics: new NeverScrollableScrollPhysics(), 72 | scrollDirection: Axis.horizontal, 73 | controller: _controller, 74 | children: [ 75 | getStartEndWidget(), 76 | getCenterWidget(), 77 | getStartEndWidget(), 78 | ], 79 | ) 80 | : Text( 81 | widget.text, 82 | maxLines: 1, 83 | style: widget.style, 84 | ), 85 | ); 86 | } 87 | 88 | @override 89 | void dispose() { 90 | super.dispose(); 91 | if (timer != null) { 92 | timer.cancel(); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /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 | 25 | 26 | apply plugin: 'com.android.application' 27 | apply plugin: 'kotlin-android' 28 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 29 | 30 | def keystorePropertiesFile = rootProject.file("key.properties") 31 | def keystoreProperties = new Properties() 32 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 33 | 34 | android { 35 | compileSdkVersion 28 36 | 37 | sourceSets { 38 | main.java.srcDirs += 'src/main/kotlin' 39 | } 40 | 41 | lintOptions { 42 | disable 'InvalidPackage' 43 | } 44 | 45 | defaultConfig { 46 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 47 | applicationId "com.example.wanandroidflutter" 48 | minSdkVersion 16 49 | targetSdkVersion 28 50 | versionCode flutterVersionCode.toInteger() 51 | versionName flutterVersionName 52 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 53 | } 54 | 55 | signingConfigs { 56 | release { 57 | keyAlias keystoreProperties['keyAlias'] 58 | keyPassword keystoreProperties['keyPassword'] 59 | storeFile file(keystoreProperties['storeFile']) 60 | storePassword keystoreProperties['storePassword'] 61 | } 62 | } 63 | 64 | buildTypes { 65 | release { 66 | // TODO: Add your own signing config for the release build. 67 | // Signing with the debug keys for now, so `flutter run --release` works. 68 | signingConfig signingConfigs.release 69 | } 70 | } 71 | } 72 | 73 | flutter { 74 | source '../..' 75 | } 76 | 77 | dependencies { 78 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 79 | testImplementation 'junit:junit:4.12' 80 | androidTestImplementation 'androidx.test:runner:1.1.1' 81 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 82 | } 83 | -------------------------------------------------------------------------------- /lib/widget/title_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | import 'package:wanandroidflutter/theme/theme_model.dart'; 4 | 5 | import 'marquee_widget.dart'; 6 | 7 | class TitleBar extends StatefulWidget implements PreferredSizeWidget { 8 | Widget leftButton; 9 | String title = ""; 10 | bool isShowBack = true; 11 | 12 | TitleBar({this.leftButton, this.isShowBack, this.title}); 13 | 14 | @override 15 | _TitleBarState createState() => _TitleBarState(); 16 | 17 | static Widget textButton( 18 | String text, { 19 | Color color = Colors.white, 20 | Function() press, 21 | }) { 22 | return InkWell( 23 | child: Container( 24 | margin: EdgeInsets.only(right: 5), 25 | padding: EdgeInsets.all(5), 26 | child: Text( 27 | text, 28 | style: TextStyle(color: color), 29 | ), 30 | ), 31 | onTap: press, 32 | ); 33 | } 34 | 35 | static Widget iconButton( 36 | {IconData icon, Color color = Colors.white, Function() press}) { 37 | return IconButton( 38 | padding: EdgeInsets.all(2), 39 | icon: Icon( 40 | icon, 41 | color: color, 42 | ), 43 | onPressed: press); 44 | } 45 | 46 | @override 47 | Size get preferredSize => Size.fromHeight(56.0); 48 | } 49 | 50 | class _TitleBarState extends State { 51 | @override 52 | Widget build(BuildContext context) { 53 | var appTheme = Provider.of(context); 54 | return Container( 55 | padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top), 56 | decoration: BoxDecoration( 57 | gradient: LinearGradient( 58 | begin: Alignment.centerLeft, 59 | end: Alignment.centerRight, 60 | colors: [appTheme.themeColor.withAlpha(180), appTheme.themeColor])), 61 | child: Stack( 62 | children: [ 63 | Offstage( 64 | offstage: !widget.isShowBack, 65 | child: Container( 66 | alignment: Alignment.centerLeft, 67 | child: widget.leftButton == null 68 | ? IconButton( 69 | icon: Icon( 70 | Icons.arrow_back, 71 | color: Colors.white, 72 | ), 73 | onPressed: () { 74 | Navigator.of(context).pop(); 75 | }, 76 | ) 77 | : widget.leftButton, 78 | ), 79 | ), 80 | Container( 81 | margin: EdgeInsets.symmetric(horizontal: 50), 82 | child: Center( 83 | child: MarqueeWidget( 84 | text: widget.title, 85 | height: 56, 86 | width: MediaQuery.of(context).size.width - 90, 87 | style: TextStyle(fontSize: 18, color: Colors.white), 88 | ), 89 | ), 90 | ), 91 | ], 92 | ), 93 | ); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/main_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | import 'package:wanandroidflutter/application.dart'; 4 | import 'package:wanandroidflutter/constant/Constants.dart'; 5 | import 'package:wanandroidflutter/generated/l10n.dart'; 6 | import 'package:wanandroidflutter/theme/font_model.dart'; 7 | import 'package:wanandroidflutter/theme/locale_model.dart'; 8 | import 'package:wanandroidflutter/theme/theme_colors.dart'; 9 | import 'package:wanandroidflutter/page/tabs.dart'; 10 | import 'package:wanandroidflutter/routes/routes.dart'; 11 | import 'package:wanandroidflutter/theme/dark_model.dart'; 12 | import 'package:wanandroidflutter/theme/theme_model.dart'; 13 | import 'package:wanandroidflutter/utils/Config.dart'; 14 | import 'package:flutter_localizations/flutter_localizations.dart'; 15 | 16 | 17 | class MainPage extends StatefulWidget { 18 | @override 19 | _MainPageState createState() => _MainPageState(); 20 | } 21 | 22 | class _MainPageState extends State { 23 | @override 24 | void initState() { 25 | super.initState(); 26 | queryThemeColor().then((index) { 27 | Provider.of(context).updateThemeColor(getThemeColors()[index]); 28 | }); 29 | queryDark().then((value) { 30 | Provider.of(context).updateDarkMode(value); 31 | if (value) { 32 | Provider.of(context).updateThemeColor(Color(0xff323638)); 33 | } 34 | }); 35 | queryFontIndex().then((index) { 36 | Provider.of(context).updateFontIndex(index); 37 | }); 38 | } 39 | 40 | queryThemeColor() async { 41 | int themeColorIndex = Application.sp.getInt(Config.SP_THEME_COLOR) ?? 0; 42 | return themeColorIndex; 43 | } 44 | 45 | /// 查询暗黑模式 46 | queryDark() async { 47 | bool isDark = Application.sp.getBool(Config.SP_DARK_MODEL) ?? false; 48 | return isDark; 49 | } 50 | 51 | /// 查询字体模式 52 | queryFontIndex() async { 53 | int fontIndex = Application.sp.getInt(Config.SP_FONT_INDEX) ?? 0; 54 | return fontIndex; 55 | } 56 | 57 | 58 | @override 59 | Widget build(BuildContext context) { 60 | var appTheme = Provider.of(context); 61 | var darkMode = Provider.of(context); 62 | var fontMode = Provider.of(context); 63 | var localeMode = Provider.of(context); 64 | return MaterialApp( 65 | theme: getTheme(appTheme.themeColor, isDarkMode: darkMode.isDark, fontIndex: fontMode.fontIndex), 66 | locale: localeMode.locale, 67 | localizationsDelegates: const [ 68 | S.delegate, 69 | GlobalMaterialLocalizations.delegate, 70 | GlobalCupertinoLocalizations.delegate, 71 | GlobalWidgetsLocalizations.delegate 72 | ], 73 | // 设置中文为首选项 74 | supportedLocales: [const Locale('zh', ''), ...S.delegate.supportedLocales], 75 | home: Tabs(), 76 | initialRoute: '/', 77 | onGenerateRoute: onGenerateRoute, 78 | ); 79 | } 80 | 81 | getTheme(Color themeColor, {bool isDarkMode = false, int fontIndex = 0}) { 82 | return ThemeData( 83 | fontFamily: Constants.FontList[fontIndex], 84 | brightness: isDarkMode ? Brightness.dark : Brightness.light, 85 | ); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /lib/utils/sp_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:shared_preferences/shared_preferences.dart'; 2 | 3 | class SpUtil { 4 | static SpUtil _instance; 5 | 6 | static Future get instance async { 7 | return await getInstance(); 8 | } 9 | 10 | static SharedPreferences _sp; 11 | 12 | SpUtil._internal(); 13 | 14 | Future _init() async { 15 | _sp = await SharedPreferences.getInstance(); 16 | } 17 | 18 | static Future getInstance() async { 19 | if (_instance == null) { 20 | _instance = SpUtil._internal(); 21 | } 22 | if (_sp == null) { 23 | await _instance._init(); 24 | } 25 | return _instance; 26 | } 27 | 28 | static bool _beforeCheck() { 29 | if (_sp == null) { 30 | return true; 31 | } 32 | return false; 33 | } 34 | 35 | bool hasKey(String key) { 36 | Set keys = getKeys(); 37 | return keys.contains(key); 38 | } 39 | 40 | Set getKeys() { 41 | if (_beforeCheck()) { 42 | return null; 43 | } 44 | return _sp.getKeys(); 45 | } 46 | 47 | get(String key) { 48 | return _sp.get(key); 49 | } 50 | 51 | getString(String key) { 52 | if (_beforeCheck()) { 53 | return null; 54 | } 55 | return _sp.getString(key); 56 | } 57 | 58 | Future putString(String key, String value) async { 59 | if (_beforeCheck()) { 60 | return null; 61 | } 62 | return _sp.setString(key, value); 63 | } 64 | 65 | getInt(String key) { 66 | if (_beforeCheck()) { 67 | return null; 68 | } 69 | return _sp.getInt(key); 70 | } 71 | 72 | Future putInt(String key, int value) async { 73 | if (_beforeCheck()) { 74 | return null; 75 | } 76 | return _sp.setInt(key, value); 77 | } 78 | 79 | bool getBool(String key) { 80 | if (_beforeCheck()) { 81 | return null; 82 | } 83 | return _sp.getBool(key); 84 | } 85 | 86 | Future putBool(String key, bool value) { 87 | if (_beforeCheck()) { 88 | return null; 89 | } 90 | return _sp.setBool(key, value); 91 | } 92 | 93 | double getDouble(String key) { 94 | if (_beforeCheck()) { 95 | return null; 96 | } 97 | return _sp.getDouble(key); 98 | } 99 | 100 | Future putDouble(String key, double value) { 101 | if (_beforeCheck()) { 102 | return null; 103 | } 104 | return _sp.setDouble(key, value); 105 | } 106 | 107 | List getStringList(String key) { 108 | return _sp.getStringList(key); 109 | } 110 | 111 | Future putStringList(String key, List value) { 112 | if (_beforeCheck()) { 113 | return null; 114 | } 115 | return _sp.setStringList(key, value); 116 | } 117 | 118 | dynamic getDynamic(String key) { 119 | if (_beforeCheck()) { 120 | return null; 121 | } 122 | return _sp.get(key); 123 | } 124 | 125 | Future remove(String key) { 126 | if (_beforeCheck()) { 127 | return null; 128 | } 129 | return _sp.remove(key); 130 | } 131 | 132 | Future clear() { 133 | if (_beforeCheck()) { 134 | return null; 135 | } 136 | return _sp.clear(); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /lib/utils/common.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'dart:math'; 4 | 5 | import 'package:flutter/cupertino.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:fluttertoast/fluttertoast.dart'; 8 | import 'package:path_provider/path_provider.dart'; 9 | 10 | class CommonUtils { 11 | //Dialog 封装 12 | static void showAlertDialog(BuildContext context, String contentText, 13 | {Function confirmCallback, 14 | Function dismissCallback, 15 | String cancelText = "", 16 | String confirmText = ""}) async { 17 | return showDialog( 18 | context: context, 19 | barrierDismissible: true, // user must tap button! 20 | builder: (BuildContext context) { 21 | return AlertDialog( 22 | content: Text(contentText), 23 | actions: [ 24 | FlatButton( 25 | child: Text(cancelText), 26 | onPressed: () { 27 | if (dismissCallback != null) { 28 | dismissCallback(); 29 | } 30 | Navigator.of(context).pop(); 31 | }, 32 | ), 33 | FlatButton( 34 | child: Text(confirmText == "" ? '注销' : confirmText), 35 | onPressed: () { 36 | if (confirmCallback != null) { 37 | confirmCallback(); 38 | } 39 | Navigator.of(context).pop(); 40 | }, 41 | ) 42 | ], 43 | elevation: 20, //阴影 44 | ); 45 | }, 46 | ); 47 | } 48 | 49 | //清除缓存 50 | static void clearCache() async { 51 | Directory tempDir = await getTemporaryDirectory(); 52 | //删除缓存目录 53 | await delDir(tempDir); 54 | //清除图片缓存 55 | PaintingBinding.instance.imageCache.clear(); 56 | //await loadCache(); 57 | toast("清除缓存成功"); 58 | } 59 | 60 | ///递归方式删除目录 61 | static Future delDir(FileSystemEntity file) async { 62 | if (file is Directory) { 63 | final List children = file.listSync(); 64 | for (final FileSystemEntity child in children) { 65 | await delDir(child); 66 | } 67 | } 68 | await file.delete(); 69 | } 70 | 71 | static toast(String msg) { 72 | Fluttertoast.showToast(msg: msg, fontSize: 14); 73 | } 74 | 75 | static Color getRandomColor() { 76 | Random random = Random(); 77 | var temp = random.nextInt(6); 78 | List colors = [ 79 | Colors.blueAccent, 80 | Colors.grey, 81 | Colors.redAccent, 82 | Colors.purpleAccent, 83 | Colors.lightGreen, 84 | Colors.deepOrangeAccent, 85 | ]; 86 | return colors[temp]; 87 | } 88 | 89 | static Future push(BuildContext context, Widget widget) { 90 | Future result = Navigator.push( 91 | context, 92 | // PageRouteBuilder( 93 | // transitionDuration: Duration(milliseconds: 500), 94 | // pageBuilder: (BuildContext context, Animation animation, 95 | // Animation secondaryAnimatioZn) { 96 | // return new FadeTransition( 97 | // //使用渐隐渐入过渡, 98 | // opacity: animation, 99 | // child: widget, //路由B 100 | // ); 101 | // }, 102 | // )); 103 | // 使用ios的转场动画 104 | CupertinoPageRoute( 105 | builder: (context) => widget, 106 | )); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:event_bus/event_bus.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/services.dart'; 5 | import 'package:fluttertoast/fluttertoast.dart'; 6 | import 'package:permission_handler/permission_handler.dart'; 7 | import 'package:provider/provider.dart'; 8 | import 'package:wanandroidflutter/application.dart'; 9 | import 'package:wanandroidflutter/splash.dart'; 10 | import 'package:wanandroidflutter/theme/dark_model.dart'; 11 | import 'package:wanandroidflutter/theme/font_model.dart'; 12 | import 'package:wanandroidflutter/theme/locale_model.dart'; 13 | import 'package:wanandroidflutter/theme/theme_model.dart'; 14 | import 'package:wanandroidflutter/utils/sp_util.dart'; 15 | 16 | void main() async { 17 | WidgetsFlutterBinding.ensureInitialized(); 18 | Application.sp = await SpUtil.getInstance(); 19 | final appTheme = ThemeModel(); 20 | final darkMode = DarkMode(); 21 | final fontMode = FontModel(); 22 | final localeMode = LocaleModel(); 23 | if (Platform.isAndroid) { 24 | SystemUiOverlayStyle systemUiOverlayStyle = 25 | SystemUiOverlayStyle(statusBarColor: Colors.transparent); 26 | SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle); 27 | } 28 | 29 | // Permission check 30 | Future getPermission() async { 31 | //请求权限 32 | Map statuses = await [ 33 | Permission.notification, 34 | Permission.storage, 35 | ].request(); 36 | //校验权限 37 | if (statuses[Permission.notification] != PermissionStatus.granted) { 38 | Fluttertoast.showToast( 39 | msg: "需要打开通知权限", 40 | toastLength: Toast.LENGTH_SHORT, 41 | gravity: ToastGravity.CENTER, 42 | timeInSecForIosWeb: 1, 43 | backgroundColor: Colors.white, 44 | textColor: Colors.black, 45 | fontSize: 16.0); 46 | openAppSettings(); 47 | } 48 | if (statuses[Permission.storage] != PermissionStatus.granted) { 49 | Fluttertoast.showToast( 50 | msg: "需要打开允许访问存储权限", 51 | toastLength: Toast.LENGTH_SHORT, 52 | gravity: ToastGravity.CENTER, 53 | timeInSecForIosWeb: 1, 54 | backgroundColor: Colors.white, 55 | textColor: Colors.black, 56 | fontSize: 16.0); 57 | openAppSettings(); 58 | } 59 | } 60 | 61 | Future.wait([getPermission()]).then((result) { 62 | runApp( 63 | MultiProvider( 64 | // 接受监听 65 | providers: [ 66 | ChangeNotifierProvider.value(value: appTheme), 67 | ChangeNotifierProvider.value(value: darkMode), 68 | ChangeNotifierProvider.value(value: fontMode), 69 | ChangeNotifierProvider.value(value: localeMode), 70 | ], 71 | child: MyApp(), 72 | ), 73 | ); 74 | }); 75 | } 76 | 77 | class MyApp extends StatefulWidget { 78 | @override 79 | _MyAppState createState() => _MyAppState(); 80 | } 81 | 82 | class _MyAppState extends State { 83 | @override 84 | void initState() { 85 | // TODO: implement initState 86 | super.initState(); 87 | Application.eventBus = EventBus(); 88 | } 89 | 90 | @override 91 | void dispose() { 92 | // TODO: implement dispose 93 | super.dispose(); 94 | Application.eventBus.destroy(); 95 | } 96 | 97 | @override 98 | Widget build(BuildContext context) { 99 | return MaterialApp( 100 | debugShowCheckedModeBanner: true, 101 | home: SplashView(), 102 | ); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/widget/page_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | import 'package:wanandroidflutter/generated/l10n.dart'; 4 | import 'package:wanandroidflutter/theme/theme_model.dart'; 5 | 6 | import 'load_fail_widget.dart'; 7 | 8 | typedef ReloadData = Function(); 9 | 10 | //页面加载状态 11 | class PageWidget extends StatefulWidget { 12 | Widget child; 13 | PageStateController controller; 14 | ReloadData reload; 15 | int index = 2; 16 | 17 | PageWidget({this.child, controller, this.reload, this.index = 2}) 18 | : controller = controller != null ? controller : PageStateController(); 19 | 20 | @override 21 | State createState() { 22 | return _PageWidgetState(); 23 | } 24 | } 25 | 26 | class _PageWidgetState extends State { 27 | int index; 28 | VoidCallback _listener; 29 | 30 | @override 31 | void initState() { 32 | super.initState(); 33 | index = widget.index; 34 | _listener = () { 35 | setState(() { 36 | switch (widget.controller._state) { 37 | case PageState.Loading: 38 | index = 2; 39 | break; 40 | case PageState.LoadSuccess: 41 | index = 0; 42 | break; 43 | case PageState.LoadFail: 44 | index = 1; 45 | break; 46 | case PageState.NoData: 47 | index = 3; 48 | break; 49 | default: 50 | index = 2; 51 | break; 52 | } 53 | }); 54 | }; 55 | widget.controller.loadingNotifier.addListener(_listener); 56 | } 57 | 58 | @override 59 | Widget build(BuildContext context) { 60 | var appTheme = Provider.of(context); 61 | return IndexedStack( 62 | index: index, 63 | children: [ 64 | widget.child, 65 | LoadFailWidget( 66 | onTap: () { 67 | widget.controller.changeState(PageState.Loading); 68 | widget.reload(); 69 | }, 70 | ), 71 | Center( 72 | child: CircularProgressIndicator( 73 | valueColor: AlwaysStoppedAnimation(appTheme.themeColor), 74 | ), 75 | ), 76 | GestureDetector( 77 | onTap: () { 78 | widget.controller.changeState(PageState.Loading); 79 | widget.reload(); 80 | }, 81 | child: noDataWidget(), 82 | ) 83 | ], 84 | ); 85 | } 86 | 87 | Widget noDataWidget() { 88 | return Center( 89 | child: Column( 90 | mainAxisAlignment: MainAxisAlignment.center, 91 | children: [ 92 | ImageIcon( 93 | AssetImage("assets/img/load_no_data.png"), 94 | size: 50, 95 | ), 96 | SizedBox( 97 | height: 10, 98 | ), 99 | Text( 100 | S.of(context).no_data, 101 | style: Theme.of(context).textTheme.caption, 102 | ), 103 | ], 104 | ), 105 | ); 106 | } 107 | 108 | @override 109 | void dispose() { 110 | widget.controller.loadingNotifier.removeListener(_listener); 111 | super.dispose(); 112 | } 113 | } 114 | 115 | class PageStateController { 116 | //属性监听,也可以用InheritedWidget、Redux实现 117 | ValueNotifier loadingNotifier = ValueNotifier(PageState.Loading); 118 | PageState _state = PageState.Loading; 119 | 120 | void changeState(PageState state) { 121 | this._state = state; 122 | loadingNotifier.value = state; 123 | } 124 | } 125 | 126 | enum PageState { Loading, LoadSuccess, LoadFail, NoData } 127 | -------------------------------------------------------------------------------- /lib/page/wechat/wechat_fragment.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:provider/provider.dart'; 5 | import 'package:wanandroidflutter/data/wechat_tab.dart'; 6 | import 'package:wanandroidflutter/http/http_request.dart'; 7 | import 'package:wanandroidflutter/http/api.dart'; 8 | import 'package:wanandroidflutter/page/wechat/wechat_list_fragment.dart'; 9 | import 'package:wanandroidflutter/theme/theme_model.dart'; 10 | import 'package:wanandroidflutter/widget/load_fail_widget.dart'; 11 | 12 | class WeChatFragment extends StatefulWidget { 13 | @override 14 | WeChatFragmentState createState() => WeChatFragmentState(); 15 | } 16 | 17 | class WeChatFragmentState extends State 18 | with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin { 19 | TabController mTabController; 20 | List mTabDatas = []; 21 | int mCurrentIndex = 0; 22 | var mPageController = new PageController(initialPage: 0); 23 | var isPageChanged = true; 24 | bool isNetWorkError = false; 25 | 26 | @override 27 | void initState() { 28 | super.initState(); 29 | loadWeChatTab(); 30 | } 31 | 32 | void loadWeChatTab() async { 33 | HttpRequest.getInstance().get(Api.WECHAT_TAB, successCallBack: (data) { 34 | if (data != null) { 35 | List responseJson = json.decode(data); 36 | setState(() { 37 | isNetWorkError = false; 38 | mTabDatas = responseJson.map((m) => WeChatTab.fromJson(m)).toList(); 39 | mTabController = TabController( 40 | initialIndex: 0, length: mTabDatas.length, vsync: this); 41 | }); 42 | } else { 43 | setState(() { 44 | isNetWorkError = true; 45 | }); 46 | } 47 | }, errorCallBack: (code, msg) {}); 48 | } 49 | 50 | List initTabs() { 51 | List temps = []; 52 | for (var i = 0; i < mTabDatas.length; i++) { 53 | temps.add(Tab( 54 | text: mTabDatas[i].name, 55 | )); 56 | } 57 | return temps; 58 | } 59 | 60 | initPages() { 61 | List pages = List(); 62 | for (WeChatTab item in mTabDatas) { 63 | var page = WeChatListFragment(item.id); 64 | pages.add(page); 65 | } 66 | return pages; 67 | } 68 | 69 | @override 70 | Widget build(BuildContext context) { 71 | var appTheme = Provider.of(context); 72 | return DefaultTabController( 73 | length: mTabDatas.length, 74 | child: isNetWorkError 75 | ? Scaffold( 76 | body: LoadFailWidget( 77 | onTap: () { 78 | loadWeChatTab(); 79 | }, 80 | ), 81 | ) 82 | : Scaffold( 83 | appBar: AppBar( 84 | backgroundColor: appTheme.themeColor, 85 | title: TabBar( 86 | controller: mTabController, 87 | tabs: initTabs(), 88 | isScrollable: true, 89 | indicatorColor: Colors.white, 90 | labelColor: Colors.white, 91 | labelStyle: Theme.of(context).textTheme.subtitle, 92 | unselectedLabelColor: Colors.grey, 93 | unselectedLabelStyle: Theme.of(context).textTheme.caption, 94 | ), 95 | ), 96 | body: TabBarView( 97 | controller: mTabController, 98 | children: initPages(), 99 | ), 100 | ), 101 | ); 102 | } 103 | 104 | @override 105 | // TODO: implement wantKeepAlive 106 | bool get wantKeepAlive => true; 107 | } 108 | -------------------------------------------------------------------------------- /lib/page/project/project_fragment.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | import 'dart:convert'; 4 | import 'package:wanandroidflutter/data/project_tab.dart'; 5 | import 'package:wanandroidflutter/http/http_request.dart'; 6 | import 'package:wanandroidflutter/http/api.dart'; 7 | import 'package:wanandroidflutter/page/project/project_list_fragment.dart'; 8 | import 'package:wanandroidflutter/theme/theme_model.dart'; 9 | import 'package:wanandroidflutter/widget/load_fail_widget.dart'; 10 | 11 | class ProjectFragment extends StatefulWidget { 12 | @override 13 | ProjectFragmentState createState() => ProjectFragmentState(); 14 | } 15 | 16 | class ProjectFragmentState extends State 17 | with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin { 18 | TabController mTabController; 19 | List mTabDatas = []; 20 | int mCurrentIndex = 0; 21 | var mPageController = new PageController(initialPage: 0); 22 | var isPageChanged = true; 23 | bool isNetWorkError = false; 24 | 25 | @override 26 | void initState() { 27 | super.initState(); 28 | loadProjectTab(); 29 | } 30 | 31 | void loadProjectTab() async { 32 | HttpRequest.getInstance().get(Api.PROJECT_TAB, successCallBack: (data) { 33 | if (data != null) { 34 | List responseJson = json.decode(data); 35 | setState(() { 36 | isNetWorkError = false; 37 | mTabDatas = responseJson.map((m) => ProjectTab.fromJson(m)).toList(); 38 | mTabController = TabController( 39 | initialIndex: 0, length: mTabDatas.length, vsync: this); 40 | }); 41 | } else { 42 | setState(() { 43 | isNetWorkError = true; 44 | }); 45 | } 46 | }, errorCallBack: (code, msg) {}); 47 | } 48 | 49 | List initTabs() { 50 | List temps = []; 51 | for (var i = 0; i < mTabDatas.length; i++) { 52 | temps.add(Tab( 53 | text: mTabDatas[i].name, 54 | )); 55 | } 56 | return temps; 57 | } 58 | 59 | initPages() { 60 | List pages = List(); 61 | for (ProjectTab item in mTabDatas) { 62 | var page = ProjectListFragment(item.id); 63 | pages.add(page); 64 | } 65 | return pages; 66 | } 67 | 68 | @override 69 | Widget build(BuildContext context) { 70 | var appTheme = Provider.of(context); 71 | return DefaultTabController( 72 | length: mTabDatas.length, 73 | child: isNetWorkError 74 | ? Scaffold( 75 | body: LoadFailWidget( 76 | onTap: () { 77 | loadProjectTab(); 78 | }, 79 | ), 80 | ) 81 | : Scaffold( 82 | appBar: AppBar( 83 | backgroundColor: appTheme.themeColor, 84 | title: TabBar( 85 | controller: mTabController, 86 | tabs: initTabs(), 87 | isScrollable: true, 88 | indicatorColor: Colors.white, 89 | labelColor: Colors.white, 90 | labelStyle: Theme.of(context).textTheme.subtitle, 91 | unselectedLabelColor: Colors.grey, 92 | unselectedLabelStyle: Theme.of(context).textTheme.caption, 93 | ), 94 | ), 95 | body: TabBarView( 96 | controller: mTabController, 97 | children: initPages(), 98 | ), 99 | ), 100 | ); 101 | } 102 | 103 | @override 104 | // TODO: implement wantKeepAlive 105 | bool get wantKeepAlive => true; 106 | } 107 | -------------------------------------------------------------------------------- /lib/page/wenda/wenda_fragment.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_easyrefresh/easy_refresh.dart'; 5 | import 'package:provider/provider.dart'; 6 | import 'package:wanandroidflutter/data/article.dart'; 7 | import 'package:wanandroidflutter/http/api.dart'; 8 | import 'package:wanandroidflutter/http/http_request.dart'; 9 | import 'package:wanandroidflutter/theme/theme_model.dart'; 10 | import 'package:wanandroidflutter/widget/article_item.dart'; 11 | import 'package:wanandroidflutter/widget/custom_refresh.dart'; 12 | import 'package:wanandroidflutter/widget/page_widget.dart'; 13 | 14 | class WenDaFragment extends StatefulWidget { 15 | @override 16 | _WenDaFragmentState createState() => _WenDaFragmentState(); 17 | } 18 | 19 | class _WenDaFragmentState extends State 20 | with AutomaticKeepAliveClientMixin { 21 | List
wendaList = List(); 22 | int currentPage = 0; 23 | ScrollController _scrollController; 24 | PageStateController _pageStateController; 25 | GlobalKey _easyRefreshKey = 26 | new GlobalKey(); 27 | 28 | void _onRefresh(bool up) { 29 | if (up) { 30 | currentPage = 0; 31 | loadWenDaList(); 32 | } else { 33 | currentPage++; 34 | loadWenDaList(); 35 | } 36 | } 37 | 38 | @override 39 | void initState() { 40 | super.initState(); 41 | _pageStateController = PageStateController(); 42 | _scrollController = ScrollController(); 43 | loadWenDaList(); 44 | } 45 | 46 | void loadWenDaList() async { 47 | HttpRequest.getInstance().get("${Api.WENDA_LIST}$currentPage/json", 48 | successCallBack: (data) { 49 | if (currentPage == 0) { 50 | wendaList.clear(); 51 | } 52 | _easyRefreshKey.currentState.callRefreshFinish(); 53 | _easyRefreshKey.currentState.callLoadMoreFinish(); 54 | if (data != null) { 55 | _pageStateController.changeState(PageState.LoadSuccess); 56 | Map dataJson = json.decode(data); 57 | List responseJson = json.decode(json.encode(dataJson["datas"])); 58 | setState(() { 59 | wendaList 60 | .addAll(responseJson.map((m) => Article.fromJson(m)).toList()); 61 | }); 62 | if (wendaList.length == 0) { 63 | _pageStateController.changeState(PageState.NoData); 64 | } 65 | } else { 66 | _pageStateController.changeState(PageState.LoadFail); 67 | } 68 | }, errorCallBack: (code, msg) {}); 69 | } 70 | 71 | @override 72 | Widget build(BuildContext context) { 73 | var appTheme = Provider.of(context); 74 | return Scaffold( 75 | body: PageWidget( 76 | controller: _pageStateController, 77 | reload: () { 78 | loadWenDaList(); 79 | }, 80 | child: CustomRefresh( 81 | easyRefreshKey: _easyRefreshKey, 82 | onRefresh: () { 83 | _onRefresh(true); 84 | }, 85 | loadMore: () { 86 | _onRefresh(false); 87 | }, 88 | child: ListView.builder( 89 | controller: _scrollController, 90 | itemCount: wendaList.length, 91 | itemBuilder: (context, index) { 92 | return ArticleWidget(wendaList[index]); 93 | })), 94 | ), 95 | floatingActionButton: FloatingActionButton( 96 | backgroundColor: appTheme.themeColor.withAlpha(180), 97 | child: Icon(Icons.arrow_upward), 98 | onPressed: () { 99 | _scrollController.animateTo(0, 100 | duration: Duration(milliseconds: 1000), curve: Curves.linear); 101 | }), 102 | ); 103 | } 104 | 105 | @override 106 | bool get wantKeepAlive => true; 107 | } 108 | -------------------------------------------------------------------------------- /lib/data/navigation.dart: -------------------------------------------------------------------------------- 1 | class NavigationData { 2 | String name; 3 | List articles; 4 | int cid; 5 | 6 | NavigationData.fromJson(Map json) { 7 | name = json['name']; 8 | if (json['articles'] != null) { 9 | articles = new List(); 10 | (json['articles'] as List).forEach((v) { 11 | articles.add(NavigationTag.fromJson(v)); 12 | }); 13 | } 14 | cid = json['cid']; 15 | } 16 | 17 | Map toJson() { 18 | final Map data = new Map(); 19 | data['name'] = this.name; 20 | if (this.articles != null) { 21 | data['articles'] = this.articles.map((v) => v.toJson()).toList(); 22 | } 23 | data['cid'] = this.cid; 24 | return data; 25 | } 26 | } 27 | 28 | class NavigationTag { 29 | dynamic shareDate; 30 | String projectLink; 31 | String prefix; 32 | String origin; 33 | String link; 34 | String title; 35 | int type; 36 | int selfVisible; 37 | String apkLink; 38 | String envelopePic; 39 | int audit; 40 | int chapterId; 41 | int id; 42 | int courseId; 43 | String superChapterName; 44 | int publishTime; 45 | String niceShareDate; 46 | int visible; 47 | String niceDate; 48 | String author; 49 | int zan; 50 | String chapterName; 51 | int userId; 52 | List tags; 53 | int superChapterId; 54 | bool fresh; 55 | bool collect; 56 | String shareUser; 57 | String desc; 58 | 59 | NavigationTag.fromJson(Map json) { 60 | shareDate = json['shareDate']; 61 | projectLink = json['projectLink']; 62 | prefix = json['prefix']; 63 | origin = json['origin']; 64 | link = json['link']; 65 | title = json['title']; 66 | type = json['type']; 67 | selfVisible = json['selfVisible']; 68 | apkLink = json['apkLink']; 69 | envelopePic = json['envelopePic']; 70 | audit = json['audit']; 71 | chapterId = json['chapterId']; 72 | id = json['id']; 73 | courseId = json['courseId']; 74 | superChapterName = json['superChapterName']; 75 | publishTime = json['publishTime']; 76 | niceShareDate = json['niceShareDate']; 77 | visible = json['visible']; 78 | niceDate = json['niceDate']; 79 | author = json['author']; 80 | zan = json['zan']; 81 | chapterName = json['chapterName']; 82 | userId = json['userId']; 83 | if (json['tags'] != null) { 84 | tags = new List(); 85 | } 86 | superChapterId = json['superChapterId']; 87 | fresh = json['fresh']; 88 | collect = json['collect']; 89 | shareUser = json['shareUser']; 90 | desc = json['desc']; 91 | } 92 | 93 | Map toJson() { 94 | final Map data = new Map(); 95 | data['shareDate'] = this.shareDate; 96 | data['projectLink'] = this.projectLink; 97 | data['prefix'] = this.prefix; 98 | data['origin'] = this.origin; 99 | data['link'] = this.link; 100 | data['title'] = this.title; 101 | data['type'] = this.type; 102 | data['selfVisible'] = this.selfVisible; 103 | data['apkLink'] = this.apkLink; 104 | data['envelopePic'] = this.envelopePic; 105 | data['audit'] = this.audit; 106 | data['chapterId'] = this.chapterId; 107 | data['id'] = this.id; 108 | data['courseId'] = this.courseId; 109 | data['superChapterName'] = this.superChapterName; 110 | data['publishTime'] = this.publishTime; 111 | data['niceShareDate'] = this.niceShareDate; 112 | data['visible'] = this.visible; 113 | data['niceDate'] = this.niceDate; 114 | data['author'] = this.author; 115 | data['zan'] = this.zan; 116 | data['chapterName'] = this.chapterName; 117 | data['userId'] = this.userId; 118 | if (this.tags != null) { 119 | data['tags'] = []; 120 | } 121 | data['superChapterId'] = this.superChapterId; 122 | data['fresh'] = this.fresh; 123 | data['collect'] = this.collect; 124 | data['shareUser'] = this.shareUser; 125 | data['desc'] = this.desc; 126 | return data; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /lib/page/home/search_result_fragment.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_easyrefresh/easy_refresh.dart'; 5 | import 'package:provider/provider.dart'; 6 | import 'package:wanandroidflutter/data/article.dart'; 7 | import 'package:wanandroidflutter/http/api.dart'; 8 | import 'package:wanandroidflutter/http/http_request.dart'; 9 | import 'package:wanandroidflutter/theme/theme_model.dart'; 10 | import 'package:wanandroidflutter/widget/article_item.dart'; 11 | import 'package:wanandroidflutter/widget/custom_refresh.dart'; 12 | import 'package:wanandroidflutter/widget/page_widget.dart'; 13 | 14 | class SearchResultFragment extends StatefulWidget { 15 | String keyWord = ""; 16 | 17 | SearchResultFragment(this.keyWord); 18 | 19 | @override 20 | _SearchResultFragmentState createState() => _SearchResultFragmentState(); 21 | } 22 | 23 | class _SearchResultFragmentState extends State 24 | with AutomaticKeepAliveClientMixin { 25 | int currentPage = 0; 26 | List
searchResultList = List(); 27 | ScrollController _scrollController; 28 | PageStateController _pageStateController; 29 | 30 | GlobalKey _easyRefreshKey = 31 | new GlobalKey(); 32 | 33 | @override 34 | void initState() { 35 | super.initState(); 36 | _scrollController = ScrollController(); 37 | _pageStateController = PageStateController(); 38 | loadSearchList(); 39 | } 40 | 41 | void loadSearchList() { 42 | var data = {'k': widget.keyWord}; 43 | HttpRequest.getInstance().post("${Api.SEARCH_RESULT_LIST}$currentPage/json", 44 | data: data, successCallBack: (data) { 45 | if (currentPage == 0) { 46 | searchResultList.clear(); 47 | } 48 | _easyRefreshKey.currentState.callRefreshFinish(); 49 | _easyRefreshKey.currentState.callLoadMoreFinish(); 50 | if (data != null) { 51 | _pageStateController.changeState(PageState.LoadSuccess); 52 | Map dataJson = json.decode(data); 53 | List responseJson = json.decode(json.encode(dataJson["datas"])); 54 | setState(() { 55 | searchResultList 56 | .addAll(responseJson.map((m) => Article.fromJson(m)).toList()); 57 | }); 58 | if (searchResultList.length == 0) { 59 | _pageStateController.changeState(PageState.NoData); 60 | } 61 | } else { 62 | _pageStateController.changeState(PageState.LoadFail); 63 | } 64 | }, errorCallBack: (code, msg) {}); 65 | } 66 | 67 | void _onRefresh(bool up) { 68 | if (up) { 69 | currentPage = 0; 70 | loadSearchList(); 71 | } else { 72 | currentPage++; 73 | loadSearchList(); 74 | } 75 | } 76 | 77 | @override 78 | Widget build(BuildContext context) { 79 | var appTheme = Provider.of(context); 80 | return Scaffold( 81 | body: PageWidget( 82 | controller: _pageStateController, 83 | reload: () { 84 | loadSearchList(); 85 | }, 86 | child: CustomRefresh( 87 | easyRefreshKey: _easyRefreshKey, 88 | onRefresh: () { 89 | _onRefresh(true); 90 | }, 91 | loadMore: () { 92 | _onRefresh(false); 93 | }, 94 | child: ListView.builder( 95 | controller: _scrollController, 96 | itemCount: searchResultList.length, 97 | itemBuilder: (context, index) { 98 | return ArticleWidget(searchResultList[index]); 99 | })), 100 | ), 101 | floatingActionButton: FloatingActionButton( 102 | backgroundColor: appTheme.themeColor.withAlpha(180), 103 | child: Icon(Icons.arrow_upward), 104 | onPressed: () { 105 | _scrollController.animateTo(0, 106 | duration: Duration(milliseconds: 1000), curve: Curves.linear); 107 | }), 108 | ); 109 | } 110 | 111 | @override 112 | // TODO: implement wantKeepAlive 113 | bool get wantKeepAlive => true; 114 | } 115 | -------------------------------------------------------------------------------- /lib/page/system/knowledge/KnowledgeListFragment.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_easyrefresh/easy_refresh.dart'; 5 | import 'package:provider/provider.dart'; 6 | import 'package:wanandroidflutter/data/article.dart'; 7 | import 'package:wanandroidflutter/http/api.dart'; 8 | import 'package:wanandroidflutter/http/http_request.dart'; 9 | import 'package:wanandroidflutter/theme/theme_model.dart'; 10 | import 'package:wanandroidflutter/widget/article_item.dart'; 11 | import 'package:wanandroidflutter/widget/custom_refresh.dart'; 12 | import 'package:wanandroidflutter/widget/page_widget.dart'; 13 | 14 | class KnowledgeListFragment extends StatefulWidget { 15 | int _Id; 16 | 17 | KnowledgeListFragment(this._Id); 18 | 19 | @override 20 | _KnowledgeListFragmentState createState() => _KnowledgeListFragmentState(_Id); 21 | } 22 | 23 | class _KnowledgeListFragmentState extends State 24 | with AutomaticKeepAliveClientMixin { 25 | int _Id; 26 | int currentPage = 0; 27 | List
knowledgeList = List(); 28 | ScrollController _scrollController; 29 | PageStateController _pageStateController; 30 | 31 | _KnowledgeListFragmentState(this._Id); 32 | 33 | GlobalKey _easyRefreshKey = 34 | new GlobalKey(); 35 | 36 | void _onRefresh(bool up) { 37 | if (up) { 38 | currentPage = 0; 39 | loadKnowledgeData(); 40 | } else { 41 | currentPage++; 42 | loadKnowledgeData(); 43 | } 44 | } 45 | 46 | @override 47 | void initState() { 48 | // TODO: implement initState 49 | super.initState(); 50 | _pageStateController = PageStateController(); 51 | _scrollController = ScrollController(); 52 | loadKnowledgeData(); 53 | } 54 | 55 | void loadKnowledgeData() async { 56 | var params = {"cid": _Id}; 57 | HttpRequest.getInstance().get("${Api.HOME_ARTICLE_LIST}$currentPage/json", 58 | data: params, successCallBack: (data) { 59 | if (currentPage == 0) { 60 | knowledgeList.clear(); 61 | } 62 | _easyRefreshKey.currentState.callRefreshFinish(); 63 | _easyRefreshKey.currentState.callLoadMoreFinish(); 64 | if (data != null) { 65 | _pageStateController.changeState(PageState.LoadSuccess); 66 | Map dataJson = json.decode(data); 67 | List responseJson = json.decode(json.encode(dataJson["datas"])); 68 | setState(() { 69 | knowledgeList 70 | .addAll(responseJson.map((m) => Article.fromJson(m)).toList()); 71 | }); 72 | if (knowledgeList.length == 0) { 73 | _pageStateController.changeState(PageState.NoData); 74 | } 75 | } else { 76 | _pageStateController.changeState(PageState.LoadFail); 77 | } 78 | }, errorCallBack: (code, msg) {}); 79 | } 80 | 81 | @override 82 | Widget build(BuildContext context) { 83 | var appTheme = Provider.of(context); 84 | return Scaffold( 85 | body: PageWidget( 86 | controller: _pageStateController, 87 | reload: () { 88 | loadKnowledgeData(); 89 | }, 90 | child: CustomRefresh( 91 | easyRefreshKey: _easyRefreshKey, 92 | onRefresh: () { 93 | _onRefresh(true); 94 | }, 95 | loadMore: () { 96 | _onRefresh(false); 97 | }, 98 | child: ListView.builder( 99 | controller: _scrollController, 100 | itemCount: knowledgeList.length, 101 | itemBuilder: (context, index) { 102 | return ArticleWidget(knowledgeList[index]); 103 | })), 104 | ), 105 | floatingActionButton: FloatingActionButton( 106 | backgroundColor: appTheme.themeColor.withAlpha(180), 107 | child: Icon(Icons.arrow_upward), 108 | onPressed: () { 109 | _scrollController.animateTo(0, 110 | duration: Duration(milliseconds: 1000), curve: Curves.linear); 111 | }), 112 | ); 113 | } 114 | 115 | @override 116 | // TODO: implement wantKeepAlive 117 | bool get wantKeepAlive => true; 118 | } 119 | -------------------------------------------------------------------------------- /lib/page/rank/rank_fragment.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_easyrefresh/easy_refresh.dart'; 5 | import 'package:provider/provider.dart'; 6 | import 'package:wanandroidflutter/data/rank.dart'; 7 | import 'package:wanandroidflutter/http/api.dart'; 8 | import 'package:wanandroidflutter/http/http_request.dart'; 9 | import 'package:wanandroidflutter/theme/dark_model.dart'; 10 | import 'package:wanandroidflutter/theme/theme_model.dart'; 11 | import 'package:wanandroidflutter/widget/coin_item.dart'; 12 | import 'package:wanandroidflutter/widget/custom_refresh.dart'; 13 | import 'package:wanandroidflutter/widget/page_widget.dart'; 14 | 15 | class RankFragment extends StatefulWidget { 16 | @override 17 | _RankFragmentState createState() => _RankFragmentState(); 18 | } 19 | 20 | class _RankFragmentState extends State 21 | with AutomaticKeepAliveClientMixin { 22 | List rankList = List(); 23 | int currentPage = 1; 24 | ScrollController _scrollController; 25 | PageStateController _pageStateController; 26 | GlobalKey _easyRefreshKey = 27 | new GlobalKey(); 28 | RankData coinData; 29 | 30 | void getCoinCount() async { 31 | HttpRequest.getInstance().get(Api.COIN_INFO, successCallBack: (data) { 32 | if (data != null && data.isNotEmpty) { 33 | Map coinMap = json.decode(data); 34 | setState(() { 35 | coinData = RankData.fromJson(coinMap); 36 | }); 37 | } 38 | }, errorCallBack: (code, msg) {}); 39 | } 40 | 41 | @override 42 | void initState() { 43 | super.initState(); 44 | _pageStateController = PageStateController(); 45 | _scrollController = ScrollController(); 46 | loadRankList(); 47 | getCoinCount(); 48 | } 49 | 50 | void _onRefresh(bool up) { 51 | if (up) { 52 | currentPage = 1; 53 | loadRankList(); 54 | } else { 55 | currentPage++; 56 | loadRankList(); 57 | } 58 | } 59 | 60 | void loadRankList() async { 61 | HttpRequest.getInstance().get("${Api.RANK_LIST}$currentPage/json", 62 | successCallBack: (data) { 63 | if (currentPage == 1) { 64 | rankList.clear(); 65 | } 66 | _easyRefreshKey.currentState.callRefreshFinish(); 67 | _easyRefreshKey.currentState.callLoadMoreFinish(); 68 | if (data != null) { 69 | _pageStateController.changeState(PageState.LoadSuccess); 70 | Map dataJson = json.decode(data); 71 | List responseJson = json.decode(json.encode(dataJson["datas"])); 72 | setState(() { 73 | rankList 74 | .addAll(responseJson.map((m) => RankData.fromJson(m)).toList()); 75 | }); 76 | if (rankList.length == 0) { 77 | _pageStateController.changeState(PageState.NoData); 78 | } 79 | } else { 80 | _pageStateController.changeState(PageState.LoadFail); 81 | } 82 | }, errorCallBack: (code, msg) {}); 83 | } 84 | 85 | @override 86 | Widget build(BuildContext context) { 87 | var isDarkMode = Provider.of(context).isDark; 88 | return Scaffold( 89 | body: Stack(children: [ 90 | PageWidget( 91 | controller: _pageStateController, 92 | reload: () { 93 | loadRankList(); 94 | }, 95 | child: CustomRefresh( 96 | easyRefreshKey: _easyRefreshKey, 97 | onRefresh: () { 98 | _onRefresh(true); 99 | }, 100 | loadMore: () { 101 | _onRefresh(false); 102 | }, 103 | child: ListView.builder( 104 | controller: _scrollController, 105 | itemCount: rankList.length, 106 | itemBuilder: (context, index) { 107 | return CoinRankWidget(rankList[index]); 108 | })), 109 | ), 110 | Align( 111 | alignment: Alignment.bottomCenter, 112 | child: Container( 113 | child: CoinRankWidget(coinData), 114 | color: isDarkMode ? Color(0xff323638) : Color(0xFFCDCDCD), 115 | ), 116 | ) 117 | ])); 118 | } 119 | 120 | @override 121 | bool get wantKeepAlive => true; 122 | } 123 | -------------------------------------------------------------------------------- /lib/page/share/share_article_fragment.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_easyrefresh/easy_refresh.dart'; 5 | import 'package:provider/provider.dart'; 6 | import 'package:wanandroidflutter/application.dart'; 7 | import 'package:wanandroidflutter/data/article.dart'; 8 | import 'package:wanandroidflutter/http/api.dart'; 9 | import 'package:wanandroidflutter/http/http_request.dart'; 10 | import 'package:wanandroidflutter/theme/theme_model.dart'; 11 | import 'package:wanandroidflutter/utils/refresh_event.dart'; 12 | import 'package:wanandroidflutter/widget/custom_refresh.dart'; 13 | import 'package:wanandroidflutter/widget/page_widget.dart'; 14 | import 'package:wanandroidflutter/widget/share_item.dart'; 15 | 16 | class ShareArticleFragment extends StatefulWidget { 17 | @override 18 | _ShareArticleFragmentState createState() => _ShareArticleFragmentState(); 19 | } 20 | 21 | class _ShareArticleFragmentState extends State 22 | with AutomaticKeepAliveClientMixin { 23 | int currentPage = 1; 24 | List
shareArticleList = List(); 25 | ScrollController _scrollController; 26 | PageStateController _pageStateController; 27 | GlobalKey _easyRefreshKey = 28 | new GlobalKey(); 29 | 30 | void _onRefresh(bool up) { 31 | if (up) { 32 | currentPage = 1; 33 | loadShareArticleList(); 34 | } else { 35 | currentPage++; 36 | loadShareArticleList(); 37 | } 38 | } 39 | 40 | @override 41 | void initState() { 42 | // TODO: implement initState 43 | super.initState(); 44 | _pageStateController = PageStateController(); 45 | _scrollController = ScrollController(); 46 | Application.eventBus.on().listen((event) { 47 | setState(() { 48 | _onRefresh(true); 49 | }); 50 | }); 51 | loadShareArticleList(); 52 | } 53 | 54 | void loadShareArticleList() async { 55 | HttpRequest.getInstance().get("${Api.SHARE_ARTICLE_LIST}$currentPage/json", 56 | successCallBack: (data) { 57 | if (currentPage == 1) { 58 | shareArticleList.clear(); 59 | } 60 | _easyRefreshKey.currentState.callRefreshFinish(); 61 | _easyRefreshKey.currentState.callLoadMoreFinish(); 62 | if (data != null) { 63 | _pageStateController.changeState(PageState.LoadSuccess); 64 | Map dataJson = json.decode(data); 65 | List responseJson = 66 | json.decode(json.encode(dataJson["shareArticles"]["datas"])); 67 | print(responseJson); 68 | setState(() { 69 | shareArticleList 70 | .addAll(responseJson.map((m) => Article.fromJson(m)).toList()); 71 | }); 72 | if (shareArticleList.length == 0) { 73 | _pageStateController.changeState(PageState.NoData); 74 | } 75 | } else { 76 | _pageStateController.changeState(PageState.LoadFail); 77 | } 78 | }, errorCallBack: (code, msg) {}); 79 | } 80 | 81 | @override 82 | Widget build(BuildContext context) { 83 | var appTheme = Provider.of(context); 84 | return Scaffold( 85 | body: PageWidget( 86 | controller: _pageStateController, 87 | reload: () { 88 | loadShareArticleList(); 89 | }, 90 | child: CustomRefresh( 91 | easyRefreshKey: _easyRefreshKey, 92 | onRefresh: () { 93 | _onRefresh(true); 94 | }, 95 | loadMore: () { 96 | _onRefresh(false); 97 | }, 98 | child: ListView.builder( 99 | controller: _scrollController, 100 | itemCount: shareArticleList.length, 101 | itemBuilder: (context, index) { 102 | return ShareWidget(shareArticleList[index]); 103 | })), 104 | ), 105 | floatingActionButton: FloatingActionButton( 106 | backgroundColor: appTheme.themeColor.withAlpha(180), 107 | child: Icon(Icons.arrow_upward), 108 | onPressed: () { 109 | _scrollController.animateTo(0, 110 | duration: Duration(milliseconds: 1000), curve: Curves.linear); 111 | }), 112 | ); 113 | } 114 | 115 | @override 116 | // TODO: implement wantKeepAlive 117 | bool get wantKeepAlive => true; 118 | } 119 | -------------------------------------------------------------------------------- /lib/page/system/navigation/NavigationFragment.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:provider/provider.dart'; 5 | import 'package:wanandroidflutter/data/navigation.dart'; 6 | import 'package:wanandroidflutter/http/api.dart'; 7 | import 'package:wanandroidflutter/http/http_request.dart'; 8 | import 'package:wanandroidflutter/page/webview_page.dart'; 9 | import 'package:wanandroidflutter/theme/theme_model.dart'; 10 | import 'package:wanandroidflutter/utils/common.dart'; 11 | import 'package:wanandroidflutter/widget/load_fail_widget.dart'; 12 | 13 | class NavigationFragment extends StatefulWidget { 14 | @override 15 | _NavigationFragmentState createState() => _NavigationFragmentState(); 16 | } 17 | 18 | class _NavigationFragmentState extends State { 19 | List navigationList = []; 20 | var appTheme; 21 | bool isNetWorkError = false; 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | loadNavigationList(); 27 | } 28 | 29 | void loadNavigationList() async { 30 | HttpRequest.getInstance().get(Api.NAVIGATION, successCallBack: (data) { 31 | if (data != null) { 32 | List responseJson = json.decode(data); 33 | setState(() { 34 | isNetWorkError = false; 35 | navigationList.addAll( 36 | responseJson.map((m) => NavigationData.fromJson(m)).toList()); 37 | }); 38 | } else { 39 | setState(() { 40 | isNetWorkError = true; 41 | }); 42 | } 43 | }, errorCallBack: (code, msg) {}); 44 | } 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | appTheme = Provider.of(context); 49 | return Container( 50 | decoration: BoxDecoration( 51 | color: Theme.of(context).iconTheme.color.withAlpha(0), 52 | ), 53 | child: isNetWorkError 54 | ? Scaffold( 55 | body: LoadFailWidget( 56 | onTap: () { 57 | loadNavigationList(); 58 | }, 59 | ), 60 | ) 61 | : Scrollbar( 62 | child: ListView.builder( 63 | padding: EdgeInsets.all(15), 64 | itemCount: navigationList.length, 65 | itemBuilder: (context, index) { 66 | return NavigationCategoryWidget( 67 | navigationData: navigationList[index], 68 | ); 69 | }), 70 | ), 71 | ); 72 | } 73 | } 74 | 75 | class NavigationCategoryWidget extends StatelessWidget { 76 | final NavigationData navigationData; 77 | 78 | NavigationCategoryWidget({Key key, this.navigationData}); 79 | 80 | @override 81 | Widget build(BuildContext context) { 82 | var appTheme = Provider.of(context); 83 | return Padding( 84 | padding: EdgeInsets.symmetric(vertical: 10), 85 | child: Column( 86 | crossAxisAlignment: CrossAxisAlignment.start, 87 | children: [ 88 | Text( 89 | navigationData.name, 90 | style: Theme.of(context).textTheme.subtitle, 91 | ), 92 | Wrap( 93 | spacing: 10, 94 | children: List.generate( 95 | navigationData.articles.length, 96 | (index) => ActionChip( 97 | onPressed: () { 98 | CommonUtils.push( 99 | context, 100 | WebViewPage( 101 | url: navigationData.articles[index].link, 102 | title: navigationData.articles[index].title, 103 | id: navigationData.articles[index].id, 104 | isCollect: navigationData.articles[index].collect, 105 | )); 106 | }, 107 | backgroundColor: 108 | Theme.of(context).iconTheme.color.withAlpha(20), 109 | label: Text( 110 | navigationData.articles[index].title, 111 | maxLines: 1, 112 | style: TextStyle( 113 | fontSize: 13, color: CommonUtils.getRandomColor()), 114 | ), 115 | )), 116 | ) 117 | ], 118 | ), 119 | ); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /lib/page/system/knowledge/KnowledgeFragment.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:provider/provider.dart'; 5 | import 'package:wanandroidflutter/data/knowledge.dart'; 6 | import 'package:wanandroidflutter/http/api.dart'; 7 | import 'package:wanandroidflutter/http/http_request.dart'; 8 | import 'package:wanandroidflutter/page/system/knowledge/KnowledgeListFragment.dart'; 9 | import 'package:wanandroidflutter/theme/theme_model.dart'; 10 | import 'package:wanandroidflutter/utils/common.dart'; 11 | import 'package:wanandroidflutter/widget/load_fail_widget.dart'; 12 | 13 | class KnowledgeFragment extends StatefulWidget { 14 | @override 15 | _KnowledgeFragmentState createState() => _KnowledgeFragmentState(); 16 | } 17 | 18 | class _KnowledgeFragmentState extends State { 19 | List knowLedgeList = []; 20 | var appTheme; 21 | bool isNetWorkError = false; 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | loadKnowledgeList(); 27 | } 28 | 29 | void loadKnowledgeList() async { 30 | HttpRequest.getInstance().get(Api.TREE, successCallBack: (data) { 31 | if (data != null) { 32 | List responseJson = json.decode(data); 33 | setState(() { 34 | isNetWorkError = false; 35 | knowLedgeList.addAll( 36 | responseJson.map((m) => KnowledgeData.fromJson(m)).toList()); 37 | }); 38 | } else { 39 | setState(() { 40 | isNetWorkError = true; 41 | }); 42 | } 43 | }, errorCallBack: (code, msg) {}); 44 | } 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | appTheme = Provider.of(context); 49 | return Container( 50 | decoration: BoxDecoration( 51 | color: Theme.of(context).iconTheme.color.withAlpha(0), 52 | ), 53 | child: isNetWorkError 54 | ? Scaffold( 55 | body: LoadFailWidget( 56 | onTap: () { 57 | loadKnowledgeList(); 58 | }, 59 | ), 60 | ) 61 | : Scrollbar( 62 | child: ListView.builder( 63 | padding: EdgeInsets.all(15), 64 | itemCount: knowLedgeList.length, 65 | itemBuilder: (context, index) { 66 | return KnowledgeCategoryWidget( 67 | knowledgeData: knowLedgeList[index], 68 | ); 69 | }), 70 | ), 71 | ); 72 | } 73 | } 74 | 75 | class KnowledgeCategoryWidget extends StatelessWidget { 76 | final KnowledgeData knowledgeData; 77 | 78 | KnowledgeCategoryWidget({Key key, this.knowledgeData}); 79 | 80 | @override 81 | Widget build(BuildContext context) { 82 | var appTheme = Provider.of(context); 83 | return Padding( 84 | padding: EdgeInsets.symmetric(vertical: 10), 85 | child: Column( 86 | crossAxisAlignment: CrossAxisAlignment.start, 87 | children: [ 88 | Text( 89 | knowledgeData.name, 90 | style: Theme.of(context).textTheme.subtitle, 91 | ), 92 | Wrap( 93 | spacing: 10, 94 | children: List.generate( 95 | knowledgeData.children.length, 96 | (index) => ActionChip( 97 | onPressed: () { 98 | CommonUtils.push( 99 | context, 100 | Scaffold( 101 | appBar: AppBar( 102 | title: Text(knowledgeData.children[index].name 103 | .toString()), 104 | backgroundColor: appTheme.themeColor, 105 | centerTitle: true, 106 | ), 107 | body: KnowledgeListFragment( 108 | knowledgeData.children[index].id), 109 | )); 110 | }, 111 | backgroundColor: 112 | Theme.of(context).iconTheme.color.withAlpha(20), 113 | label: Text( 114 | knowledgeData.children[index].name, 115 | maxLines: 1, 116 | style: TextStyle( 117 | fontSize: 13, color: CommonUtils.getRandomColor()), 118 | ), 119 | )), 120 | ) 121 | ], 122 | ), 123 | ); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /lib/page/square/square_fragment.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_easyrefresh/easy_refresh.dart'; 5 | import 'package:provider/provider.dart'; 6 | import 'package:wanandroidflutter/data/article.dart'; 7 | import 'package:wanandroidflutter/http/api.dart'; 8 | import 'package:wanandroidflutter/http/http_request.dart'; 9 | import 'package:wanandroidflutter/page/input/share_fragment.dart'; 10 | import 'package:wanandroidflutter/theme/theme_model.dart'; 11 | import 'package:wanandroidflutter/utils/common.dart'; 12 | import 'package:wanandroidflutter/widget/article_item.dart'; 13 | import 'package:wanandroidflutter/widget/custom_refresh.dart'; 14 | import 'package:wanandroidflutter/widget/page_widget.dart'; 15 | 16 | class SquareFragment extends StatefulWidget { 17 | @override 18 | _SquareFragmentState createState() => _SquareFragmentState(); 19 | } 20 | 21 | class _SquareFragmentState extends State 22 | with AutomaticKeepAliveClientMixin { 23 | List
squareList = List(); 24 | int currentPage = 0; 25 | ScrollController _scrollController; 26 | PageStateController _pageStateController; 27 | GlobalKey _easyRefreshKey = 28 | new GlobalKey(); 29 | 30 | void _onRefresh(bool up) { 31 | if (up) { 32 | currentPage = 0; 33 | loadSquareList(); 34 | } else { 35 | currentPage++; 36 | loadSquareList(); 37 | } 38 | } 39 | 40 | @override 41 | void initState() { 42 | super.initState(); 43 | _pageStateController = PageStateController(); 44 | _scrollController = ScrollController(); 45 | loadSquareList(); 46 | } 47 | 48 | void loadSquareList() async { 49 | HttpRequest.getInstance().get("${Api.SQUARE_LIST}$currentPage/json", 50 | successCallBack: (data) { 51 | if (currentPage == 0) { 52 | squareList.clear(); 53 | } 54 | _easyRefreshKey.currentState.callRefreshFinish(); 55 | _easyRefreshKey.currentState.callLoadMoreFinish(); 56 | if (data != null) { 57 | _pageStateController.changeState(PageState.LoadSuccess); 58 | Map dataJson = json.decode(data); 59 | List responseJson = json.decode(json.encode(dataJson["datas"])); 60 | setState(() { 61 | squareList 62 | .addAll(responseJson.map((m) => Article.fromJson(m)).toList()); 63 | }); 64 | if (squareList.length == 0) { 65 | _pageStateController.changeState(PageState.NoData); 66 | } 67 | } else { 68 | _pageStateController.changeState(PageState.LoadFail); 69 | } 70 | }, errorCallBack: (code, msg) {}); 71 | } 72 | 73 | @override 74 | Widget build(BuildContext context) { 75 | var appTheme = Provider.of(context); 76 | return Scaffold( 77 | body: PageWidget( 78 | controller: _pageStateController, 79 | reload: () { 80 | loadSquareList(); 81 | }, 82 | child: CustomRefresh( 83 | easyRefreshKey: _easyRefreshKey, 84 | onRefresh: () { 85 | _onRefresh(true); 86 | }, 87 | loadMore: () { 88 | _onRefresh(false); 89 | }, 90 | child: ListView.builder( 91 | controller: _scrollController, 92 | itemCount: squareList.length, 93 | itemBuilder: (context, index) { 94 | return ArticleWidget(squareList[index]); 95 | })), 96 | ), 97 | floatingActionButton: FloatingActionButton( 98 | backgroundColor: appTheme.themeColor.withAlpha(180), 99 | child: Icon(Icons.add), 100 | onPressed: () { 101 | _inAddShare(); 102 | }), 103 | ); 104 | } 105 | 106 | //分享文章 107 | void _inAddShare() { 108 | showDialog( 109 | context: context, 110 | barrierDismissible: false, // user must tap button! 111 | builder: (BuildContext context) { 112 | return SimpleInputDialogLayout( 113 | isCollectArticle: false, 114 | isDIYText: true, 115 | themeText: "分享", 116 | dialogTitleText: "分享文章", 117 | confirmCallback2: (collectTitle, collectUrl) async { 118 | //收藏文章 119 | var data; 120 | data = {'title': collectTitle, 'link': collectUrl}; 121 | HttpRequest.getInstance().post(Api.SHARE_ARTICLE, data: data, 122 | successCallBack: (data) { 123 | CommonUtils.toast("分享文章成功"); 124 | _onRefresh(true); 125 | }); 126 | }, 127 | ); 128 | }); 129 | } 130 | 131 | @override 132 | bool get wantKeepAlive => true; 133 | } 134 | -------------------------------------------------------------------------------- /lib/generated/intl/messages_zh.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart 2 | // This is a library that provides messages for a zh locale. All the 3 | // messages from the main program should be duplicated here with the same 4 | // function name. 5 | 6 | // Ignore issues from commonly used lints in this file. 7 | // ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new 8 | // ignore_for_file:prefer_single_quotes,comment_references, directives_ordering 9 | // ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases 10 | // ignore_for_file:unused_import, file_names 11 | 12 | import 'package:intl/intl.dart'; 13 | import 'package:intl/message_lookup_by_library.dart'; 14 | 15 | final messages = new MessageLookup(); 16 | 17 | typedef String MessageIfAbsent(String messageStr, List args); 18 | 19 | class MessageLookup extends MessageLookupByLibrary { 20 | String get localeName => 'zh'; 21 | 22 | final messages = _notInlinedMessages(_notInlinedMessages); 23 | static _notInlinedMessages(_) => { 24 | "about" : MessageLookupByLibrary.simpleMessage("关于"), 25 | "autoBySystem" : MessageLookupByLibrary.simpleMessage("跟随系统"), 26 | "cancel" : MessageLookupByLibrary.simpleMessage("取消"), 27 | "clear_cache" : MessageLookupByLibrary.simpleMessage("清空缓存"), 28 | "clear_cache_tip" : MessageLookupByLibrary.simpleMessage("确定清除缓存吗?"), 29 | "clear_history" : MessageLookupByLibrary.simpleMessage("清空"), 30 | "collect" : MessageLookupByLibrary.simpleMessage("收藏"), 31 | "complete_refresh" : MessageLookupByLibrary.simpleMessage("刷新完成"), 32 | "confirm" : MessageLookupByLibrary.simpleMessage("确认"), 33 | "copy_link" : MessageLookupByLibrary.simpleMessage("复制链接"), 34 | "copy_tip" : MessageLookupByLibrary.simpleMessage("复制成功"), 35 | "crop_image" : MessageLookupByLibrary.simpleMessage("裁剪头像"), 36 | "down_refresh" : MessageLookupByLibrary.simpleMessage("下拉刷新"), 37 | "exit" : MessageLookupByLibrary.simpleMessage("退出"), 38 | "hot_search" : MessageLookupByLibrary.simpleMessage("热门搜索"), 39 | "input_search" : MessageLookupByLibrary.simpleMessage("请输入搜索关键字"), 40 | "integral" : MessageLookupByLibrary.simpleMessage("积分: "), 41 | "integral_rank" : MessageLookupByLibrary.simpleMessage("积分排行"), 42 | "kuaile_font" : MessageLookupByLibrary.simpleMessage("喵趣字体"), 43 | "language_setting" : MessageLookupByLibrary.simpleMessage("语言设置"), 44 | "level" : MessageLookupByLibrary.simpleMessage("排名: "), 45 | "login" : MessageLookupByLibrary.simpleMessage("登录"), 46 | "login_tip" : MessageLookupByLibrary.simpleMessage("点击头像登录"), 47 | "loginout" : MessageLookupByLibrary.simpleMessage("退出登录"), 48 | "loginoutTip" : MessageLookupByLibrary.simpleMessage("登出成功"), 49 | "me_collect" : MessageLookupByLibrary.simpleMessage("我的收藏"), 50 | "me_share" : MessageLookupByLibrary.simpleMessage("我的分享"), 51 | "my_blog" : MessageLookupByLibrary.simpleMessage("我的博客"), 52 | "network_error" : MessageLookupByLibrary.simpleMessage("网络异常,点击重试!"), 53 | "new_article" : MessageLookupByLibrary.simpleMessage("新"), 54 | "night_mode" : MessageLookupByLibrary.simpleMessage("夜间模式"), 55 | "no_data" : MessageLookupByLibrary.simpleMessage("没有数据,请重试!"), 56 | "normol_font" : MessageLookupByLibrary.simpleMessage("正常字体"), 57 | "password" : MessageLookupByLibrary.simpleMessage("请输入密码"), 58 | "rank" : MessageLookupByLibrary.simpleMessage("积分排行榜"), 59 | "refreshing" : MessageLookupByLibrary.simpleMessage("刷新中"), 60 | "register" : MessageLookupByLibrary.simpleMessage("注册"), 61 | "release_refresh" : MessageLookupByLibrary.simpleMessage("释放刷新"), 62 | "repassword" : MessageLookupByLibrary.simpleMessage("请输入密码"), 63 | "search" : MessageLookupByLibrary.simpleMessage("搜索"), 64 | "search_history" : MessageLookupByLibrary.simpleMessage("搜索记录"), 65 | "search_tip" : MessageLookupByLibrary.simpleMessage("输入字段为空"), 66 | "setting" : MessageLookupByLibrary.simpleMessage("设置"), 67 | "share_to_square" : MessageLookupByLibrary.simpleMessage("分享到广场"), 68 | "square" : MessageLookupByLibrary.simpleMessage("广场"), 69 | "switching_fonts" : MessageLookupByLibrary.simpleMessage("切换字体"), 70 | "tab_home" : MessageLookupByLibrary.simpleMessage("首页"), 71 | "tab_navigation" : MessageLookupByLibrary.simpleMessage("导航"), 72 | "tab_project" : MessageLookupByLibrary.simpleMessage("项目"), 73 | "tab_system" : MessageLookupByLibrary.simpleMessage("体系"), 74 | "tab_wechat" : MessageLookupByLibrary.simpleMessage("公众号"), 75 | "theme" : MessageLookupByLibrary.simpleMessage("主题"), 76 | "theme_choose" : MessageLookupByLibrary.simpleMessage("主题颜色选择"), 77 | "theme_tips" : MessageLookupByLibrary.simpleMessage("夜间模式下不可以更改主题嗷~"), 78 | "top" : MessageLookupByLibrary.simpleMessage("置顶"), 79 | "up_refresh" : MessageLookupByLibrary.simpleMessage("上拉刷新"), 80 | "update_time" : MessageLookupByLibrary.simpleMessage("更新时间: "), 81 | "username" : MessageLookupByLibrary.simpleMessage("请输入用户名"), 82 | "wenda" : MessageLookupByLibrary.simpleMessage("问答") 83 | }; 84 | } 85 | -------------------------------------------------------------------------------- /lib/data/article.dart: -------------------------------------------------------------------------------- 1 | class Article { 2 | int shareDate; 3 | String projectLink; 4 | String prefix; 5 | String origin; 6 | int originId; 7 | String link; 8 | String title; 9 | int type; 10 | int selfVisible; 11 | String apkLink; 12 | String envelopePic; 13 | int audit; 14 | int chapterId; 15 | int id; 16 | int courseId; 17 | String superChapterName; 18 | int publishTime; 19 | String niceShareDate; 20 | int visible; 21 | String niceDate; 22 | String author; 23 | int zan; 24 | String chapterName; 25 | int userId; 26 | List tags; 27 | int superChapterId; 28 | bool fresh; 29 | bool collect; 30 | String shareUser; 31 | String desc; 32 | 33 | Article( 34 | {this.shareDate, 35 | this.projectLink, 36 | this.prefix, 37 | this.origin, 38 | this.originId, 39 | this.link, 40 | this.title, 41 | this.type, 42 | this.selfVisible, 43 | this.apkLink, 44 | this.envelopePic, 45 | this.audit, 46 | this.chapterId, 47 | this.id, 48 | this.courseId, 49 | this.superChapterName, 50 | this.publishTime, 51 | this.niceShareDate, 52 | this.visible, 53 | this.niceDate, 54 | this.author, 55 | this.zan, 56 | this.chapterName, 57 | this.userId, 58 | this.tags, 59 | this.superChapterId, 60 | this.fresh, 61 | this.collect, 62 | this.shareUser, 63 | this.desc}); 64 | 65 | Article.fromJson(Map json) { 66 | shareDate = json['shareDate']; 67 | projectLink = json['projectLink']; 68 | prefix = json['prefix']; 69 | originId = json['originId']; 70 | origin = json['origin']; 71 | link = json['link']; 72 | title = json['title']; 73 | type = json['type']; 74 | selfVisible = json['selfVisible']; 75 | apkLink = json['apkLink']; 76 | envelopePic = json['envelopePic']; 77 | audit = json['audit']; 78 | chapterId = json['chapterId']; 79 | id = json['id']; 80 | courseId = json['courseId']; 81 | superChapterName = json['superChapterName']; 82 | publishTime = json['publishTime']; 83 | niceShareDate = json['niceShareDate']; 84 | visible = json['visible']; 85 | niceDate = json['niceDate']; 86 | author = json['author']; 87 | zan = json['zan']; 88 | chapterName = json['chapterName']; 89 | userId = json['userId']; 90 | if (json['tags'] != null) { 91 | tags = new List(); 92 | (json['tags'] as List).forEach((v) { 93 | tags.add(new ArticleTag.fromJson(v)); 94 | }); 95 | } 96 | superChapterId = json['superChapterId']; 97 | fresh = json['fresh']; 98 | collect = json['collect']; 99 | shareUser = json['shareUser']; 100 | desc = json['desc']; 101 | } 102 | 103 | Map toJson() { 104 | final Map data = new Map(); 105 | data['shareDate'] = this.shareDate; 106 | data['projectLink'] = this.projectLink; 107 | data['prefix'] = this.prefix; 108 | data['origin'] = this.origin; 109 | data['originId'] = this.originId; 110 | data['link'] = this.link; 111 | data['title'] = this.title; 112 | data['type'] = this.type; 113 | data['selfVisible'] = this.selfVisible; 114 | data['apkLink'] = this.apkLink; 115 | data['envelopePic'] = this.envelopePic; 116 | data['audit'] = this.audit; 117 | data['chapterId'] = this.chapterId; 118 | data['id'] = this.id; 119 | data['courseId'] = this.courseId; 120 | data['superChapterName'] = this.superChapterName; 121 | data['publishTime'] = this.publishTime; 122 | data['niceShareDate'] = this.niceShareDate; 123 | data['visible'] = this.visible; 124 | data['niceDate'] = this.niceDate; 125 | data['author'] = this.author; 126 | data['zan'] = this.zan; 127 | data['chapterName'] = this.chapterName; 128 | data['userId'] = this.userId; 129 | if (this.tags != null) { 130 | data['tags'] = this.tags.map((v) => v.toJson()).toList(); 131 | } 132 | data['superChapterId'] = this.superChapterId; 133 | data['fresh'] = this.fresh; 134 | data['collect'] = this.collect; 135 | data['shareUser'] = this.shareUser; 136 | data['desc'] = this.desc; 137 | return data; 138 | } 139 | 140 | static List
parseList(List list) { 141 | List
articles = List(); 142 | for (var a in list) { 143 | Article article = Article.fromJson(a); 144 | articles.add(article); 145 | } 146 | return articles; 147 | } 148 | } 149 | 150 | class ArticleTag { 151 | String name; 152 | String url; 153 | 154 | ArticleTag({this.name, this.url}); 155 | 156 | ArticleTag.fromJson(Map json) { 157 | name = json['name']; 158 | url = json['url']; 159 | } 160 | 161 | Map toJson() { 162 | final Map data = new Map(); 163 | data['name'] = this.name; 164 | data['url'] = this.url; 165 | return data; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /lib/http/http_request.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:cookie_jar/cookie_jar.dart'; 4 | import 'package:dio/dio.dart'; 5 | import 'package:dio_cookie_manager/dio_cookie_manager.dart'; 6 | import 'package:flutter/cupertino.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:wanandroidflutter/constant/Constants.dart'; 9 | import 'package:wanandroidflutter/http/api.dart'; 10 | import 'package:wanandroidflutter/http/base_response.dart'; 11 | import 'package:wanandroidflutter/page/account/login_fragment.dart'; 12 | import 'package:wanandroidflutter/utils/common.dart'; 13 | 14 | class HttpRequest { 15 | static String _baseUrl = Api.BASE_URL; 16 | static HttpRequest instance; 17 | 18 | Dio dio; 19 | BaseOptions options; 20 | 21 | CancelToken cancelToken = CancelToken(); 22 | 23 | static HttpRequest getInstance() { 24 | if (instance == null) { 25 | instance = HttpRequest(); 26 | } 27 | return instance; 28 | } 29 | 30 | HttpRequest() { 31 | options = BaseOptions( 32 | // 访问url 33 | baseUrl: _baseUrl, 34 | // 连接超时时间 35 | connectTimeout: 5000, 36 | // 响应流收到数据的间隔 37 | receiveTimeout: 15000, 38 | // http请求头 39 | headers: {"version": "1.0.0"}, 40 | // 接收响应数据的格式 41 | responseType: ResponseType.plain, 42 | ); 43 | dio = Dio(options); 44 | // 在拦截其中加入Cookie管理器 45 | dio.interceptors.add(CookieManager(CookieJar())); 46 | } 47 | 48 | get(url, 49 | {data, 50 | options, 51 | cancelToken, 52 | BuildContext context, 53 | Function successCallBack, 54 | Function errorCallBack}) async { 55 | Response response; 56 | try { 57 | response = await dio.get(url, 58 | queryParameters: data, options: options, cancelToken: cancelToken); 59 | } on DioError catch (e) { 60 | handlerError(e); 61 | successCallBack(null); 62 | } 63 | if (response.data != null) { 64 | BaseResponse baseResponse = 65 | BaseResponse.fromJson(json.decode(response.data)); 66 | if (baseResponse != null) { 67 | switch (baseResponse.errorCode) { 68 | case 0: 69 | successCallBack(jsonEncode(baseResponse.data)); 70 | break; 71 | case -1001: 72 | /// 返回-1001跳转到登录页 73 | errorCallBack(baseResponse.errorCode, baseResponse.errorMessage); 74 | if (context != null) { 75 | CommonUtils.push( 76 | context, 77 | Scaffold( 78 | body: LoginPage(), 79 | )); 80 | } 81 | break; 82 | default: 83 | errorCallBack(baseResponse.errorCode, baseResponse.errorMessage); 84 | break; 85 | } 86 | } else { 87 | errorCallBack(Constants.NETWORK_JSON_EXCEPTION, "网络数据问题"); 88 | } 89 | } else { 90 | errorCallBack(Constants.NETWORK_ERROR, "网络出错啦,请稍后重试"); 91 | } 92 | } 93 | 94 | post(url, 95 | {data, 96 | options, 97 | cancelToken, 98 | BuildContext context, 99 | Function successCallBack, 100 | Function errorCallBack}) async { 101 | Response response; 102 | try { 103 | response = await dio.post(url, 104 | queryParameters: data, options: options, cancelToken: cancelToken); 105 | } on DioError catch (e) { 106 | handlerError(e); 107 | successCallBack(null); 108 | } 109 | if (response.data != null) { 110 | BaseResponse baseResponse = 111 | BaseResponse.fromJson(json.decode(response.data)); 112 | if (baseResponse != null) { 113 | switch (baseResponse.errorCode) { 114 | case 0: 115 | successCallBack(jsonEncode(baseResponse.data)); 116 | break; 117 | case -1001: 118 | errorCallBack(baseResponse.errorCode, baseResponse.errorMessage); 119 | if (context != null) { 120 | CommonUtils.push( 121 | context, 122 | Scaffold( 123 | body: LoginPage(), 124 | )); 125 | } 126 | break; 127 | default: 128 | errorCallBack(baseResponse.errorCode, baseResponse.errorMessage); 129 | break; 130 | } 131 | } else { 132 | errorCallBack(Constants.NETWORK_JSON_EXCEPTION, "网络数据问题"); 133 | } 134 | } else { 135 | errorCallBack(Constants.NETWORK_ERROR, "网络出错啦,请稍后重试"); 136 | } 137 | } 138 | 139 | handlerError(DioError e) { 140 | if (e.type == DioErrorType.CONNECT_TIMEOUT) { 141 | print("连接超时"); 142 | } else if (e.type == DioErrorType.SEND_TIMEOUT) { 143 | print("请求超时"); 144 | } else if (e.type == DioErrorType.RECEIVE_TIMEOUT) { 145 | print("响应超时"); 146 | } else if (e.type == DioErrorType.RESPONSE) { 147 | print("响应异常"); 148 | } else if (e.type == DioErrorType.CANCEL) { 149 | print("请求取消"); 150 | } else { 151 | print("未知错误"); 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /lib/page/collect/collect_fragment.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_easyrefresh/easy_refresh.dart'; 5 | import 'package:provider/provider.dart'; 6 | import 'package:wanandroidflutter/application.dart'; 7 | import 'package:wanandroidflutter/data/article.dart'; 8 | import 'package:wanandroidflutter/http/api.dart'; 9 | import 'package:wanandroidflutter/http/http_request.dart'; 10 | import 'package:wanandroidflutter/page/input/share_fragment.dart'; 11 | import 'package:wanandroidflutter/theme/theme_model.dart'; 12 | import 'package:wanandroidflutter/utils/collect_event.dart'; 13 | import 'package:wanandroidflutter/utils/common.dart'; 14 | import 'package:wanandroidflutter/widget/collect_item.dart'; 15 | import 'package:wanandroidflutter/widget/custom_refresh.dart'; 16 | import 'package:wanandroidflutter/widget/page_widget.dart'; 17 | 18 | class CollectFragment extends StatefulWidget { 19 | @override 20 | _CollectFragmentState createState() => _CollectFragmentState(); 21 | } 22 | 23 | class _CollectFragmentState extends State 24 | with AutomaticKeepAliveClientMixin { 25 | List
collectList = List(); 26 | int currentPage = 0; 27 | ScrollController _scrollController; 28 | PageStateController _pageStateController; 29 | GlobalKey _easyRefreshKey = 30 | new GlobalKey(); 31 | 32 | void _onRefresh(bool up) { 33 | if (up) { 34 | currentPage = 0; 35 | loadCollectList(); 36 | } else { 37 | currentPage++; 38 | loadCollectList(); 39 | } 40 | } 41 | 42 | @override 43 | void initState() { 44 | super.initState(); 45 | _pageStateController = PageStateController(); 46 | _scrollController = ScrollController(); 47 | loadCollectList(); 48 | Application.eventBus.on().listen((event) { 49 | setState(() { 50 | _onRefresh(true); 51 | }); 52 | }); 53 | } 54 | 55 | void loadCollectList() async { 56 | HttpRequest.getInstance().get("${Api.COLLECT_LIST}$currentPage/json", 57 | successCallBack: (data) { 58 | if (currentPage == 0) { 59 | collectList.clear(); 60 | } 61 | _easyRefreshKey.currentState.callRefreshFinish(); 62 | _easyRefreshKey.currentState.callLoadMoreFinish(); 63 | if (data != null) { 64 | _pageStateController.changeState(PageState.LoadSuccess); 65 | Map dataJson = json.decode(data); 66 | List responseJson = json.decode(json.encode(dataJson["datas"])); 67 | setState(() { 68 | collectList 69 | .addAll(responseJson.map((m) => Article.fromJson(m)).toList()); 70 | }); 71 | if (collectList.length == 0) { 72 | _pageStateController.changeState(PageState.NoData); 73 | } 74 | } else { 75 | _pageStateController.changeState(PageState.LoadFail); 76 | } 77 | }, errorCallBack: (code, msg) {}); 78 | } 79 | 80 | @override 81 | Widget build(BuildContext context) { 82 | var appTheme = Provider.of(context); 83 | return Scaffold( 84 | body: PageWidget( 85 | controller: _pageStateController, 86 | reload: () { 87 | loadCollectList(); 88 | }, 89 | child: CustomRefresh( 90 | easyRefreshKey: _easyRefreshKey, 91 | onRefresh: () { 92 | _onRefresh(true); 93 | }, 94 | loadMore: () { 95 | _onRefresh(false); 96 | }, 97 | child: ListView.builder( 98 | controller: _scrollController, 99 | itemCount: collectList.length, 100 | itemBuilder: (context, index) { 101 | return CollectWidget(collectList[index]); 102 | })), 103 | ), 104 | floatingActionButton: FloatingActionButton( 105 | backgroundColor: appTheme.themeColor.withAlpha(180), 106 | child: Icon(Icons.add), 107 | onPressed: () { 108 | _inAddCollect(); 109 | }), 110 | ); 111 | } 112 | 113 | void _inAddCollect() { 114 | showDialog( 115 | context: context, 116 | barrierDismissible: false, // user must tap button! 117 | builder: (BuildContext context) { 118 | return SimpleInputDialogLayout( 119 | isCollectArticle: true, 120 | confirmCallback1: (collectTitle, collectAuthor, collectUrl) async { 121 | //收藏文章 122 | var data = { 123 | 'title': collectTitle, 124 | 'author': collectAuthor, 125 | 'link': collectUrl 126 | }; 127 | HttpRequest.getInstance().post(Api.ADD_COLLECT_ARTICLE, 128 | data: data, successCallBack: (data) { 129 | CommonUtils.toast("收藏文章成功"); 130 | Application.eventBus.fire(CollectEvent()); 131 | }); 132 | }, 133 | ); 134 | }); 135 | } 136 | 137 | @override 138 | bool get wantKeepAlive => true; 139 | } 140 | -------------------------------------------------------------------------------- /lib/page/wechat/wechat_list_fragment.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_easyrefresh/easy_refresh.dart'; 5 | import 'package:provider/provider.dart'; 6 | import 'package:wanandroidflutter/application.dart'; 7 | import 'package:wanandroidflutter/data/article.dart'; 8 | import 'package:wanandroidflutter/http/http_request.dart'; 9 | import 'package:wanandroidflutter/http/api.dart'; 10 | import 'package:wanandroidflutter/theme/theme_model.dart'; 11 | import 'package:wanandroidflutter/utils/collect_event.dart'; 12 | import 'package:wanandroidflutter/utils/login_event.dart'; 13 | import 'package:wanandroidflutter/utils/loginout_event.dart'; 14 | import 'package:wanandroidflutter/widget/article_item.dart'; 15 | import 'package:wanandroidflutter/widget/custom_refresh.dart'; 16 | import 'package:wanandroidflutter/widget/page_widget.dart'; 17 | 18 | class WeChatListFragment extends StatefulWidget { 19 | int _Id; 20 | 21 | WeChatListFragment(this._Id); 22 | 23 | @override 24 | WeChatListFragmentState createState() => WeChatListFragmentState(_Id); 25 | } 26 | 27 | class WeChatListFragmentState extends State 28 | with AutomaticKeepAliveClientMixin { 29 | int _Id; 30 | int currentPage = 1; 31 | List
weChatArticleList = List(); 32 | 33 | WeChatListFragmentState(this._Id); 34 | 35 | ScrollController _scrollController; 36 | PageStateController _pageStateController; 37 | 38 | bool isShowFab = false; 39 | 40 | GlobalKey _easyRefreshKey = 41 | new GlobalKey(); 42 | 43 | @override 44 | void initState() { 45 | super.initState(); 46 | _pageStateController = PageStateController(); 47 | _scrollController = ScrollController(); 48 | loadWeChatArticleList(); 49 | Application.eventBus.on().listen((event) { 50 | _onRefresh(true); 51 | }); 52 | Application.eventBus.on().listen((event) { 53 | _onRefresh(true); 54 | }); 55 | Application.eventBus.on().listen((event) { 56 | _onRefresh(true); 57 | }); 58 | initFabAnimator(); 59 | } 60 | 61 | void initFabAnimator() { 62 | _scrollController.addListener(() { 63 | if (_scrollController.offset < 200) { 64 | setState(() { 65 | isShowFab = false; 66 | }); 67 | } else if (_scrollController.offset >= 200) { 68 | setState(() { 69 | isShowFab = true; 70 | }); 71 | } 72 | }); 73 | } 74 | 75 | void loadWeChatArticleList() async { 76 | HttpRequest.getInstance().get("${Api.WECHAT_LIST}$_Id/$currentPage/json", 77 | successCallBack: (data) { 78 | if (currentPage == 1) { 79 | weChatArticleList.clear(); 80 | } 81 | _easyRefreshKey.currentState.callRefreshFinish(); 82 | _easyRefreshKey.currentState.callLoadMoreFinish(); 83 | if (data != null) { 84 | _pageStateController.changeState(PageState.LoadSuccess); 85 | Map dataJson = json.decode(data); 86 | List responseJson = json.decode(json.encode(dataJson["datas"])); 87 | print(responseJson); 88 | setState(() { 89 | weChatArticleList 90 | .addAll(responseJson.map((m) => Article.fromJson(m)).toList()); 91 | }); 92 | if (weChatArticleList.length == 0) { 93 | _pageStateController.changeState(PageState.NoData); 94 | } 95 | } else { 96 | _pageStateController.changeState(PageState.LoadFail); 97 | } 98 | }, errorCallBack: (code, msg) {}); 99 | } 100 | 101 | void _onRefresh(bool up) { 102 | if (up) { 103 | currentPage = 1; 104 | loadWeChatArticleList(); 105 | } else { 106 | currentPage++; 107 | loadWeChatArticleList(); 108 | } 109 | } 110 | 111 | @override 112 | Widget build(BuildContext context) { 113 | var appTheme = Provider.of(context); 114 | return Scaffold( 115 | body: PageWidget( 116 | controller: _pageStateController, 117 | reload: () { 118 | loadWeChatArticleList(); 119 | }, 120 | child: CustomRefresh( 121 | easyRefreshKey: _easyRefreshKey, 122 | onRefresh: () { 123 | _onRefresh(true); 124 | }, 125 | loadMore: () { 126 | _onRefresh(false); 127 | }, 128 | child: ListView.builder( 129 | controller: _scrollController, 130 | itemCount: weChatArticleList.length, 131 | itemBuilder: (context, index) { 132 | return ArticleWidget(weChatArticleList[index]); 133 | })), 134 | ), 135 | floatingActionButton: isShowFab 136 | ? FloatingActionButton( 137 | backgroundColor: appTheme.themeColor.withAlpha(180), 138 | child: Icon(Icons.arrow_upward), 139 | onPressed: () { 140 | _scrollController.animateTo(0, 141 | duration: Duration(milliseconds: 1000), 142 | curve: Curves.linear); 143 | }) 144 | : null, 145 | ); 146 | } 147 | 148 | @override 149 | // TODO: implement wantKeepAlive 150 | bool get wantKeepAlive => true; 151 | } 152 | -------------------------------------------------------------------------------- /lib/page/project/project_list_fragment.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_easyrefresh/easy_refresh.dart'; 5 | import 'package:provider/provider.dart'; 6 | import 'package:wanandroidflutter/application.dart'; 7 | import 'package:wanandroidflutter/data/article.dart'; 8 | import 'package:wanandroidflutter/http/http_request.dart'; 9 | import 'package:wanandroidflutter/http/api.dart'; 10 | import 'package:wanandroidflutter/theme/theme_model.dart'; 11 | import 'package:wanandroidflutter/utils/collect_event.dart'; 12 | import 'package:wanandroidflutter/utils/login_event.dart'; 13 | import 'package:wanandroidflutter/utils/loginout_event.dart'; 14 | import 'package:wanandroidflutter/widget/article_item.dart'; 15 | import 'package:wanandroidflutter/widget/custom_refresh.dart'; 16 | import 'package:wanandroidflutter/widget/page_widget.dart'; 17 | 18 | class ProjectListFragment extends StatefulWidget { 19 | int _Id; 20 | 21 | ProjectListFragment(this._Id); 22 | 23 | @override 24 | State createState() { 25 | return ProjectListFragmentState(_Id); 26 | } 27 | } 28 | 29 | class ProjectListFragmentState extends State 30 | with AutomaticKeepAliveClientMixin { 31 | int _Id; 32 | int currentPage = 1; 33 | List
projectArticleList = List(); 34 | 35 | ProjectListFragmentState(this._Id); 36 | 37 | ScrollController _scrollController; 38 | PageStateController _pageStateController; 39 | bool isShowFab = false; 40 | 41 | GlobalKey _easyRefreshKey = 42 | new GlobalKey(); 43 | 44 | @override 45 | void initState() { 46 | super.initState(); 47 | _pageStateController = PageStateController(); 48 | _scrollController = ScrollController(); 49 | loadprojectArticleList(); 50 | Application.eventBus.on().listen((event) { 51 | _onRefresh(true); 52 | }); 53 | Application.eventBus.on().listen((event) { 54 | _onRefresh(true); 55 | }); 56 | Application.eventBus.on().listen((event) { 57 | _onRefresh(true); 58 | }); 59 | initFabAnimator(); 60 | } 61 | 62 | void initFabAnimator() { 63 | _scrollController.addListener(() { 64 | if (_scrollController.offset < 200) { 65 | setState(() { 66 | isShowFab = false; 67 | }); 68 | } else if (_scrollController.offset >= 200) { 69 | setState(() { 70 | isShowFab = true; 71 | }); 72 | } 73 | }); 74 | } 75 | 76 | void loadprojectArticleList() async { 77 | HttpRequest.getInstance() 78 | .get("${Api.PROJECT_LIST}$currentPage/json?cid=$_Id", 79 | successCallBack: (data) { 80 | if (currentPage == 1) { 81 | projectArticleList.clear(); 82 | } 83 | _easyRefreshKey.currentState.callRefreshFinish(); 84 | _easyRefreshKey.currentState.callLoadMoreFinish(); 85 | if (data != null) { 86 | _pageStateController.changeState(PageState.LoadSuccess); 87 | Map dataJson = json.decode(data); 88 | List responseJson = json.decode(json.encode(dataJson["datas"])); 89 | print(responseJson); 90 | setState(() { 91 | projectArticleList 92 | .addAll(responseJson.map((m) => Article.fromJson(m)).toList()); 93 | }); 94 | if (projectArticleList.length == 0) { 95 | _pageStateController.changeState(PageState.NoData); 96 | } 97 | } else { 98 | _pageStateController.changeState(PageState.LoadFail); 99 | } 100 | }, errorCallBack: (code, msg) {}); 101 | } 102 | 103 | void _onRefresh(bool up) { 104 | if (up) { 105 | currentPage = 1; 106 | loadprojectArticleList(); 107 | } else { 108 | currentPage++; 109 | loadprojectArticleList(); 110 | } 111 | } 112 | 113 | @override 114 | Widget build(BuildContext context) { 115 | var appTheme = Provider.of(context); 116 | return Scaffold( 117 | body: PageWidget( 118 | controller: _pageStateController, 119 | reload: () { 120 | loadprojectArticleList(); 121 | }, 122 | child: CustomRefresh( 123 | easyRefreshKey: _easyRefreshKey, 124 | onRefresh: () { 125 | _onRefresh(true); 126 | }, 127 | loadMore: () { 128 | _onRefresh(false); 129 | }, 130 | child: ListView.builder( 131 | controller: _scrollController, 132 | itemCount: projectArticleList.length, 133 | itemBuilder: (context, index) { 134 | return ArticleWidget(projectArticleList[index]); 135 | })), 136 | ), 137 | floatingActionButton: isShowFab 138 | ? FloatingActionButton( 139 | backgroundColor: appTheme.themeColor.withAlpha(180), 140 | child: Icon(Icons.arrow_upward), 141 | onPressed: () { 142 | _scrollController.animateTo(0, 143 | duration: Duration(milliseconds: 1000), 144 | curve: Curves.linear); 145 | }) 146 | : null, 147 | ); 148 | } 149 | 150 | @override 151 | // TODO: implement wantKeepAlive 152 | bool get wantKeepAlive => true; 153 | } 154 | -------------------------------------------------------------------------------- /lib/generated/intl/messages_en.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart 2 | // This is a library that provides messages for a en locale. All the 3 | // messages from the main program should be duplicated here with the same 4 | // function name. 5 | 6 | // Ignore issues from commonly used lints in this file. 7 | // ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new 8 | // ignore_for_file:prefer_single_quotes,comment_references, directives_ordering 9 | // ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases 10 | // ignore_for_file:unused_import, file_names 11 | 12 | import 'package:intl/intl.dart'; 13 | import 'package:intl/message_lookup_by_library.dart'; 14 | 15 | final messages = new MessageLookup(); 16 | 17 | typedef String MessageIfAbsent(String messageStr, List args); 18 | 19 | class MessageLookup extends MessageLookupByLibrary { 20 | String get localeName => 'en'; 21 | 22 | final messages = _notInlinedMessages(_notInlinedMessages); 23 | static _notInlinedMessages(_) => { 24 | "about" : MessageLookupByLibrary.simpleMessage("About"), 25 | "autoBySystem" : MessageLookupByLibrary.simpleMessage("Auto"), 26 | "cancel" : MessageLookupByLibrary.simpleMessage("Cancel"), 27 | "clear_cache" : MessageLookupByLibrary.simpleMessage("ClearCache"), 28 | "clear_cache_tip" : MessageLookupByLibrary.simpleMessage("Are you sure?"), 29 | "clear_history" : MessageLookupByLibrary.simpleMessage("Clear"), 30 | "collect" : MessageLookupByLibrary.simpleMessage("Collect"), 31 | "complete_refresh" : MessageLookupByLibrary.simpleMessage("complete"), 32 | "confirm" : MessageLookupByLibrary.simpleMessage("Confirm"), 33 | "copy_link" : MessageLookupByLibrary.simpleMessage("CopyLink"), 34 | "copy_tip" : MessageLookupByLibrary.simpleMessage("CopySuccess"), 35 | "crop_image" : MessageLookupByLibrary.simpleMessage("Crop image"), 36 | "down_refresh" : MessageLookupByLibrary.simpleMessage("Down refresh"), 37 | "exit" : MessageLookupByLibrary.simpleMessage("Exit"), 38 | "hot_search" : MessageLookupByLibrary.simpleMessage("HotSearch"), 39 | "input_search" : MessageLookupByLibrary.simpleMessage("Input search key"), 40 | "integral" : MessageLookupByLibrary.simpleMessage("Integral: "), 41 | "integral_rank" : MessageLookupByLibrary.simpleMessage("ranking"), 42 | "kuaile_font" : MessageLookupByLibrary.simpleMessage("MiaoQu"), 43 | "language_setting" : MessageLookupByLibrary.simpleMessage("Language"), 44 | "level" : MessageLookupByLibrary.simpleMessage("level: "), 45 | "login" : MessageLookupByLibrary.simpleMessage("Login"), 46 | "login_tip" : MessageLookupByLibrary.simpleMessage("Click avatar login"), 47 | "loginout" : MessageLookupByLibrary.simpleMessage("LoginOut"), 48 | "loginoutTip" : MessageLookupByLibrary.simpleMessage("Loginout Success"), 49 | "me_collect" : MessageLookupByLibrary.simpleMessage("MeCollect"), 50 | "me_share" : MessageLookupByLibrary.simpleMessage("MeShare"), 51 | "my_blog" : MessageLookupByLibrary.simpleMessage("Blog"), 52 | "network_error" : MessageLookupByLibrary.simpleMessage("Network error, please try again!"), 53 | "new_article" : MessageLookupByLibrary.simpleMessage("New"), 54 | "night_mode" : MessageLookupByLibrary.simpleMessage("NightMode"), 55 | "no_data" : MessageLookupByLibrary.simpleMessage("No data, please try again!"), 56 | "normol_font" : MessageLookupByLibrary.simpleMessage("Normal"), 57 | "password" : MessageLookupByLibrary.simpleMessage("Input password"), 58 | "rank" : MessageLookupByLibrary.simpleMessage("Ranking"), 59 | "refreshing" : MessageLookupByLibrary.simpleMessage("refreshing"), 60 | "register" : MessageLookupByLibrary.simpleMessage("Register"), 61 | "release_refresh" : MessageLookupByLibrary.simpleMessage("release refresh"), 62 | "repassword" : MessageLookupByLibrary.simpleMessage("Confirm password"), 63 | "search" : MessageLookupByLibrary.simpleMessage("go"), 64 | "search_history" : MessageLookupByLibrary.simpleMessage("SearchHistory"), 65 | "search_tip" : MessageLookupByLibrary.simpleMessage("Input Empty"), 66 | "setting" : MessageLookupByLibrary.simpleMessage("Setting"), 67 | "share_to_square" : MessageLookupByLibrary.simpleMessage("Share"), 68 | "square" : MessageLookupByLibrary.simpleMessage("Square"), 69 | "stranger" : MessageLookupByLibrary.simpleMessage("Stranger"), 70 | "switching_fonts" : MessageLookupByLibrary.simpleMessage("Fonts"), 71 | "tab_home" : MessageLookupByLibrary.simpleMessage("Home"), 72 | "tab_navigation" : MessageLookupByLibrary.simpleMessage("Navigation"), 73 | "tab_project" : MessageLookupByLibrary.simpleMessage("Project"), 74 | "tab_system" : MessageLookupByLibrary.simpleMessage("System"), 75 | "tab_wechat" : MessageLookupByLibrary.simpleMessage("WX"), 76 | "theme" : MessageLookupByLibrary.simpleMessage("Theme"), 77 | "theme_choose" : MessageLookupByLibrary.simpleMessage("Theme Choose"), 78 | "theme_tips" : MessageLookupByLibrary.simpleMessage("Cannot be changed in night mode~"), 79 | "top" : MessageLookupByLibrary.simpleMessage("Top"), 80 | "up_refresh" : MessageLookupByLibrary.simpleMessage("Up refresh"), 81 | "update_time" : MessageLookupByLibrary.simpleMessage("update time: "), 82 | "username" : MessageLookupByLibrary.simpleMessage("Input username"), 83 | "wenda" : MessageLookupByLibrary.simpleMessage("Q&A") 84 | }; 85 | } 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # WanAndroid-Flutter 项目 3 | 🔥🔥🔥项目基于 Flutter 移动应用框架,采用 Dart 语言编写,打造新的WanAndroid,持续更新(♡˙♡).... 4 | [WanAndroid(github)](https://github.com/wangjianxiandev/WanAndroidFlutter) 如果项目对你有帮助,留下个star再走叭(๑• . •๑) 5 | 6 | ## 下载体验 7 | 8 | 9 | 10 | 11 |
12 | 13 | #### 密码123456 14 | ## 当前版本(2.0) 15 | - 添加自定义设置头像功能 16 | - 添加切换字体功能 17 | - 添加切换语言功能 18 | ## 项目展示 19 | 20 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200409142134382.gif) 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 | 36 | 37 | 38 | 39 | 40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 |
48 | 49 | 50 | 51 | 52 | 53 | 54 |
55 | 56 | ## 项目功能 57 | 58 | ### 首页 59 | - 首页文章列表 60 | - 首页banner 61 | - 搜索热词(包含在搜索界面) 62 | - 置顶文章 63 | ### 广场 64 | - 我的分享 65 | - 分享文章 66 | 67 | ### 知识体系 68 | - 体系数据 69 | - 知识体系下的文章 70 | 71 | ### 导航 72 | - 导航数据 73 | 74 | ### 公众号 75 | - 获取公众号列表 76 | 77 | ### 项目 78 | - 项目分类 79 | - 项目列表数据 80 | 81 | ### 登录与注册 82 | - 登录、注册功能 83 | 84 | ### 收藏 85 | - 收藏文章列表 86 | - 取消收藏 87 | 88 | ### 搜索 89 | - 首页文章搜索 90 | - 关键词搜索 91 | - 搜索历史记录 92 | 93 | ### 设置 94 | - 清除缓存 95 | - 夜间模式 96 | - 切换字体 97 | - 版本信息 98 | - 关于我们 99 | - 更换主题 100 | - 退出登录 101 | 102 | 103 | ### 特别感谢 104 | - [感谢鸿洋大神的WanAndroid网站提供开放Api](https://www.wanandroid.com/) 105 | - [参考开源项目fun_wandroid中的部分动画UI ](https://github.com/phoenixsky/fun_android_flutter) 106 | 107 | ### 开源 108 | 109 | | 第三方库 | 释义 | 110 | | --- | --- | 111 | | dio | 网络请求 | 112 | | cookie_jar | 网络Cookie | 113 | | dio_cookie_manager | 网络Cookie管理 | 114 | | flutter_easyrefresh | 下拉刷新以及上拉加载 | 115 | | event_bus | 事件总线 | 116 | | shared_preferences | 本地配置文件存储 | 117 | | webview_flutter | webview | 118 | | provider | 跨组件数据共享 | 119 | | url_launcher |唤醒第三方应用 | 120 | | flutter_swiper | 轮播 | 121 | | image_picker | 图像选择 | 122 | | image_cropper| 图像裁剪 | 123 | -------------------------------------------------------------------------------- /lib/widget/share_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:provider/provider.dart'; 4 | import 'package:wanandroidflutter/application.dart'; 5 | import 'package:wanandroidflutter/data/article.dart'; 6 | import 'package:wanandroidflutter/http/http_request.dart'; 7 | import 'package:wanandroidflutter/page/webview_page.dart'; 8 | import 'package:wanandroidflutter/theme/dark_model.dart'; 9 | import 'package:wanandroidflutter/theme/theme_model.dart'; 10 | import 'package:wanandroidflutter/utils/common.dart'; 11 | import 'package:wanandroidflutter/utils/refresh_event.dart'; 12 | 13 | import 'article_title.dart'; 14 | 15 | //文章item 16 | class ShareWidget extends StatefulWidget { 17 | Article article; 18 | 19 | ShareWidget(this.article); 20 | 21 | @override 22 | State createState() { 23 | return _ShareWidgetState(); 24 | } 25 | } 26 | 27 | class _ShareWidgetState extends State { 28 | Article article; 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | article = widget.article; 33 | var appTheme = Provider.of(context); 34 | var isDarkMode = Provider.of(context).isDark; 35 | return GestureDetector( 36 | onTap: () { 37 | CommonUtils.push( 38 | context, 39 | WebViewPage( 40 | url: article.link, 41 | title: article.title, 42 | id: article.id, 43 | isCollect: article.collect, 44 | )); 45 | }, 46 | child: Card( 47 | elevation: 15.0, 48 | shape: const RoundedRectangleBorder( 49 | borderRadius: BorderRadius.all(Radius.circular(14.0))), 50 | margin: EdgeInsets.all(5), 51 | child: Container( 52 | padding: EdgeInsets.fromLTRB(10, 10, 10, 5), 53 | child: Column( 54 | children: [ 55 | Row( 56 | children: [ 57 | Expanded( 58 | flex: 1, 59 | child: Row( 60 | children: [ 61 | Icon( 62 | article.author == "" 63 | ? Icons.folder_shared 64 | : Icons.person, 65 | size: 20.0, 66 | color: !isDarkMode 67 | ? appTheme.themeColor 68 | : Colors.white.withAlpha(120), 69 | ), 70 | Container( 71 | padding: EdgeInsets.only(left: 5), 72 | child: Text( 73 | "${article.author.isNotEmpty ? article.author : article.shareUser}", 74 | overflow: TextOverflow.ellipsis, 75 | maxLines: 1, 76 | style: Theme.of(context).textTheme.caption), 77 | ), 78 | ], 79 | )), 80 | Align( 81 | alignment: Alignment.centerRight, 82 | child: Row( 83 | children: [ 84 | Container( 85 | child: Icon( 86 | Icons.access_time, 87 | color: Theme.of(context) 88 | .iconTheme 89 | .color 90 | .withAlpha(120), 91 | size: 20, 92 | ), 93 | ), 94 | Container( 95 | padding: EdgeInsets.fromLTRB(5, 0, 5, 0), 96 | child: Text("${article.niceDate}", 97 | textAlign: TextAlign.center, 98 | style: Theme.of(context).textTheme.caption), 99 | ) 100 | ], 101 | )), 102 | ], 103 | ), 104 | Align( 105 | alignment: Alignment.centerLeft, 106 | child: Padding( 107 | padding: EdgeInsets.fromLTRB(0, 15, 0, 0), 108 | child: ArticleTitleWidget(article.title)), 109 | ), 110 | Row( 111 | children: [ 112 | Expanded( 113 | flex: 1, 114 | child: Container( 115 | alignment: Alignment.centerLeft, 116 | child: Text("${article.chapterName}", 117 | style: TextStyle( 118 | color: !isDarkMode 119 | ? appTheme.themeColor 120 | : Colors.white.withAlpha(120), 121 | fontSize: 11.0)))), 122 | Align( 123 | alignment: Alignment.centerRight, 124 | child: IconButton( 125 | alignment: Alignment.centerRight, 126 | padding: EdgeInsets.only(right: 5), 127 | icon: Icon( 128 | Icons.delete_forever, 129 | color: Colors.red, 130 | ), 131 | onPressed: () => _delete(), 132 | ), 133 | ), 134 | ], 135 | ), 136 | ], 137 | ), 138 | ), 139 | ), 140 | ); 141 | } 142 | 143 | //删除分享 144 | _delete() { 145 | String url = "lg/user_article/delete/${article.id}/json"; 146 | HttpRequest.getInstance().post(url, successCallBack: (data) { 147 | Application.eventBus.fire(RefreshEvent()); 148 | }, errorCallBack: (code, msg) {}, context: context); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /lib/widget/collect_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | import 'package:wanandroidflutter/application.dart'; 4 | import 'package:wanandroidflutter/data/article.dart'; 5 | import 'package:wanandroidflutter/generated/l10n.dart'; 6 | import 'package:wanandroidflutter/http/http_request.dart'; 7 | import 'package:wanandroidflutter/page/webview_page.dart'; 8 | import 'package:wanandroidflutter/theme/dark_model.dart'; 9 | import 'package:wanandroidflutter/theme/theme_model.dart'; 10 | import 'package:wanandroidflutter/utils/collect_event.dart'; 11 | import 'package:wanandroidflutter/utils/common.dart'; 12 | import 'package:wanandroidflutter/widget/article_title.dart'; 13 | 14 | //文章item 15 | class CollectWidget extends StatefulWidget { 16 | Article article; 17 | 18 | CollectWidget(this.article); 19 | 20 | @override 21 | State createState() { 22 | return _CollectWidgetState(); 23 | } 24 | } 25 | 26 | class _CollectWidgetState extends State { 27 | Article article; 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | var appTheme = Provider.of(context); 32 | var isDarkMode = Provider.of(context).isDark; 33 | article = widget.article; 34 | return GestureDetector( 35 | onTap: () { 36 | CommonUtils.push( 37 | context, 38 | WebViewPage( 39 | url: article.link, 40 | title: article.title, 41 | id: article.id, 42 | isCollect: article.collect, 43 | )); 44 | }, 45 | child: Card( 46 | elevation: 15.0, 47 | shape: const RoundedRectangleBorder( 48 | borderRadius: BorderRadius.all(Radius.circular(14.0))), 49 | margin: EdgeInsets.all(5), 50 | child: Container( 51 | padding: EdgeInsets.fromLTRB(10, 10, 10, 5), 52 | child: Column( 53 | children: [ 54 | Row( 55 | children: [ 56 | Expanded( 57 | flex: 1, 58 | child: Row( 59 | children: [ 60 | Icon( 61 | article.author == "" 62 | ? Icons.folder_shared 63 | : Icons.person, 64 | size: 20.0, 65 | color: !isDarkMode 66 | ? appTheme.themeColor 67 | : Colors.white.withAlpha(120), 68 | ), 69 | Container( 70 | padding: EdgeInsets.only(left: 5), 71 | child: Text( 72 | "${article.author.isNotEmpty ? article.author : S.of(context).stranger}", 73 | overflow: TextOverflow.ellipsis, 74 | maxLines: 1, 75 | style: Theme.of(context).textTheme.caption), 76 | ), 77 | ], 78 | )), 79 | Align( 80 | alignment: Alignment.centerRight, 81 | child: Row( 82 | children: [ 83 | Container( 84 | child: Icon( 85 | Icons.access_time, 86 | color: Theme.of(context) 87 | .iconTheme 88 | .color 89 | .withAlpha(120), 90 | size: 20, 91 | ), 92 | ), 93 | Container( 94 | padding: EdgeInsets.fromLTRB(5, 0, 5, 0), 95 | child: Text("${article.niceDate}", 96 | textAlign: TextAlign.center, 97 | style: Theme.of(context).textTheme.caption), 98 | ) 99 | ], 100 | )), 101 | ], 102 | ), 103 | Align( 104 | alignment: Alignment.centerLeft, 105 | child: Padding( 106 | padding: EdgeInsets.fromLTRB(0, 15, 0, 0), 107 | child: ArticleTitleWidget(article.title)), 108 | ), 109 | Row( 110 | children: [ 111 | Expanded( 112 | flex: 1, 113 | child: Container( 114 | alignment: Alignment.centerLeft, 115 | child: Text( 116 | "${article.chapterName}", 117 | style: TextStyle( 118 | color: !isDarkMode 119 | ? appTheme.themeColor 120 | : Colors.white.withAlpha(120), 121 | fontSize: 11.0), 122 | ), 123 | )), 124 | Align( 125 | alignment: Alignment.centerRight, 126 | child: IconButton( 127 | alignment: Alignment.centerRight, 128 | padding: EdgeInsets.only(right: 5), 129 | icon: Icon( 130 | Icons.delete_forever, 131 | color: Colors.red, 132 | ), 133 | onPressed: () => _delete(), 134 | ), 135 | ), 136 | ], 137 | ), 138 | ], 139 | ), 140 | ), 141 | ), 142 | ); 143 | } 144 | 145 | //取消收藏 146 | _delete() { 147 | String url = "lg/uncollect_originId/${article.originId}/json"; 148 | HttpRequest.getInstance().post(url, successCallBack: (data) { 149 | Application.eventBus.fire(CollectEvent()); 150 | }, errorCallBack: (code, msg) {}, context: context); 151 | } 152 | } 153 | --------------------------------------------------------------------------------