├── ios
├── 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
├── Flutter
│ ├── Debug.xcconfig
│ ├── Release.xcconfig
│ └── AppFrameworkInfo.plist
├── Runner.xcodeproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── WorkspaceSettings.xcsettings
│ │ └── IDEWorkspaceChecks.plist
├── RunnerTests
│ └── RunnerTests.swift
└── .gitignore
├── android
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── 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
│ │ │ │ ├── drawable
│ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── drawable-v21
│ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── values
│ │ │ │ │ └── styles.xml
│ │ │ │ └── values-night
│ │ │ │ │ └── styles.xml
│ │ │ ├── kotlin
│ │ │ │ └── com
│ │ │ │ │ └── qianyue
│ │ │ │ │ └── wan_android_flutter
│ │ │ │ │ └── MainActivity.kt
│ │ │ └── AndroidManifest.xml
│ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ └── profile
│ │ │ └── AndroidManifest.xml
│ └── build.gradle
├── .gitignore
├── build.gradle
├── settings.gradle
├── gradlew.bat
└── gradlew
├── assets
└── images
│ ├── ic_finish.png
│ ├── icon_collect.png
│ ├── icon_uncollect.png
│ └── ic_default_avatar.png
├── lib
├── constants
│ └── constants.dart
├── utils
│ ├── error_handle.dart
│ ├── log_util.dart
│ └── persistent_util.dart
├── generated
│ └── json
│ │ ├── base
│ │ ├── json_field.dart
│ │ └── json_convert_content.dart
│ │ ├── hot_keyword_entity.g.dart
│ │ ├── user_tool_entity.g.dart
│ │ ├── banner_entity.g.dart
│ │ ├── user_info_entity.g.dart
│ │ ├── project_category_entity.g.dart
│ │ ├── my_todo_data_entity.g.dart
│ │ └── my_shared_data_entity.g.dart
├── network
│ ├── bean
│ │ ├── hot_keyword_entity.dart
│ │ ├── banner_entity.dart
│ │ ├── user_tool_entity.dart
│ │ ├── user_info_entity.dart
│ │ ├── AppResponse.dart
│ │ ├── project_category_entity.dart
│ │ ├── my_todo_data_entity.dart
│ │ ├── my_shared_data_entity.dart
│ │ ├── article_data_entity.dart
│ │ └── project_list_data_entity.dart
│ ├── api.dart
│ └── request_util.dart
├── user.dart
├── base
│ └── base_page.dart
├── pages
│ ├── detail_page.dart
│ ├── setting_page.dart
│ ├── search_result_page.dart
│ ├── tabpage
│ │ ├── plaza_page.dart
│ │ ├── project_page.dart
│ │ ├── home_page.dart
│ │ └── mine_page.dart
│ ├── login_register_page.dart
│ ├── article_item_layout.dart
│ ├── my_todo_page.dart
│ ├── my_colllect_page.dart
│ ├── my_shared_page.dart
│ └── search_page.dart
└── main.dart
├── .gitignore
├── test
└── widget_test.dart
├── .metadata
├── analysis_options.yaml
├── README.md
└── pubspec.yaml
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/assets/images/ic_finish.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qianyue0317/wan_android_flutter/HEAD/assets/images/ic_finish.png
--------------------------------------------------------------------------------
/assets/images/icon_collect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qianyue0317/wan_android_flutter/HEAD/assets/images/icon_collect.png
--------------------------------------------------------------------------------
/assets/images/icon_uncollect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qianyue0317/wan_android_flutter/HEAD/assets/images/icon_uncollect.png
--------------------------------------------------------------------------------
/assets/images/ic_default_avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qianyue0317/wan_android_flutter/HEAD/assets/images/ic_default_avatar.png
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qianyue0317/wan_android_flutter/HEAD/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qianyue0317/wan_android_flutter/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/qianyue0317/wan_android_flutter/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/qianyue0317/wan_android_flutter/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/qianyue0317/wan_android_flutter/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qianyue0317/wan_android_flutter/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qianyue0317/wan_android_flutter/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qianyue0317/wan_android_flutter/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/qianyue0317/wan_android_flutter/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/qianyue0317/wan_android_flutter/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/qianyue0317/wan_android_flutter/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/qianyue0317/wan_android_flutter/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/qianyue0317/wan_android_flutter/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/qianyue0317/wan_android_flutter/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/qianyue0317/wan_android_flutter/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/qianyue0317/wan_android_flutter/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/qianyue0317/wan_android_flutter/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/qianyue0317/wan_android_flutter/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/qianyue0317/wan_android_flutter/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/qianyue0317/wan_android_flutter/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/qianyue0317/wan_android_flutter/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qianyue0317/wan_android_flutter/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/qianyue0317/wan_android_flutter/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/qianyue0317/wan_android_flutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/qianyue/wan_android_flutter/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.qianyue.wan_android_flutter
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
6 |
--------------------------------------------------------------------------------
/lib/constants/constants.dart:
--------------------------------------------------------------------------------
1 |
2 | class Constant {
3 | static bool debug = true;
4 |
5 | static const String baseUrl = "https://www.wanandroid.com/";
6 |
7 | static const int successCode = 0;
8 |
9 | static const int invalidateToken = -1001;
10 |
11 | static const int otherError = -9999;
12 | }
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/ios/RunnerTests/RunnerTests.swift:
--------------------------------------------------------------------------------
1 | import Flutter
2 | import UIKit
3 | import XCTest
4 |
5 | class RunnerTests: XCTestCase {
6 |
7 | func testExample() {
8 | // If you add code to the Runner application, consider adding tests here.
9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/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/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/lib/utils/error_handle.dart:
--------------------------------------------------------------------------------
1 |
2 |
3 | import 'dart:async';
4 |
5 | import 'package:flutter/cupertino.dart';
6 |
7 | void handleError(void Function() body) {
8 | FlutterError.onError = (FlutterErrorDetails details) {
9 | FlutterError.dumpErrorToConsole(details);
10 | };
11 |
12 | runZonedGuarded(body, (error, stack) async {
13 | await reportError(error, stack);
14 | });
15 | }
16 |
17 | Future reportError(Object error, StackTrace stack) async {
18 | // 上传报错信息
19 | }
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
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 |
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | **/dgph
2 | *.mode1v3
3 | *.mode2v3
4 | *.moved-aside
5 | *.pbxuser
6 | *.perspectivev3
7 | **/*sync/
8 | .sconsign.dblite
9 | .tags*
10 | **/.vagrant/
11 | **/DerivedData/
12 | Icon?
13 | **/Pods/
14 | **/.symlinks/
15 | profile
16 | xcuserdata
17 | **/.generated/
18 | Flutter/App.framework
19 | Flutter/Flutter.framework
20 | Flutter/Flutter.podspec
21 | Flutter/Generated.xcconfig
22 | Flutter/ephemeral/
23 | Flutter/app.flx
24 | Flutter/app.zip
25 | Flutter/flutter_assets/
26 | Flutter/flutter_export_environment.sh
27 | ServiceDefinitions.json
28 | Runner/GeneratedPluginRegistrant.*
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.7.10'
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:7.3.0'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | mavenCentral()
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 | tasks.register("clean", Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
--------------------------------------------------------------------------------
/lib/generated/json/base/json_field.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: non_constant_identifier_names
2 | // ignore_for_file: camel_case_types
3 | // ignore_for_file: prefer_single_quotes
4 |
5 | // This file is automatically generated. DO NOT EDIT, all your changes would be lost.
6 |
7 | class JsonSerializable{
8 | const JsonSerializable();
9 | }
10 |
11 | class JSONField {
12 | //Specify the parse field name
13 | final String? name;
14 |
15 | //Whether to participate in toJson
16 | final bool? serialize;
17 |
18 | //Whether to participate in fromMap
19 | final bool? deserialize;
20 |
21 | //Enumeration or not
22 | final bool? isEnum;
23 |
24 | const JSONField({this.name, this.serialize, this.deserialize, this.isEnum});
25 | }
26 |
--------------------------------------------------------------------------------
/lib/network/bean/hot_keyword_entity.dart:
--------------------------------------------------------------------------------
1 | import 'package:wan_android_flutter/generated/json/base/json_field.dart';
2 | import 'package:wan_android_flutter/generated/json/hot_keyword_entity.g.dart';
3 | import 'dart:convert';
4 | export 'package:wan_android_flutter/generated/json/hot_keyword_entity.g.dart';
5 |
6 | @JsonSerializable()
7 | class HotKeywordEntity {
8 | late int id;
9 | late String link;
10 | late String name;
11 | late int order;
12 | late int visible;
13 |
14 | HotKeywordEntity();
15 |
16 | factory HotKeywordEntity.fromJson(Map json) => $HotKeywordEntityFromJson(json);
17 |
18 | Map toJson() => $HotKeywordEntityToJson(this);
19 |
20 | @override
21 | String toString() {
22 | return jsonEncode(this);
23 | }
24 | }
--------------------------------------------------------------------------------
/lib/utils/log_util.dart:
--------------------------------------------------------------------------------
1 | import 'package:logger/logger.dart';
2 | import 'package:wan_android_flutter/constants/constants.dart';
3 |
4 | class WanLog {
5 | static const String tag = "WanLog";
6 |
7 | static Logger logger = Logger();
8 |
9 | static void i(String msg, {String tag = tag}) {
10 | if (Constant.debug) {
11 | logger.i(msg);
12 | }
13 | }
14 |
15 | static void d(String msg, {String tag = tag}) {
16 | if (Constant.debug) {
17 | logger.d(msg);
18 | }
19 | }
20 |
21 | static void w(String msg, {String tag = tag}) {
22 | if (Constant.debug) {
23 | logger.w(msg);
24 | }
25 | }
26 |
27 | static void e(String msg, {String tag = tag}) {
28 | if (Constant.debug) {
29 | logger.e(msg);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | def flutterSdkPath = {
3 | def properties = new Properties()
4 | file("local.properties").withInputStream { properties.load(it) }
5 | def flutterSdkPath = properties.getProperty("flutter.sdk")
6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
7 | return flutterSdkPath
8 | }
9 | settings.ext.flutterSdkPath = flutterSdkPath()
10 |
11 | includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
12 |
13 | plugins {
14 | id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false
15 | }
16 | }
17 |
18 | include ":app"
19 |
20 | apply from: "${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle/app_plugin_loader.gradle"
21 |
--------------------------------------------------------------------------------
/lib/network/bean/banner_entity.dart:
--------------------------------------------------------------------------------
1 | import 'package:wan_android_flutter/generated/json/base/json_field.dart';
2 | import 'package:wan_android_flutter/generated/json/banner_entity.g.dart';
3 | import 'dart:convert';
4 | export 'package:wan_android_flutter/generated/json/banner_entity.g.dart';
5 |
6 | @JsonSerializable()
7 | class BannerEntity {
8 | late String desc;
9 | late int id;
10 | late String imagePath;
11 | late int isVisible;
12 | late int order;
13 | late String title;
14 | late int type;
15 | late String url;
16 |
17 | BannerEntity();
18 |
19 | factory BannerEntity.fromJson(Map json) => $BannerEntityFromJson(json);
20 |
21 | Map toJson() => $BannerEntityToJson(this);
22 |
23 | @override
24 | String toString() {
25 | return jsonEncode(this);
26 | }
27 | }
--------------------------------------------------------------------------------
/lib/utils/persistent_util.dart:
--------------------------------------------------------------------------------
1 |
2 | import 'package:mmkv/mmkv.dart';
3 |
4 | class Persistent {
5 | var mmkv = MMKV.defaultMMKV();
6 |
7 | void encodeBool(String key, bool value) {
8 | mmkv.encodeBool(key, value);
9 | }
10 |
11 | void encodeString(String key, String value) {
12 | mmkv.encodeString(key, value);
13 | }
14 |
15 | void encodeInt(String key, int value) {
16 | mmkv.encodeInt(key, value);
17 | }
18 |
19 | bool decodeBool(String key, {bool defaultValue = false}) {
20 | return mmkv.decodeBool(key, defaultValue: defaultValue);
21 | }
22 |
23 | String? decodeString(String key) {
24 | return mmkv.decodeString(key);
25 | }
26 |
27 | int decodeInt(String key, {int defaultValue = 0}) {
28 | return mmkv.decodeInt(key, defaultValue: defaultValue);
29 | }
30 | }
--------------------------------------------------------------------------------
/lib/network/bean/user_tool_entity.dart:
--------------------------------------------------------------------------------
1 | import 'package:wan_android_flutter/generated/json/base/json_field.dart';
2 | import 'package:wan_android_flutter/generated/json/user_tool_entity.g.dart';
3 | import 'dart:convert';
4 | export 'package:wan_android_flutter/generated/json/user_tool_entity.g.dart';
5 |
6 | @JsonSerializable()
7 | class UserToolEntity {
8 | late String desc;
9 | late String icon;
10 | late int id;
11 | late String link;
12 | late String name;
13 | late int order;
14 | late int userId;
15 | late int visible;
16 |
17 | UserToolEntity();
18 |
19 | factory UserToolEntity.fromJson(Map json) => $UserToolEntityFromJson(json);
20 |
21 | Map toJson() => $UserToolEntityToJson(this);
22 |
23 | @override
24 | String toString() {
25 | return jsonEncode(this);
26 | }
27 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | migrate_working_dir/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | #.vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | **/doc/api/
26 | **/ios/Flutter/.last_build_id
27 | .dart_tool/
28 | .flutter-plugins
29 | .flutter-plugins-dependencies
30 | .packages
31 | .pub-cache/
32 | .pub/
33 | /build/
34 |
35 | # Symbolication related
36 | app.*.symbols
37 |
38 | # Obfuscation related
39 | app.*.map.json
40 |
41 | # Android Studio will place build artifacts here
42 | /android/app/debug
43 | /android/app/profile
44 | /android/app/release
45 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 11.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/lib/network/bean/user_info_entity.dart:
--------------------------------------------------------------------------------
1 | import 'package:wan_android_flutter/generated/json/base/json_field.dart';
2 | import 'package:wan_android_flutter/generated/json/user_info_entity.g.dart';
3 | import 'dart:convert';
4 | export 'package:wan_android_flutter/generated/json/user_info_entity.g.dart';
5 |
6 | @JsonSerializable()
7 | class UserInfoEntity {
8 | late bool admin;
9 | late List chapterTops;
10 | late int coinCount;
11 | late List collectIds;
12 | late String email;
13 | late String icon;
14 | late int id;
15 | late String nickname;
16 | late String password;
17 | late String publicName;
18 | late String token;
19 | late int type;
20 | late String username;
21 |
22 | UserInfoEntity();
23 |
24 | factory UserInfoEntity.fromJson(Map json) => $UserInfoEntityFromJson(json);
25 |
26 | Map toJson() => $UserInfoEntityToJson(this);
27 |
28 | @override
29 | String toString() {
30 | return jsonEncode(this);
31 | }
32 | }
--------------------------------------------------------------------------------
/lib/network/bean/AppResponse.dart:
--------------------------------------------------------------------------------
1 | import 'package:wan_android_flutter/constants/constants.dart';
2 |
3 | import '../../generated/json/base/json_convert_content.dart';
4 |
5 | class AppResponse {
6 | int errorCode = -1;
7 | String? errorMsg;
8 | T? data;
9 |
10 | AppResponse(this.errorCode, this.errorMsg, this.data);
11 |
12 | AppResponse.fromJson(Map map) {
13 | errorCode = (map['errorCode'] as int?) ?? -1;
14 | errorMsg = map['errorMsg'] as String?;
15 | if (map.containsKey('data')) {
16 | data = _generateOBJ(map['data']);
17 | }
18 | }
19 |
20 | T? _generateOBJ(Object? json) {
21 | if (json == null) {
22 | return null;
23 | }
24 | if (T.toString() == 'String') {
25 | return json.toString() as T;
26 | } else if (T.toString() == 'Map') {
27 | return json as T;
28 | } else {
29 | /// List类型数据由fromJsonAsT判断处理
30 | return JsonConvert.fromJsonAsT(json);
31 | }
32 | }
33 |
34 | bool get isSuccessful => errorCode == Constant.successCode;
35 | }
36 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/lib/network/bean/project_category_entity.dart:
--------------------------------------------------------------------------------
1 | import 'package:wan_android_flutter/generated/json/base/json_field.dart';
2 | import 'package:wan_android_flutter/generated/json/project_category_entity.g.dart';
3 | import 'dart:convert';
4 | export 'package:wan_android_flutter/generated/json/project_category_entity.g.dart';
5 |
6 | @JsonSerializable()
7 | class ProjectCategoryEntity {
8 | late List articleList;
9 | late String author;
10 | late List children;
11 | late int courseId;
12 | late String cover;
13 | late String desc;
14 | late int id;
15 | late String lisense;
16 | late String lisenseLink;
17 | late String name;
18 | late int order;
19 | late int parentChapterId;
20 | late int type;
21 | late bool userControlSetTop;
22 | late int visible;
23 |
24 | ProjectCategoryEntity();
25 |
26 | factory ProjectCategoryEntity.fromJson(Map json) => $ProjectCategoryEntityFromJson(json);
27 |
28 | Map toJson() => $ProjectCategoryEntityToJson(this);
29 |
30 | @override
31 | String toString() {
32 | return jsonEncode(this);
33 | }
34 | }
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility in the flutter_test package. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter_test/flutter_test.dart';
10 |
11 | import 'package:wan_android_flutter/main.dart';
12 |
13 | void main() {
14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
15 | // Build our app and trigger a frame.
16 | await tester.pumpWidget(const MyApp());
17 |
18 | // Verify that our counter starts at 0.
19 | expect(find.text('0'), findsOneWidget);
20 | expect(find.text('1'), findsNothing);
21 |
22 | // Tap the '+' icon and trigger a frame.
23 | await tester.tap(find.byIcon(Icons.add));
24 | await tester.pump();
25 |
26 | // Verify that our counter has incremented.
27 | expect(find.text('0'), findsNothing);
28 | expect(find.text('1'), findsOneWidget);
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: "ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a"
8 | channel: "stable"
9 |
10 | project_type: app
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
17 | base_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
18 | - platform: android
19 | create_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
20 | base_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
21 | - platform: ios
22 | create_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
23 | base_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
24 |
25 | # User provided section
26 |
27 | # List of Local paths (relative to this file) that should be
28 | # ignored by the migrate tool.
29 | #
30 | # Files that are not part of the templates will be ignored by default.
31 | unmanaged_files:
32 | - 'lib/main.dart'
33 | - 'ios/Runner.xcodeproj/project.pbxproj'
34 |
--------------------------------------------------------------------------------
/lib/network/bean/my_todo_data_entity.dart:
--------------------------------------------------------------------------------
1 | import 'package:wan_android_flutter/generated/json/base/json_field.dart';
2 | import 'package:wan_android_flutter/generated/json/my_todo_data_entity.g.dart';
3 | import 'dart:convert';
4 | export 'package:wan_android_flutter/generated/json/my_todo_data_entity.g.dart';
5 |
6 | @JsonSerializable()
7 | class MyTodoDataEntity {
8 | late int curPage;
9 | late List datas;
10 | late int offset;
11 | late bool over;
12 | late int pageCount;
13 | late int size;
14 | late int total;
15 |
16 | MyTodoDataEntity();
17 |
18 | factory MyTodoDataEntity.fromJson(Map json) => $MyTodoDataEntityFromJson(json);
19 |
20 | Map toJson() => $MyTodoDataEntityToJson(this);
21 |
22 | @override
23 | String toString() {
24 | return jsonEncode(this);
25 | }
26 | }
27 |
28 | @JsonSerializable()
29 | class MyTodoDataItem {
30 | late int completeDate;
31 | late String completeDateStr;
32 | late String content;
33 | late int date;
34 | late String dateStr;
35 | late int id;
36 | late int priority;
37 | late int status;
38 | late String title;
39 | late int type;
40 | late int userId;
41 |
42 | MyTodoDataItem();
43 |
44 | factory MyTodoDataItem.fromJson(Map json) => $MyTodoDataDatasFromJson(json);
45 |
46 | Map toJson() => $MyTodoDataDatasToJson(this);
47 |
48 | @override
49 | String toString() {
50 | return jsonEncode(this);
51 | }
52 | }
--------------------------------------------------------------------------------
/lib/network/api.dart:
--------------------------------------------------------------------------------
1 |
2 | class Api {
3 | /// 首页文章
4 | static const String homePageArticle = "article/list/";
5 |
6 | /// 置顶文章
7 | static const String topArticle = "article/top/json";
8 |
9 | /// 获取banner
10 | static const String banner = "banner/json";
11 |
12 | /// 登录
13 | static const String login = "user/login";
14 |
15 | /// 注册
16 | static const String register = "user/register";
17 |
18 | /// 退出登录
19 | static const String logout = "user/logout/json";
20 |
21 | /// 项目分类
22 | static const String projectCategory = "project/tree/json";
23 |
24 | /// 项目列表
25 | static const String projectList = "project/list/";
26 |
27 | /// 搜索
28 | static const String searchForKeyword = "article/query/";
29 |
30 | /// 广场页列表
31 | static const String plazaArticleList = "user_article/list/";
32 |
33 | /// 点击收藏
34 | static const String collectArticle = "lg/collect/";
35 |
36 | /// 取消收藏
37 | static const String uncollectArticel = "lg/uncollect_originId/";
38 |
39 | /// 获取搜索热词
40 | static const String hotKeywords = "hotkey/json";
41 |
42 | /// 获取收藏文章列表
43 | static const String collectList = "lg/collect/list/";
44 |
45 | /// 收藏网站列表
46 | static const String collectWebaddressList = "lg/collect/usertools/json";
47 |
48 | /// 我的分享
49 | static const String sharedList = "user/lg/private_articles/";
50 |
51 | /// 分享文章 post
52 | static const String shareArticle = "lg/user_article/add/json";
53 |
54 | /// todoList
55 | static const String todoList = "lg/todo/v2/list/";
56 | }
57 |
58 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | include: package:flutter_lints/flutter.yaml
11 |
12 | linter:
13 | # The lint rules applied to this project can be customized in the
14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15 | # included above or to enable additional rules. A list of all available lints
16 | # and their documentation is published at https://dart.dev/lints.
17 | #
18 | # Instead of disabling a lint rule for the entire project in the
19 | # section below, it can also be suppressed for a single line of code
20 | # or a specific dart file by using the `// ignore: name_of_lint` and
21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
22 | # producing the lint.
23 | rules:
24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
26 |
27 | # Additional information about this file can be found at
28 | # https://dart.dev/guides/language/analysis-options
29 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # wan_android_flutter
2 |
3 | ## 玩Android的flutter客户端项目
4 |
5 | ## 注: 此项目中的数据实体类是通过JsonToDart插件生成的,因为dart没有像java中那样的反射,解析json都是要硬编码的,所以用插件生成这些硬编码的代码比较合适。插件地址https://plugins.jetbrains.com/plugin/12562-jsontodart-json-to-dart-
6 |
7 | |  |  |  |
8 | | -------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
9 | |  |  |  |
10 | |  | | |
11 |
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | Wan Android Flutter
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | wan_android_flutter
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | $(FLUTTER_BUILD_NUMBER)
25 | LSRequiresIPhoneOS
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UIMainStoryboardFile
30 | Main
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 | CADisableMinimumFrameDurationOnPhone
45 |
46 | UIApplicationSupportsIndirectInputEvents
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/lib/generated/json/hot_keyword_entity.g.dart:
--------------------------------------------------------------------------------
1 | import 'package:wan_android_flutter/generated/json/base/json_convert_content.dart';
2 | import 'package:wan_android_flutter/network/bean/hot_keyword_entity.dart';
3 |
4 | HotKeywordEntity $HotKeywordEntityFromJson(Map json) {
5 | final HotKeywordEntity hotKeywordEntity = HotKeywordEntity();
6 | final int? id = jsonConvert.convert(json['id']);
7 | if (id != null) {
8 | hotKeywordEntity.id = id;
9 | }
10 | final String? link = jsonConvert.convert(json['link']);
11 | if (link != null) {
12 | hotKeywordEntity.link = link;
13 | }
14 | final String? name = jsonConvert.convert(json['name']);
15 | if (name != null) {
16 | hotKeywordEntity.name = name;
17 | }
18 | final int? order = jsonConvert.convert(json['order']);
19 | if (order != null) {
20 | hotKeywordEntity.order = order;
21 | }
22 | final int? visible = jsonConvert.convert(json['visible']);
23 | if (visible != null) {
24 | hotKeywordEntity.visible = visible;
25 | }
26 | return hotKeywordEntity;
27 | }
28 |
29 | Map $HotKeywordEntityToJson(HotKeywordEntity entity) {
30 | final Map data = {};
31 | data['id'] = entity.id;
32 | data['link'] = entity.link;
33 | data['name'] = entity.name;
34 | data['order'] = entity.order;
35 | data['visible'] = entity.visible;
36 | return data;
37 | }
38 |
39 | extension HotKeywordEntityExtension on HotKeywordEntity {
40 | HotKeywordEntity copyWith({
41 | int? id,
42 | String? link,
43 | String? name,
44 | int? order,
45 | int? visible,
46 | }) {
47 | return HotKeywordEntity()
48 | ..id = id ?? this.id
49 | ..link = link ?? this.link
50 | ..name = name ?? this.name
51 | ..order = order ?? this.order
52 | ..visible = visible ?? this.visible;
53 | }
54 | }
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
16 |
20 |
24 |
25 |
26 |
27 |
28 |
29 |
31 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/lib/network/bean/my_shared_data_entity.dart:
--------------------------------------------------------------------------------
1 | import 'package:wan_android_flutter/generated/json/base/json_field.dart';
2 | import 'package:wan_android_flutter/generated/json/my_shared_data_entity.g.dart';
3 | import 'dart:convert';
4 |
5 | import 'package:wan_android_flutter/network/bean/article_data_entity.dart';
6 | export 'package:wan_android_flutter/generated/json/my_shared_data_entity.g.dart';
7 |
8 | @JsonSerializable()
9 | class MySharedDataEntity {
10 | late MySharedDataCoinInfo coinInfo;
11 | late MySharedDataShareArticles shareArticles;
12 |
13 | MySharedDataEntity();
14 |
15 | factory MySharedDataEntity.fromJson(Map json) => $MySharedDataEntityFromJson(json);
16 |
17 | Map toJson() => $MySharedDataEntityToJson(this);
18 |
19 | @override
20 | String toString() {
21 | return jsonEncode(this);
22 | }
23 | }
24 |
25 | @JsonSerializable()
26 | class MySharedDataCoinInfo {
27 | late int coinCount;
28 | late int level;
29 | late String nickname;
30 | late String rank;
31 | late int userId;
32 | late String username;
33 |
34 | MySharedDataCoinInfo();
35 |
36 | factory MySharedDataCoinInfo.fromJson(Map json) => $MySharedDataCoinInfoFromJson(json);
37 |
38 | Map toJson() => $MySharedDataCoinInfoToJson(this);
39 |
40 | @override
41 | String toString() {
42 | return jsonEncode(this);
43 | }
44 | }
45 |
46 | @JsonSerializable()
47 | class MySharedDataShareArticles {
48 | late int curPage;
49 | late List datas;
50 | late int offset;
51 | late bool over;
52 | late int pageCount;
53 | late int size;
54 | late int total;
55 |
56 | MySharedDataShareArticles();
57 |
58 | factory MySharedDataShareArticles.fromJson(Map json) => $MySharedDataShareArticlesFromJson(json);
59 |
60 | Map toJson() => $MySharedDataShareArticlesToJson(this);
61 |
62 | @override
63 | String toString() {
64 | return jsonEncode(this);
65 | }
66 | }
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "com.android.application"
3 | id "kotlin-android"
4 | id "dev.flutter.flutter-gradle-plugin"
5 | }
6 |
7 | def localProperties = new Properties()
8 | def localPropertiesFile = rootProject.file('local.properties')
9 | if (localPropertiesFile.exists()) {
10 | localPropertiesFile.withReader('UTF-8') { reader ->
11 | localProperties.load(reader)
12 | }
13 | }
14 |
15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
16 | if (flutterVersionCode == null) {
17 | flutterVersionCode = '1'
18 | }
19 |
20 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
21 | if (flutterVersionName == null) {
22 | flutterVersionName = '1.0'
23 | }
24 |
25 | android {
26 | namespace "com.qianyue.wan_android_flutter"
27 | compileSdkVersion flutter.compileSdkVersion
28 | ndkVersion flutter.ndkVersion
29 |
30 | compileOptions {
31 | sourceCompatibility JavaVersion.VERSION_1_8
32 | targetCompatibility JavaVersion.VERSION_1_8
33 | }
34 |
35 | kotlinOptions {
36 | jvmTarget = '1.8'
37 | }
38 |
39 | sourceSets {
40 | main.java.srcDirs += 'src/main/kotlin'
41 | }
42 |
43 | defaultConfig {
44 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
45 | applicationId "com.qianyue.wan_android_flutter"
46 | // You can update the following values to match your application needs.
47 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
48 | minSdkVersion 26
49 | targetSdkVersion flutter.targetSdkVersion
50 | versionCode flutterVersionCode.toInteger()
51 | versionName flutterVersionName
52 | }
53 |
54 | buildTypes {
55 | release {
56 | // TODO: Add your own signing config for the release build.
57 | // Signing with the debug keys for now, so `flutter run --release` works.
58 | signingConfig signingConfigs.debug
59 | }
60 | }
61 | }
62 |
63 | flutter {
64 | source '../..'
65 | }
66 |
67 | dependencies {
68 | implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0"))
69 | }
70 |
--------------------------------------------------------------------------------
/lib/network/bean/article_data_entity.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:wan_android_flutter/generated/json/article_data_entity.g.dart';
3 | import 'package:wan_android_flutter/generated/json/base/json_field.dart';
4 | import 'dart:convert';
5 |
6 | export 'package:wan_android_flutter/generated/json/article_data_entity.g.dart';
7 |
8 | @JsonSerializable()
9 | class ArticleDataEntity {
10 | late int curPage;
11 | late List datas;
12 | late int offset;
13 | late bool over;
14 | late int pageCount;
15 | late int size;
16 | late int total;
17 |
18 | ArticleDataEntity();
19 |
20 | factory ArticleDataEntity.fromJson(Map json) => $ArticleDataEntityFromJson(json);
21 |
22 | Map toJson() => $ArticleDataEntityToJson(this);
23 |
24 | @override
25 | String toString() {
26 | return jsonEncode(this);
27 | }
28 | }
29 |
30 | @JsonSerializable()
31 | class ArticleItemEntity with ChangeNotifier {
32 | late bool adminAdd;
33 | late String apkLink;
34 | late int audit;
35 | late String? author;
36 | late bool canEdit;
37 | late int chapterId;
38 | late String? chapterName;
39 | bool? _collect;
40 | set collect(bool value) {
41 | _collect = value;
42 | notifyListeners();
43 | }
44 | bool get collect => _collect ?? false;
45 | late int courseId;
46 | late String desc;
47 | late String descMd;
48 | late String envelopePic;
49 | late bool fresh;
50 | late String host;
51 | late int id;
52 | late bool isAdminAdd;
53 | late String link;
54 | late String niceDate;
55 | late String niceShareDate;
56 | late String origin;
57 | late String prefix;
58 | late String projectLink;
59 | late int publishTime;
60 | late int realSuperChapterId;
61 | late int selfVisible;
62 | late int shareDate;
63 | String? shareUser;
64 | late int superChapterId;
65 | String? superChapterName;
66 | late List tags;
67 | late String title;
68 | int? type;
69 | late int userId;
70 | late int visible;
71 | late int zan;
72 |
73 | ArticleItemEntity();
74 |
75 | factory ArticleItemEntity.fromJson(Map json) => $ArticleItemEntityFromJson(json);
76 |
77 | Map toJson() => $ArticleItemEntityToJson(this);
78 |
79 | @override
80 | String toString() {
81 | return jsonEncode(this);
82 | }
83 | }
--------------------------------------------------------------------------------
/lib/user.dart:
--------------------------------------------------------------------------------
1 |
2 |
3 | import 'dart:convert';
4 |
5 | import 'package:flutter/foundation.dart';
6 | import 'package:mmkv/mmkv.dart';
7 | import 'package:wan_android_flutter/network/bean/user_info_entity.dart';
8 | import 'package:wan_android_flutter/network/request_util.dart';
9 | import 'package:wan_android_flutter/utils/log_util.dart';
10 |
11 | typedef LoginStatusChangeCallback = void Function();
12 |
13 | class User extends ChangeNotifier {
14 | static const String _userInfoKey = "userInfo";
15 |
16 | User._internal();
17 |
18 | static final User _singleton = User._internal();
19 |
20 | factory User() => _singleton;
21 |
22 | UserInfoEntity? _userInfoEntity;
23 |
24 | bool isLoggedIn() => _userInfoEntity != null;
25 |
26 | String get userName => _userInfoEntity!.username;
27 |
28 | int get userCoinCount => _userInfoEntity!.coinCount;
29 |
30 | final List _list = [];
31 |
32 | on(LoginStatusChangeCallback loginStatusChange) {
33 | _list.add(loginStatusChange);
34 | }
35 |
36 | off(LoginStatusChangeCallback loginStatusChange) {
37 | _list.remove(loginStatusChange);
38 | }
39 |
40 | loadFromLocal() {
41 | try {
42 | MMKV mmkv = MMKV.defaultMMKV();
43 | String? infoContent = mmkv.decodeString(_userInfoKey);
44 | if (infoContent == null || infoContent.isEmpty) {
45 | return;
46 | }
47 | _userInfoEntity = UserInfoEntity.fromJson(json.decoder.convert(infoContent));
48 | } catch(e) {
49 | WanLog.e("load user info from local error- $e");
50 | }
51 | }
52 |
53 | loginSuccess(UserInfoEntity userInfoEntity) {
54 | _userInfoEntity = userInfoEntity;
55 | try {
56 | MMKV mmkv = MMKV.defaultMMKV();
57 | String infoContent = _userInfoEntity.toString();
58 | mmkv.encodeString(_userInfoKey, infoContent);
59 | } catch(e) {
60 | WanLog.e("save user info to local error- $e");
61 | }
62 | notifyListeners();
63 | for (var callback in _list) {
64 | callback();
65 | }
66 | }
67 |
68 | logout() {
69 | _userInfoEntity = null;
70 | HttpGo.instance.cookieJar?.deleteAll();
71 | try {
72 | MMKV mmkv = MMKV.defaultMMKV();
73 | mmkv.encodeString(_userInfoKey, "");
74 | } catch(e) {
75 | WanLog.e("logout user info error- $e");
76 | }
77 |
78 | notifyListeners();
79 | for (var callback in _list) {
80 | callback();
81 | }
82 | }
83 | }
--------------------------------------------------------------------------------
/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/generated/json/user_tool_entity.g.dart:
--------------------------------------------------------------------------------
1 | import 'package:wan_android_flutter/generated/json/base/json_convert_content.dart';
2 | import 'package:wan_android_flutter/network/bean/user_tool_entity.dart';
3 |
4 | UserToolEntity $UserToolEntityFromJson(Map json) {
5 | final UserToolEntity userToolEntity = UserToolEntity();
6 | final String? desc = jsonConvert.convert(json['desc']);
7 | if (desc != null) {
8 | userToolEntity.desc = desc;
9 | }
10 | final String? icon = jsonConvert.convert(json['icon']);
11 | if (icon != null) {
12 | userToolEntity.icon = icon;
13 | }
14 | final int? id = jsonConvert.convert(json['id']);
15 | if (id != null) {
16 | userToolEntity.id = id;
17 | }
18 | final String? link = jsonConvert.convert(json['link']);
19 | if (link != null) {
20 | userToolEntity.link = link;
21 | }
22 | final String? name = jsonConvert.convert(json['name']);
23 | if (name != null) {
24 | userToolEntity.name = name;
25 | }
26 | final int? order = jsonConvert.convert(json['order']);
27 | if (order != null) {
28 | userToolEntity.order = order;
29 | }
30 | final int? userId = jsonConvert.convert(json['userId']);
31 | if (userId != null) {
32 | userToolEntity.userId = userId;
33 | }
34 | final int? visible = jsonConvert.convert(json['visible']);
35 | if (visible != null) {
36 | userToolEntity.visible = visible;
37 | }
38 | return userToolEntity;
39 | }
40 |
41 | Map $UserToolEntityToJson(UserToolEntity entity) {
42 | final Map data = {};
43 | data['desc'] = entity.desc;
44 | data['icon'] = entity.icon;
45 | data['id'] = entity.id;
46 | data['link'] = entity.link;
47 | data['name'] = entity.name;
48 | data['order'] = entity.order;
49 | data['userId'] = entity.userId;
50 | data['visible'] = entity.visible;
51 | return data;
52 | }
53 |
54 | extension UserToolEntityExtension on UserToolEntity {
55 | UserToolEntity copyWith({
56 | String? desc,
57 | String? icon,
58 | int? id,
59 | String? link,
60 | String? name,
61 | int? order,
62 | int? userId,
63 | int? visible,
64 | }) {
65 | return UserToolEntity()
66 | ..desc = desc ?? this.desc
67 | ..icon = icon ?? this.icon
68 | ..id = id ?? this.id
69 | ..link = link ?? this.link
70 | ..name = name ?? this.name
71 | ..order = order ?? this.order
72 | ..userId = userId ?? this.userId
73 | ..visible = visible ?? this.visible;
74 | }
75 | }
--------------------------------------------------------------------------------
/lib/generated/json/banner_entity.g.dart:
--------------------------------------------------------------------------------
1 | import 'package:wan_android_flutter/generated/json/base/json_convert_content.dart';
2 | import 'package:wan_android_flutter/network/bean/banner_entity.dart';
3 |
4 | BannerEntity $BannerEntityFromJson(Map json) {
5 | final BannerEntity bannerEntity = BannerEntity();
6 | final String? desc = jsonConvert.convert(json['desc']);
7 | if (desc != null) {
8 | bannerEntity.desc = desc;
9 | }
10 | final int? id = jsonConvert.convert(json['id']);
11 | if (id != null) {
12 | bannerEntity.id = id;
13 | }
14 | final String? imagePath = jsonConvert.convert(json['imagePath']);
15 | if (imagePath != null) {
16 | bannerEntity.imagePath = imagePath;
17 | }
18 | final int? isVisible = jsonConvert.convert(json['isVisible']);
19 | if (isVisible != null) {
20 | bannerEntity.isVisible = isVisible;
21 | }
22 | final int? order = jsonConvert.convert(json['order']);
23 | if (order != null) {
24 | bannerEntity.order = order;
25 | }
26 | final String? title = jsonConvert.convert(json['title']);
27 | if (title != null) {
28 | bannerEntity.title = title;
29 | }
30 | final int? type = jsonConvert.convert(json['type']);
31 | if (type != null) {
32 | bannerEntity.type = type;
33 | }
34 | final String? url = jsonConvert.convert(json['url']);
35 | if (url != null) {
36 | bannerEntity.url = url;
37 | }
38 | return bannerEntity;
39 | }
40 |
41 | Map $BannerEntityToJson(BannerEntity entity) {
42 | final Map data = {};
43 | data['desc'] = entity.desc;
44 | data['id'] = entity.id;
45 | data['imagePath'] = entity.imagePath;
46 | data['isVisible'] = entity.isVisible;
47 | data['order'] = entity.order;
48 | data['title'] = entity.title;
49 | data['type'] = entity.type;
50 | data['url'] = entity.url;
51 | return data;
52 | }
53 |
54 | extension BannerEntityExtension on BannerEntity {
55 | BannerEntity copyWith({
56 | String? desc,
57 | int? id,
58 | String? imagePath,
59 | int? isVisible,
60 | int? order,
61 | String? title,
62 | int? type,
63 | String? url,
64 | }) {
65 | return BannerEntity()
66 | ..desc = desc ?? this.desc
67 | ..id = id ?? this.id
68 | ..imagePath = imagePath ?? this.imagePath
69 | ..isVisible = isVisible ?? this.isVisible
70 | ..order = order ?? this.order
71 | ..title = title ?? this.title
72 | ..type = type ?? this.type
73 | ..url = url ?? this.url;
74 | }
75 | }
--------------------------------------------------------------------------------
/lib/base/base_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:fluttertoast/fluttertoast.dart';
4 |
5 | mixin BasePage on State {
6 | /// 是否正在显示loading弹窗
7 | bool showingLoading = false;
8 |
9 | showLoadingDialog() async {
10 | if (showingLoading) {
11 | return;
12 | }
13 | showingLoading = true;
14 | await showDialog(
15 | context: context,
16 | barrierDismissible: true,
17 | builder: (context) {
18 | return const AlertDialog(
19 | content: Column(
20 | mainAxisSize: MainAxisSize.min,
21 | children: [
22 | CircularProgressIndicator(),
23 | Padding(
24 | padding: EdgeInsets.only(top: 24),
25 | child: Text("请稍候..."),
26 | )
27 | ],
28 | ),
29 | );
30 | });
31 | showingLoading = false;
32 | }
33 |
34 | dismissLoading() {
35 | if (showingLoading) {
36 | Navigator.of(context).pop();
37 | }
38 | }
39 | }
40 |
41 | class RetryWidget extends StatelessWidget {
42 | const RetryWidget({super.key, required this.onTapRetry});
43 |
44 | final void Function() onTapRetry;
45 |
46 | @override
47 | Widget build(BuildContext context) {
48 | return GestureDetector(
49 | behavior: HitTestBehavior.opaque,
50 | onTap: onTapRetry,
51 | child: const SizedBox(
52 | width: double.infinity,
53 | height: double.infinity,
54 | child: Column(
55 | mainAxisAlignment: MainAxisAlignment.center,
56 | crossAxisAlignment: CrossAxisAlignment.center,
57 | children: [
58 | Padding(
59 | padding: EdgeInsets.only(bottom: 16),
60 | child: Icon(Icons.refresh)),
61 | Text("加载失败,点击重试")
62 | ],
63 | ),
64 | ));
65 | }
66 | }
67 |
68 | class EmptyWidget extends StatelessWidget {
69 | const EmptyWidget({super.key});
70 |
71 | @override
72 | Widget build(BuildContext context) {
73 | return const SizedBox(
74 | width: double.infinity,
75 | height: double.infinity,
76 | child: Column(
77 | mainAxisAlignment: MainAxisAlignment.center,
78 | crossAxisAlignment: CrossAxisAlignment.center,
79 | children: [
80 | Padding(
81 | padding: EdgeInsets.only(bottom: 16), child: Icon(Icons.book)),
82 | Text("无数据")
83 | ],
84 | ),
85 | );
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/lib/network/bean/project_list_data_entity.dart:
--------------------------------------------------------------------------------
1 | import 'package:wan_android_flutter/generated/json/base/json_field.dart';
2 | import 'package:wan_android_flutter/generated/json/project_list_data_entity.g.dart';
3 | import 'dart:convert';
4 | export 'package:wan_android_flutter/generated/json/project_list_data_entity.g.dart';
5 |
6 | @JsonSerializable()
7 | class ProjectListDataEntity {
8 | late int curPage;
9 | late List datas;
10 | late int offset;
11 | late bool over;
12 | late int pageCount;
13 | late int size;
14 | late int total;
15 |
16 | ProjectListDataEntity();
17 |
18 | factory ProjectListDataEntity.fromJson(Map json) => $ProjectListDataEntityFromJson(json);
19 |
20 | Map toJson() => $ProjectListDataEntityToJson(this);
21 |
22 | @override
23 | String toString() {
24 | return jsonEncode(this);
25 | }
26 | }
27 |
28 | @JsonSerializable()
29 | class ProjectListDataItemEntity {
30 | late bool adminAdd;
31 | late String apkLink;
32 | late int audit;
33 | late String author;
34 | late bool canEdit;
35 | late int chapterId;
36 | late String chapterName;
37 | late bool collect;
38 | late int courseId;
39 | late String desc;
40 | late String descMd;
41 | late String envelopePic;
42 | late bool fresh;
43 | late String host;
44 | late int id;
45 | late bool isAdminAdd;
46 | late String link;
47 | late String niceDate;
48 | late String niceShareDate;
49 | late String origin;
50 | late String prefix;
51 | late String projectLink;
52 | late int publishTime;
53 | late int realSuperChapterId;
54 | late int selfVisible;
55 | late int shareDate;
56 | late String shareUser;
57 | late int superChapterId;
58 | late String superChapterName;
59 | late List tags;
60 | late String title;
61 | late int type;
62 | late int userId;
63 | late int visible;
64 | late int zan;
65 |
66 | ProjectListDataItemEntity();
67 |
68 | factory ProjectListDataItemEntity.fromJson(Map json) => $ProjectListDataDatasFromJson(json);
69 |
70 | Map toJson() => $ProjectListDataDatasToJson(this);
71 |
72 | @override
73 | String toString() {
74 | return jsonEncode(this);
75 | }
76 | }
77 |
78 | @JsonSerializable()
79 | class ProjectListDataDatasTags {
80 | late String name;
81 | late String url;
82 |
83 | ProjectListDataDatasTags();
84 |
85 | factory ProjectListDataDatasTags.fromJson(Map json) => $ProjectListDataDatasTagsFromJson(json);
86 |
87 | Map toJson() => $ProjectListDataDatasTagsToJson(this);
88 |
89 | @override
90 | String toString() {
91 | return jsonEncode(this);
92 | }
93 | }
--------------------------------------------------------------------------------
/lib/pages/detail_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_html/flutter_html.dart';
3 | import 'package:webview_flutter/webview_flutter.dart';
4 |
5 | class DetailPage extends StatefulWidget {
6 | const DetailPage(this.url, this.title, {Key? key}) : super(key: key);
7 |
8 | final String url;
9 |
10 | final String title;
11 |
12 | @override
13 | State createState() => _DetailPageState();
14 | }
15 |
16 | class _DetailPageState extends State {
17 | _DetailPageState();
18 |
19 | Key progressKey = GlobalKey();
20 |
21 | Key contentKey = GlobalKey();
22 |
23 | final WebViewController _controller = WebViewController();
24 |
25 | bool finish = false;
26 |
27 | @override
28 | void initState() {
29 | super.initState();
30 | _controller
31 | ..setJavaScriptMode(JavaScriptMode.unrestricted)
32 | ..setNavigationDelegate(NavigationDelegate(
33 | onPageStarted: (url) {},
34 | onProgress: (progress) {},
35 | onPageFinished: (content) {
36 | setState(() {
37 | finish = true;
38 | });
39 | }))
40 | ..loadRequest(Uri.parse(widget.url));
41 | }
42 |
43 | @override
44 | Widget build(BuildContext context) {
45 | return Scaffold(
46 | appBar: AppBar(
47 | titleSpacing: 0,
48 | title: Html(
49 | data: widget.title,
50 | style: {
51 | "html": Style(
52 | color: Colors.white,
53 | margin: Margins.zero,
54 | maxLines: 1,
55 | textOverflow: TextOverflow.ellipsis,
56 | fontSize: FontSize(18),
57 | padding: HtmlPaddings.zero,
58 | alignment: Alignment.topLeft),
59 | "body": Style(
60 | color: Colors.white,
61 | margin: Margins.zero,
62 | maxLines: 1,
63 | textOverflow: TextOverflow.ellipsis,
64 | fontSize: FontSize(18),
65 | padding: HtmlPaddings.zero,
66 | alignment: Alignment.topLeft)
67 | },
68 | ),
69 | backgroundColor: Theme.of(context).primaryColor,
70 | iconTheme: const IconThemeData(color: Colors.white),
71 | ),
72 | body: !finish
73 | ? Container(
74 | key: progressKey,
75 | width: double.infinity,
76 | height: double.infinity,
77 | alignment: Alignment.center,
78 | child: const CircularProgressIndicator())
79 | : WebViewWidget(key: contentKey, controller: _controller),
80 | );
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/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/pages/setting_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:fluttertoast/fluttertoast.dart';
3 | import 'package:get/get.dart';
4 | import 'package:wan_android_flutter/base/base_page.dart';
5 | import 'package:wan_android_flutter/network/api.dart';
6 | import 'package:wan_android_flutter/network/bean/AppResponse.dart';
7 | import 'package:wan_android_flutter/network/request_util.dart';
8 |
9 | import '../user.dart';
10 |
11 | class SettingPage extends StatefulWidget {
12 | const SettingPage({Key? key}) : super(key: key);
13 |
14 | @override
15 | State createState() => _SettingPageState();
16 | }
17 |
18 | class _SettingPageState extends State with BasePage {
19 | _showLogoutDialog() async {
20 | if (!User().isLoggedIn()) {
21 | Fluttertoast.showToast(msg: "当前未登录");
22 | return;
23 | }
24 | bool result = await showDialog(
25 | context: context,
26 | builder: (context) {
27 | return AlertDialog(
28 | title: const Text("提示"),
29 | content: const Text("确定要退出吗?"),
30 | actions: [
31 | TextButton(
32 | onPressed: () {
33 | Get.back(result: false);
34 | },
35 | child: const Text("取消")),
36 | TextButton(
37 | onPressed: () {
38 | Get.back(result: true);
39 | },
40 | child: const Text("确定"))
41 | ],
42 | );
43 | }) ??
44 | false;
45 | if (result) {
46 | showLoadingDialog();
47 | AppResponse res = await HttpGo.instance.get(Api.logout);
48 | dismissLoading();
49 | if (res.isSuccessful) {
50 | User().logout();
51 | Fluttertoast.showToast(msg: "已退出登录!");
52 | } else {
53 | Fluttertoast.showToast(msg:"退出登录失败-${res.errorMsg}");
54 | }
55 | }
56 | }
57 |
58 | @override
59 | Widget build(BuildContext context) {
60 | return Scaffold(
61 | appBar: AppBar(
62 | title: const Text(
63 | "系统设置",
64 | style: TextStyle(color: Colors.white),
65 | ),
66 | iconTheme: const IconThemeData(color: Colors.white),
67 | backgroundColor: Theme.of(context).primaryColor,
68 | ),
69 | body: Column(
70 | children: [
71 | Padding(
72 | padding: const EdgeInsets.fromLTRB(16, 16, 16, 0),
73 | child: GestureDetector(
74 | onTap: _showLogoutDialog,
75 | behavior: HitTestBehavior.opaque,
76 | child: SizedBox(
77 | height: 48,
78 | width: double.infinity,
79 | child: Card(
80 | child: Container(
81 | alignment: Alignment.centerLeft,
82 | padding: const EdgeInsets.fromLTRB(16, 0, 0, 0),
83 | child: const Text("退出登录"))))))
84 | ],
85 | ));
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/lib/pages/search_result_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:easy_refresh/easy_refresh.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:fluttertoast/fluttertoast.dart';
4 | import 'package:get/get.dart';
5 | import 'package:wan_android_flutter/network/api.dart';
6 | import 'package:wan_android_flutter/network/bean/AppResponse.dart';
7 | import 'package:wan_android_flutter/network/bean/article_data_entity.dart';
8 | import 'package:wan_android_flutter/network/request_util.dart';
9 | import 'package:wan_android_flutter/pages/article_item_layout.dart';
10 | import 'package:wan_android_flutter/pages/detail_page.dart';
11 |
12 | class SearchResultPage extends StatefulWidget {
13 | const SearchResultPage({Key? key, required this.keyword}) : super(key: key);
14 |
15 | final String keyword;
16 |
17 | @override
18 | State createState() => _SearchResultPageState();
19 | }
20 |
21 | class _SearchResultPageState extends State {
22 | int _currentIndex = 0;
23 |
24 | List data = [];
25 |
26 | final EasyRefreshController _refreshController = EasyRefreshController(
27 | controlFinishLoad: true, controlFinishRefresh: true);
28 |
29 | @override
30 | void initState() {
31 | super.initState();
32 | _searchRequest();
33 | }
34 |
35 | _searchRequest() async {
36 | AppResponse res = await HttpGo.instance.post(
37 | "${Api.searchForKeyword}$_currentIndex/json",
38 | data: {"k": widget.keyword});
39 | if (_currentIndex == 0) {
40 | data.clear();
41 | }
42 | if (res.isSuccessful) {
43 | setState(() {
44 | data.addAll(res.data!.datas);
45 | });
46 | }
47 | _refreshController.finishRefresh();
48 | _refreshController.finishLoad();
49 | }
50 |
51 | @override
52 | Widget build(BuildContext context) {
53 | return Scaffold(
54 | appBar: AppBar(
55 | title: Text(
56 | widget.keyword,
57 | style: const TextStyle(color: Colors.white),
58 | ),
59 | iconTheme: const IconThemeData(color: Colors.white),
60 | backgroundColor: Theme.of(context).primaryColor,
61 | ),
62 | body: EasyRefresh.builder(
63 | controller: _refreshController,
64 | childBuilder: (context, physics) {
65 | return ListView.builder(
66 | itemBuilder: (context, index) {
67 | return GestureDetector(
68 | behavior: HitTestBehavior.opaque,
69 | onTap: () => Get.to(
70 | () => DetailPage(data[index].link, data[index].title)),
71 | child: ArticleItemLayout(
72 | itemEntity: data[index],
73 | onCollectTap: () {
74 | _onCollectClick(data[index]);
75 | }));
76 | },
77 | physics: physics,
78 | itemCount: data.length,
79 | );
80 | },
81 | onRefresh: () {
82 | _currentIndex = 0;
83 | _searchRequest();
84 | },
85 | onLoad: () {
86 | _currentIndex++;
87 | _searchRequest();
88 | },
89 | ),
90 | );
91 | }
92 |
93 | _onCollectClick(ArticleItemEntity itemEntity) async {
94 | bool collected = itemEntity.collect;
95 | AppResponse res = await (collected
96 | ? HttpGo.instance.post("${Api.uncollectArticel}${itemEntity.id}/json")
97 | : HttpGo.instance.post("${Api.collectArticle}${itemEntity.id}/json"));
98 |
99 | if (res.isSuccessful) {
100 | Fluttertoast.showToast(msg: collected ? "取消收藏!" : "收藏成功!");
101 | itemEntity.collect = !itemEntity.collect;
102 | } else {
103 | Fluttertoast.showToast(
104 | msg: (collected ? "取消失败 -- " : "收藏失败 -- ") +
105 | (res.errorMsg ?? res.errorCode.toString()));
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
43 |
49 |
50 |
51 |
52 |
53 |
63 |
65 |
71 |
72 |
73 |
74 |
80 |
82 |
88 |
89 |
90 |
91 |
93 |
94 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/lib/generated/json/user_info_entity.g.dart:
--------------------------------------------------------------------------------
1 | import 'package:wan_android_flutter/generated/json/base/json_convert_content.dart';
2 | import 'package:wan_android_flutter/network/bean/user_info_entity.dart';
3 |
4 | UserInfoEntity $UserInfoEntityFromJson(Map json) {
5 | final UserInfoEntity userInfoEntity = UserInfoEntity();
6 | final bool? admin = jsonConvert.convert(json['admin']);
7 | if (admin != null) {
8 | userInfoEntity.admin = admin;
9 | }
10 | final List? chapterTops = (json['chapterTops'] as List?)
11 | ?.map(
12 | (e) => e)
13 | .toList();
14 | if (chapterTops != null) {
15 | userInfoEntity.chapterTops = chapterTops;
16 | }
17 | final int? coinCount = jsonConvert.convert(json['coinCount']);
18 | if (coinCount != null) {
19 | userInfoEntity.coinCount = coinCount;
20 | }
21 | final List? collectIds = (json['collectIds'] as List?)?.map(
22 | (e) => jsonConvert.convert(e) as int).toList();
23 | if (collectIds != null) {
24 | userInfoEntity.collectIds = collectIds;
25 | }
26 | final String? email = jsonConvert.convert(json['email']);
27 | if (email != null) {
28 | userInfoEntity.email = email;
29 | }
30 | final String? icon = jsonConvert.convert(json['icon']);
31 | if (icon != null) {
32 | userInfoEntity.icon = icon;
33 | }
34 | final int? id = jsonConvert.convert(json['id']);
35 | if (id != null) {
36 | userInfoEntity.id = id;
37 | }
38 | final String? nickname = jsonConvert.convert(json['nickname']);
39 | if (nickname != null) {
40 | userInfoEntity.nickname = nickname;
41 | }
42 | final String? password = jsonConvert.convert(json['password']);
43 | if (password != null) {
44 | userInfoEntity.password = password;
45 | }
46 | final String? publicName = jsonConvert.convert(json['publicName']);
47 | if (publicName != null) {
48 | userInfoEntity.publicName = publicName;
49 | }
50 | final String? token = jsonConvert.convert(json['token']);
51 | if (token != null) {
52 | userInfoEntity.token = token;
53 | }
54 | final int? type = jsonConvert.convert(json['type']);
55 | if (type != null) {
56 | userInfoEntity.type = type;
57 | }
58 | final String? username = jsonConvert.convert(json['username']);
59 | if (username != null) {
60 | userInfoEntity.username = username;
61 | }
62 | return userInfoEntity;
63 | }
64 |
65 | Map $UserInfoEntityToJson(UserInfoEntity entity) {
66 | final Map data = {};
67 | data['admin'] = entity.admin;
68 | data['chapterTops'] = entity.chapterTops;
69 | data['coinCount'] = entity.coinCount;
70 | data['collectIds'] = entity.collectIds;
71 | data['email'] = entity.email;
72 | data['icon'] = entity.icon;
73 | data['id'] = entity.id;
74 | data['nickname'] = entity.nickname;
75 | data['password'] = entity.password;
76 | data['publicName'] = entity.publicName;
77 | data['token'] = entity.token;
78 | data['type'] = entity.type;
79 | data['username'] = entity.username;
80 | return data;
81 | }
82 |
83 | extension UserInfoEntityExtension on UserInfoEntity {
84 | UserInfoEntity copyWith({
85 | bool? admin,
86 | List? chapterTops,
87 | int? coinCount,
88 | List? collectIds,
89 | String? email,
90 | String? icon,
91 | int? id,
92 | String? nickname,
93 | String? password,
94 | String? publicName,
95 | String? token,
96 | int? type,
97 | String? username,
98 | }) {
99 | return UserInfoEntity()
100 | ..admin = admin ?? this.admin
101 | ..chapterTops = chapterTops ?? this.chapterTops
102 | ..coinCount = coinCount ?? this.coinCount
103 | ..collectIds = collectIds ?? this.collectIds
104 | ..email = email ?? this.email
105 | ..icon = icon ?? this.icon
106 | ..id = id ?? this.id
107 | ..nickname = nickname ?? this.nickname
108 | ..password = password ?? this.password
109 | ..publicName = publicName ?? this.publicName
110 | ..token = token ?? this.token
111 | ..type = type ?? this.type
112 | ..username = username ?? this.username;
113 | }
114 | }
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:fluttertoast/fluttertoast.dart';
3 | import 'package:get/get.dart';
4 | import 'package:mmkv/mmkv.dart';
5 | import 'package:provider/provider.dart';
6 | import 'package:url_strategy/url_strategy.dart';
7 | import 'package:wan_android_flutter/constants/constants.dart';
8 | import 'package:wan_android_flutter/network/request_util.dart';
9 | import 'package:wan_android_flutter/pages/search_page.dart';
10 | import 'package:wan_android_flutter/pages/tabpage/home_page.dart';
11 | import 'package:wan_android_flutter/pages/tabpage/mine_page.dart';
12 | import 'package:wan_android_flutter/pages/tabpage/plaza_page.dart';
13 | import 'package:wan_android_flutter/pages/tabpage/project_page.dart';
14 | import 'package:wan_android_flutter/user.dart';
15 | import 'package:wan_android_flutter/utils/error_handle.dart';
16 | import 'package:wan_android_flutter/utils/log_util.dart';
17 |
18 | Future main() async {
19 | handleError(() async {
20 | WidgetsFlutterBinding.ensureInitialized();
21 |
22 | // 初始化mmkv
23 | final rootDir = await MMKV.initialize();
24 | WanLog.i("mmkv rootDir: ${rootDir}");
25 |
26 | // 加载本地用户信息
27 | User().loadFromLocal();
28 |
29 | // 初始化dio
30 | configDio(baseUrl: Constant.baseUrl);
31 |
32 | setPathUrlStrategy();
33 | runApp(ChangeNotifierProvider(
34 | create: (context) => User(), child: const MyApp()));
35 | });
36 | }
37 |
38 | class MyApp extends StatelessWidget {
39 | const MyApp({super.key});
40 |
41 | // This widget is the root of your application.
42 | @override
43 | Widget build(BuildContext context) {
44 | return GetMaterialApp(
45 | builder: FToastBuilder(),
46 | debugShowCheckedModeBanner: false,
47 | title: 'Flutter Demo',
48 | theme: ThemeData(
49 | colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
50 | useMaterial3: true,
51 | ),
52 | home: const MainPage(title: 'WanAndroidFlutter'),
53 | );
54 | }
55 | }
56 |
57 | class MainPage extends StatefulWidget {
58 | const MainPage({super.key, required this.title});
59 |
60 | final String title;
61 |
62 | @override
63 | State createState() => _MainPageState();
64 | }
65 |
66 | class _MainPageState extends State {
67 | int _selectedItemIndex = 0;
68 |
69 | String _currentTitle = "首页";
70 |
71 | final PageController _pageController = PageController(initialPage: 0);
72 |
73 | final List _titles = ["首页", "项目", "广场", "我的"];
74 | final List _navIcons = [
75 | const Icon(Icons.home),
76 | const Icon(Icons.ac_unit),
77 | const Icon(Icons.animation),
78 | const Icon(Icons.verified_user_rounded)
79 | ];
80 |
81 | final List _pages = [
82 | const HomePage(),
83 | const ProjectPage(),
84 | const PlazaPage(),
85 | const MinePage()
86 | ];
87 |
88 | @override
89 | Widget build(BuildContext context) {
90 | return Scaffold(
91 | appBar: AppBar(
92 | backgroundColor: Theme.of(context).primaryColor,
93 | title: Text(_currentTitle, style: const TextStyle(color: Colors.white)),
94 | actions: [
95 | IconButton(
96 | icon: const Icon(Icons.search, color: Colors.white),
97 | tooltip: '搜索',
98 | onPressed: () {
99 | Get.to(() => const SearchPage());
100 | },
101 | ),
102 | ],
103 | ),
104 | bottomNavigationBar: BottomNavigationBar(
105 | type: BottomNavigationBarType.fixed,
106 | selectedFontSize: 14,
107 | unselectedFontSize: 14,
108 | iconSize: 24,
109 | selectedItemColor: Theme.of(context).primaryColor,
110 | unselectedItemColor: Colors.grey,
111 | items: _generateBottomNavList(),
112 | currentIndex: _selectedItemIndex,
113 | onTap: _onNavItemTapped,
114 | ),
115 | body: PageView.builder(
116 | physics: const NeverScrollableScrollPhysics(),
117 | itemBuilder: (context, index) {
118 | return _pages[index];
119 | },
120 | onPageChanged: _onPageChanged,
121 | controller: _pageController,
122 | ),
123 | );
124 | }
125 |
126 | List _generateBottomNavList() {
127 | return List.generate(_titles.length, (index) {
128 | return BottomNavigationBarItem(
129 | icon: _navIcons[index], label: _titles[index]);
130 | });
131 | }
132 |
133 | void _onPageChanged(int index) {
134 | setState(() {
135 | _selectedItemIndex = index;
136 | _currentTitle = _titles[index];
137 | });
138 | }
139 |
140 | void _onNavItemTapped(int index) {
141 | // _pageController.animateToPage(index, duration: const Duration(milliseconds: 200), curve: Curves.ease);
142 | _pageController.jumpToPage(index);
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: wan_android_flutter
2 | description: 玩Android的flutter客户端项目
3 | # The following line prevents the package from being accidentally published to
4 | # pub.dev using `flutter pub publish`. This is preferred for private packages.
5 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev
6 |
7 | # The following defines the version and build number for your application.
8 | # A version number is three numbers separated by dots, like 1.2.43
9 | # followed by an optional build number separated by a +.
10 | # Both the version and the builder number may be overridden in flutter
11 | # build by specifying --build-name and --build-number, respectively.
12 | # In Android, build-name is used as versionName while build-number used as versionCode.
13 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
14 | # In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
15 | # Read more about iOS versioning at
16 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
17 | # In Windows, build-name is used as the major, minor, and patch parts
18 | # of the product and file versions while build-number is used as the build suffix.
19 | version: 1.0.0+1
20 |
21 | environment:
22 | sdk: '>=3.1.0 <4.0.0'
23 |
24 | # Dependencies specify other packages that your package needs in order to work.
25 | # To automatically upgrade your package dependencies to the latest versions
26 | # consider running `flutter pub upgrade --major-versions`. Alternatively,
27 | # dependencies can be manually updated by changing the version numbers below to
28 | # the latest version available on pub.dev. To see which dependencies have newer
29 | # versions available, run `flutter pub outdated`.
30 | dependencies:
31 | flutter:
32 | sdk: flutter
33 |
34 |
35 | get: ^4.6.6
36 | logger: ^2.0.2
37 | mmkv: ^1.3.1
38 | dio: ^5.3.3
39 | easy_refresh: ^3.3.2+1
40 | # banner
41 | banner_carousel: ^1.2.1
42 | carousel_slider: ^4.2.1
43 | # cookie
44 | dio_cookie_manager: ^3.1.1
45 | # toast
46 | fluttertoast: ^8.2.2
47 | # webview
48 | webview_flutter: ^4.4.1
49 | # provider
50 | provider: ^6.0.5
51 | # 常用工具类 https://github.com/Sky24n/common_utils
52 | common_utils: ^2.1.0
53 | url_strategy: ^0.2.0
54 | # html解析
55 | flutter_widget_from_html: ^0.10.6
56 | flutter_html: ^3.0.0-beta.2
57 | # html: ^0.15.4
58 | url_launcher: ^6.1.14
59 | dio_cache_interceptor: ^3.4.4
60 | permission_handler: ^11.0.1
61 |
62 | # The following adds the Cupertino Icons font to your application.
63 | # Use with the CupertinoIcons class for iOS style icons.
64 | cupertino_icons: ^1.0.2
65 |
66 | dev_dependencies:
67 | flutter_test:
68 | sdk: flutter
69 |
70 | # The "flutter_lints" package below contains a set of recommended lints to
71 | # encourage good coding practices. The lint set provided by the package is
72 | # activated in the `analysis_options.yaml` file located at the root of your
73 | # package. See that file for information about deactivating specific lint
74 | # rules and activating additional ones.
75 | flutter_lints: ^2.0.0
76 |
77 | # For information on the generic Dart part of this file, see the
78 | # following page: https://dart.dev/tools/pub/pubspec
79 |
80 | # The following section is specific to Flutter packages.
81 | flutter:
82 |
83 | # The following line ensures that the Material Icons font is
84 | # included with your application, so that you can use the icons in
85 | # the material Icons class.
86 | uses-material-design: true
87 |
88 | # To add assets to your application, add an assets section, like this:
89 | assets:
90 | - assets/images/icon_collect.png
91 | - assets/images/icon_uncollect.png
92 | - assets/images/ic_default_avatar.png
93 | - assets/images/ic_finish.png
94 |
95 | # An image asset can refer to one or more resolution-specific "variants", see
96 | # https://flutter.dev/assets-and-images/#resolution-aware
97 |
98 | # For details regarding adding assets from package dependencies, see
99 | # https://flutter.dev/assets-and-images/#from-packages
100 |
101 | # To add custom fonts to your application, add a fonts section here,
102 | # in this "flutter" section. Each entry in this list should have a
103 | # "family" key with the font family name, and a "fonts" key with a
104 | # list giving the asset and other descriptors for the font. For
105 | # example:
106 | # fonts:
107 | # - family: Schyler
108 | # fonts:
109 | # - asset: fonts/Schyler-Regular.ttf
110 | # - asset: fonts/Schyler-Italic.ttf
111 | # style: italic
112 | # - family: Trajan Pro
113 | # fonts:
114 | # - asset: fonts/TrajanPro.ttf
115 | # - asset: fonts/TrajanPro_Bold.ttf
116 | # weight: 700
117 | #
118 | # For details regarding fonts from package dependencies,
119 | # see https://flutter.dev/custom-fonts/#from-packages
120 |
--------------------------------------------------------------------------------
/lib/generated/json/project_category_entity.g.dart:
--------------------------------------------------------------------------------
1 | import 'package:wan_android_flutter/generated/json/base/json_convert_content.dart';
2 | import 'package:wan_android_flutter/network/bean/project_category_entity.dart';
3 |
4 | ProjectCategoryEntity $ProjectCategoryEntityFromJson(
5 | Map json) {
6 | final ProjectCategoryEntity projectCategoryEntity = ProjectCategoryEntity();
7 | final List? articleList = (json['articleList'] as List?)
8 | ?.map(
9 | (e) => e)
10 | .toList();
11 | if (articleList != null) {
12 | projectCategoryEntity.articleList = articleList;
13 | }
14 | final String? author = jsonConvert.convert(json['author']);
15 | if (author != null) {
16 | projectCategoryEntity.author = author;
17 | }
18 | final List? children = (json['children'] as List?)?.map(
19 | (e) => e).toList();
20 | if (children != null) {
21 | projectCategoryEntity.children = children;
22 | }
23 | final int? courseId = jsonConvert.convert(json['courseId']);
24 | if (courseId != null) {
25 | projectCategoryEntity.courseId = courseId;
26 | }
27 | final String? cover = jsonConvert.convert(json['cover']);
28 | if (cover != null) {
29 | projectCategoryEntity.cover = cover;
30 | }
31 | final String? desc = jsonConvert.convert(json['desc']);
32 | if (desc != null) {
33 | projectCategoryEntity.desc = desc;
34 | }
35 | final int? id = jsonConvert.convert(json['id']);
36 | if (id != null) {
37 | projectCategoryEntity.id = id;
38 | }
39 | final String? lisense = jsonConvert.convert(json['lisense']);
40 | if (lisense != null) {
41 | projectCategoryEntity.lisense = lisense;
42 | }
43 | final String? lisenseLink = jsonConvert.convert(json['lisenseLink']);
44 | if (lisenseLink != null) {
45 | projectCategoryEntity.lisenseLink = lisenseLink;
46 | }
47 | final String? name = jsonConvert.convert(json['name']);
48 | if (name != null) {
49 | projectCategoryEntity.name = name;
50 | }
51 | final int? order = jsonConvert.convert(json['order']);
52 | if (order != null) {
53 | projectCategoryEntity.order = order;
54 | }
55 | final int? parentChapterId = jsonConvert.convert(
56 | json['parentChapterId']);
57 | if (parentChapterId != null) {
58 | projectCategoryEntity.parentChapterId = parentChapterId;
59 | }
60 | final int? type = jsonConvert.convert(json['type']);
61 | if (type != null) {
62 | projectCategoryEntity.type = type;
63 | }
64 | final bool? userControlSetTop = jsonConvert.convert(
65 | json['userControlSetTop']);
66 | if (userControlSetTop != null) {
67 | projectCategoryEntity.userControlSetTop = userControlSetTop;
68 | }
69 | final int? visible = jsonConvert.convert(json['visible']);
70 | if (visible != null) {
71 | projectCategoryEntity.visible = visible;
72 | }
73 | return projectCategoryEntity;
74 | }
75 |
76 | Map $ProjectCategoryEntityToJson(
77 | ProjectCategoryEntity entity) {
78 | final Map data = {};
79 | data['articleList'] = entity.articleList;
80 | data['author'] = entity.author;
81 | data['children'] = entity.children;
82 | data['courseId'] = entity.courseId;
83 | data['cover'] = entity.cover;
84 | data['desc'] = entity.desc;
85 | data['id'] = entity.id;
86 | data['lisense'] = entity.lisense;
87 | data['lisenseLink'] = entity.lisenseLink;
88 | data['name'] = entity.name;
89 | data['order'] = entity.order;
90 | data['parentChapterId'] = entity.parentChapterId;
91 | data['type'] = entity.type;
92 | data['userControlSetTop'] = entity.userControlSetTop;
93 | data['visible'] = entity.visible;
94 | return data;
95 | }
96 |
97 | extension ProjectCategoryEntityExtension on ProjectCategoryEntity {
98 | ProjectCategoryEntity copyWith({
99 | List? articleList,
100 | String? author,
101 | List? children,
102 | int? courseId,
103 | String? cover,
104 | String? desc,
105 | int? id,
106 | String? lisense,
107 | String? lisenseLink,
108 | String? name,
109 | int? order,
110 | int? parentChapterId,
111 | int? type,
112 | bool? userControlSetTop,
113 | int? visible,
114 | }) {
115 | return ProjectCategoryEntity()
116 | ..articleList = articleList ?? this.articleList
117 | ..author = author ?? this.author
118 | ..children = children ?? this.children
119 | ..courseId = courseId ?? this.courseId
120 | ..cover = cover ?? this.cover
121 | ..desc = desc ?? this.desc
122 | ..id = id ?? this.id
123 | ..lisense = lisense ?? this.lisense
124 | ..lisenseLink = lisenseLink ?? this.lisenseLink
125 | ..name = name ?? this.name
126 | ..order = order ?? this.order
127 | ..parentChapterId = parentChapterId ?? this.parentChapterId
128 | ..type = type ?? this.type
129 | ..userControlSetTop = userControlSetTop ?? this.userControlSetTop
130 | ..visible = visible ?? this.visible;
131 | }
132 | }
--------------------------------------------------------------------------------
/lib/pages/tabpage/plaza_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:easy_refresh/easy_refresh.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:fluttertoast/fluttertoast.dart';
4 | import 'package:get/get.dart';
5 | import 'package:provider/provider.dart';
6 | import 'package:wan_android_flutter/base/base_page.dart';
7 | import 'package:wan_android_flutter/network/api.dart';
8 | import 'package:wan_android_flutter/network/bean/AppResponse.dart';
9 | import 'package:wan_android_flutter/network/bean/article_data_entity.dart';
10 | import 'package:wan_android_flutter/network/request_util.dart';
11 | import 'package:wan_android_flutter/pages/article_item_layout.dart';
12 | import 'package:wan_android_flutter/pages/detail_page.dart';
13 | import 'package:wan_android_flutter/user.dart';
14 | import 'package:wan_android_flutter/utils/log_util.dart';
15 |
16 | class PlazaPage extends StatefulWidget {
17 | const PlazaPage({super.key});
18 |
19 | @override
20 | State createState() => _PlazaState();
21 | }
22 |
23 | class _PlazaState extends State
24 | with BasePage, AutomaticKeepAliveClientMixin {
25 | int _currentPageIndex = 0;
26 |
27 | List data = [];
28 |
29 | late RxList dataObs = data.obs;
30 |
31 | final EasyRefreshController _refreshController = EasyRefreshController(
32 | controlFinishRefresh: true, controlFinishLoad: true);
33 |
34 | Future _requestData() async {
35 | AppResponse res = await HttpGo.instance
36 | .get("${Api.plazaArticleList}$_currentPageIndex/json");
37 |
38 | bool isRefresh = _currentPageIndex == 0;
39 | if (isRefresh) {
40 | data.clear();
41 | }
42 | if (res.isSuccessful) {
43 | data.addAll(res.data!.datas);
44 | return true;
45 | }
46 | return false;
47 | }
48 |
49 | @override
50 | Widget build(BuildContext context) {
51 | super.build(context);
52 |
53 | // 监听user的状态,当登录状态改变时,重新build
54 | return Consumer(builder: (context, user, child) {
55 | return FutureBuilder(
56 | future: _requestData(),
57 | builder: (context, snapshot) {
58 | WanLog.d("plaza connect state: ${snapshot.connectionState}");
59 | if (snapshot.connectionState == ConnectionState.done) {
60 | if (snapshot.data == false) {
61 | return RetryWidget(onTapRetry: () {
62 | setState(() {});
63 | });
64 | }
65 | return _buildContent();
66 | } else {
67 | return const Center(
68 | widthFactor: 1,
69 | heightFactor: 1,
70 | child: CircularProgressIndicator(),
71 | );
72 | }
73 | });
74 | });
75 | }
76 |
77 | Widget _buildContent() {
78 | if (data.isEmpty) {
79 | return const EmptyWidget();
80 | }
81 | return EasyRefresh.builder(
82 | controller: _refreshController,
83 | onRefresh: _onRefresh,
84 | onLoad: _onLoad,
85 | childBuilder: (context, physics) {
86 | return Obx(() {
87 | // ignore: invalid_use_of_protected_member
88 | dataObs.value;
89 | return ListView.builder(
90 | physics: physics,
91 | itemBuilder: (context, index) {
92 | ArticleItemEntity itemEntity = data[index];
93 | return GestureDetector(
94 | onTap: () {
95 | Get.to(() => DetailPage(itemEntity.link, itemEntity.title));
96 | },
97 | child: ArticleItemLayout(
98 | itemEntity: itemEntity,
99 | onCollectTap: () {
100 | _onCollectClick(itemEntity);
101 | }),
102 | );
103 | },
104 | itemCount: data.length);
105 | });
106 | },
107 | );
108 | }
109 |
110 | _onRefresh() async {
111 | _currentPageIndex = 0;
112 | await _requestData();
113 | _refreshController.finishRefresh();
114 | dataObs.refresh();
115 | }
116 |
117 | _onLoad() async {
118 | _currentPageIndex++;
119 | await _requestData();
120 | _refreshController.finishLoad();
121 | dataObs.refresh();
122 | }
123 |
124 | _onCollectClick(ArticleItemEntity itemEntity) async {
125 | bool collected = itemEntity.collect;
126 | AppResponse res = await (collected
127 | ? HttpGo.instance.post("${Api.uncollectArticel}${itemEntity.id}/json")
128 | : HttpGo.instance.post("${Api.collectArticle}${itemEntity.id}/json"));
129 |
130 | if (res.isSuccessful) {
131 | Fluttertoast.showToast(msg: collected ? "取消收藏!" : "收藏成功!");
132 | itemEntity.collect = !itemEntity.collect;
133 | } else {
134 | Fluttertoast.showToast(
135 | msg: (collected ? "取消失败 -- " : "收藏失败 -- ") +
136 | (res.errorMsg ?? res.errorCode.toString()));
137 | }
138 | }
139 |
140 | @override
141 | bool get wantKeepAlive => true;
142 | }
143 |
--------------------------------------------------------------------------------
/lib/pages/login_register_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:fluttertoast/fluttertoast.dart';
3 | import 'package:wan_android_flutter/base/base_page.dart';
4 | import 'package:wan_android_flutter/network/api.dart';
5 | import 'package:wan_android_flutter/network/bean/AppResponse.dart';
6 | import 'package:wan_android_flutter/network/bean/user_info_entity.dart';
7 | import 'package:wan_android_flutter/network/request_util.dart';
8 | import 'package:wan_android_flutter/user.dart';
9 | import 'package:get/get.dart';
10 |
11 | class LoginRegisterPage extends StatefulWidget {
12 | // ignore: use_key_in_widget_constructors
13 | const LoginRegisterPage();
14 |
15 | @override
16 | State createState() => _LoginRegisterPageState();
17 | }
18 |
19 | class _LoginRegisterPageState extends State
20 | with BasePage {
21 | final TextEditingController nameTextController = TextEditingController();
22 |
23 | final TextEditingController passwordTextController = TextEditingController();
24 |
25 | final Key loginBtnKey = GlobalKey();
26 |
27 | final Key modeBtnKey = GlobalKey();
28 |
29 | final TextEditingController repasswordTextController =
30 | TextEditingController();
31 |
32 | bool isLogin = true;
33 |
34 | @override
35 | Widget build(BuildContext context) {
36 | return Scaffold(
37 | appBar: AppBar(
38 | title: const Text(
39 | "登录/注册",
40 | style: TextStyle(color: Colors.white),
41 | ),
42 | backgroundColor: Theme.of(context).primaryColor,
43 | iconTheme: const IconThemeData(color: Colors.white),
44 | ),
45 | body: Container(
46 | child: Column(
47 | children: [
48 | Container(
49 | padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
50 | child: TextField(
51 | controller: nameTextController,
52 | decoration: const InputDecoration(
53 | contentPadding:
54 | EdgeInsets.symmetric(vertical: 4, horizontal: 8),
55 | hintText: "用户名",
56 | border: OutlineInputBorder(
57 | borderRadius: BorderRadius.all(Radius.circular(4)))),
58 | ),
59 | ),
60 | Container(
61 | padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
62 | child: TextField(
63 | obscureText: true,
64 | controller: passwordTextController,
65 | decoration: const InputDecoration(
66 | contentPadding:
67 | EdgeInsets.symmetric(vertical: 4, horizontal: 8),
68 | hintText: "密码",
69 | border: OutlineInputBorder(
70 | borderRadius: BorderRadius.all(Radius.circular(4)))),
71 | ),
72 | ),
73 | if (!isLogin)
74 | Container(
75 | padding:
76 | const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
77 | child: TextField(
78 | obscureText: true,
79 | controller: repasswordTextController,
80 | decoration: const InputDecoration(
81 | contentPadding:
82 | EdgeInsets.symmetric(vertical: 4, horizontal: 8),
83 | hintText: "确认密码",
84 | border: OutlineInputBorder(
85 | borderRadius: BorderRadius.all(Radius.circular(4)))),
86 | ),
87 | ),
88 | Container(
89 | key: loginBtnKey,
90 | alignment: Alignment.center,
91 | child: TextButton(
92 | onPressed: _onLoginOrRegister,
93 | child: Text(isLogin ? "登录" : "注册")),
94 | ),
95 | Container(
96 | key: modeBtnKey,
97 | alignment: Alignment.center,
98 | child: TextButton(
99 | onPressed: _onChangeMode,
100 | child: Text(isLogin ? "没有账号?去注册" : "已有账号?去登录")),
101 | ),
102 | ],
103 | ),
104 | ),
105 | );
106 | }
107 |
108 | void _onChangeMode() {
109 | setState(() {
110 | isLogin = !isLogin;
111 | });
112 | }
113 |
114 | _onLoginOrRegister() async {
115 | FocusScope.of(context).unfocus();
116 | showLoadingDialog();
117 | var data = isLogin
118 | ? {
119 | "username": nameTextController.text.trim(),
120 | "password": passwordTextController.text.trim()
121 | }
122 | : {
123 | "username": nameTextController.text.trim(),
124 | "password": passwordTextController.text.trim(),
125 | "repassword": repasswordTextController.text.trim()
126 | };
127 |
128 | AppResponse res = await HttpGo.instance
129 | .post(isLogin ? Api.login : Api.register, data: data);
130 | dismissLoading();
131 |
132 | if (res.isSuccessful) {
133 | User().loginSuccess(res.data!);
134 | Fluttertoast.showToast(msg: isLogin ? "登录成功" : "注册成功");
135 | Get.back();
136 | } else {
137 | Fluttertoast.showToast(
138 | msg: isLogin ? "登录失败:${res.errorMsg}" : "注册失败:${res.errorMsg}");
139 | }
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/lib/generated/json/my_todo_data_entity.g.dart:
--------------------------------------------------------------------------------
1 | import 'package:wan_android_flutter/generated/json/base/json_convert_content.dart';
2 | import 'package:wan_android_flutter/network/bean/my_todo_data_entity.dart';
3 |
4 | MyTodoDataEntity $MyTodoDataEntityFromJson(Map json) {
5 | final MyTodoDataEntity myTodoDataEntity = MyTodoDataEntity();
6 | final int? curPage = jsonConvert.convert(json['curPage']);
7 | if (curPage != null) {
8 | myTodoDataEntity.curPage = curPage;
9 | }
10 | final List? datas = (json['datas'] as List?)
11 | ?.map(
12 | (e) => jsonConvert.convert(e) as MyTodoDataItem)
13 | .toList();
14 | if (datas != null) {
15 | myTodoDataEntity.datas = datas;
16 | }
17 | final int? offset = jsonConvert.convert(json['offset']);
18 | if (offset != null) {
19 | myTodoDataEntity.offset = offset;
20 | }
21 | final bool? over = jsonConvert.convert(json['over']);
22 | if (over != null) {
23 | myTodoDataEntity.over = over;
24 | }
25 | final int? pageCount = jsonConvert.convert(json['pageCount']);
26 | if (pageCount != null) {
27 | myTodoDataEntity.pageCount = pageCount;
28 | }
29 | final int? size = jsonConvert.convert(json['size']);
30 | if (size != null) {
31 | myTodoDataEntity.size = size;
32 | }
33 | final int? total = jsonConvert.convert(json['total']);
34 | if (total != null) {
35 | myTodoDataEntity.total = total;
36 | }
37 | return myTodoDataEntity;
38 | }
39 |
40 | Map $MyTodoDataEntityToJson(MyTodoDataEntity entity) {
41 | final Map data = {};
42 | data['curPage'] = entity.curPage;
43 | data['datas'] = entity.datas.map((v) => v.toJson()).toList();
44 | data['offset'] = entity.offset;
45 | data['over'] = entity.over;
46 | data['pageCount'] = entity.pageCount;
47 | data['size'] = entity.size;
48 | data['total'] = entity.total;
49 | return data;
50 | }
51 |
52 | extension MyTodoDataEntityExtension on MyTodoDataEntity {
53 | MyTodoDataEntity copyWith({
54 | int? curPage,
55 | List? datas,
56 | int? offset,
57 | bool? over,
58 | int? pageCount,
59 | int? size,
60 | int? total,
61 | }) {
62 | return MyTodoDataEntity()
63 | ..curPage = curPage ?? this.curPage
64 | ..datas = datas ?? this.datas
65 | ..offset = offset ?? this.offset
66 | ..over = over ?? this.over
67 | ..pageCount = pageCount ?? this.pageCount
68 | ..size = size ?? this.size
69 | ..total = total ?? this.total;
70 | }
71 | }
72 |
73 | MyTodoDataItem $MyTodoDataDatasFromJson(Map json) {
74 | final MyTodoDataItem myTodoDataDatas = MyTodoDataItem();
75 | final int? completeDate = jsonConvert.convert(json['completeDate']);
76 | if (completeDate != null) {
77 | myTodoDataDatas.completeDate = completeDate;
78 | }
79 | final String? completeDateStr = jsonConvert.convert(
80 | json['completeDateStr']);
81 | if (completeDateStr != null) {
82 | myTodoDataDatas.completeDateStr = completeDateStr;
83 | }
84 | final String? content = jsonConvert.convert(json['content']);
85 | if (content != null) {
86 | myTodoDataDatas.content = content;
87 | }
88 | final int? date = jsonConvert.convert(json['date']);
89 | if (date != null) {
90 | myTodoDataDatas.date = date;
91 | }
92 | final String? dateStr = jsonConvert.convert(json['dateStr']);
93 | if (dateStr != null) {
94 | myTodoDataDatas.dateStr = dateStr;
95 | }
96 | final int? id = jsonConvert.convert(json['id']);
97 | if (id != null) {
98 | myTodoDataDatas.id = id;
99 | }
100 | final int? priority = jsonConvert.convert(json['priority']);
101 | if (priority != null) {
102 | myTodoDataDatas.priority = priority;
103 | }
104 | final int? status = jsonConvert.convert(json['status']);
105 | if (status != null) {
106 | myTodoDataDatas.status = status;
107 | }
108 | final String? title = jsonConvert.convert(json['title']);
109 | if (title != null) {
110 | myTodoDataDatas.title = title;
111 | }
112 | final int? type = jsonConvert.convert(json['type']);
113 | if (type != null) {
114 | myTodoDataDatas.type = type;
115 | }
116 | final int? userId = jsonConvert.convert(json['userId']);
117 | if (userId != null) {
118 | myTodoDataDatas.userId = userId;
119 | }
120 | return myTodoDataDatas;
121 | }
122 |
123 | Map $MyTodoDataDatasToJson(MyTodoDataItem entity) {
124 | final Map data = {};
125 | data['completeDate'] = entity.completeDate;
126 | data['completeDateStr'] = entity.completeDateStr;
127 | data['content'] = entity.content;
128 | data['date'] = entity.date;
129 | data['dateStr'] = entity.dateStr;
130 | data['id'] = entity.id;
131 | data['priority'] = entity.priority;
132 | data['status'] = entity.status;
133 | data['title'] = entity.title;
134 | data['type'] = entity.type;
135 | data['userId'] = entity.userId;
136 | return data;
137 | }
138 |
139 | extension MyTodoDataDatasExtension on MyTodoDataItem {
140 | MyTodoDataItem copyWith({
141 | int? completeDate,
142 | String? completeDateStr,
143 | String? content,
144 | int? date,
145 | String? dateStr,
146 | int? id,
147 | int? priority,
148 | int? status,
149 | String? title,
150 | int? type,
151 | int? userId,
152 | }) {
153 | return MyTodoDataItem()
154 | ..completeDate = completeDate ?? this.completeDate
155 | ..completeDateStr = completeDateStr ?? this.completeDateStr
156 | ..content = content ?? this.content
157 | ..date = date ?? this.date
158 | ..dateStr = dateStr ?? this.dateStr
159 | ..id = id ?? this.id
160 | ..priority = priority ?? this.priority
161 | ..status = status ?? this.status
162 | ..title = title ?? this.title
163 | ..type = type ?? this.type
164 | ..userId = userId ?? this.userId;
165 | }
166 | }
--------------------------------------------------------------------------------
/lib/pages/article_item_layout.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_html/flutter_html.dart';
3 | import 'package:wan_android_flutter/network/bean/article_data_entity.dart';
4 |
5 | class ArticleItemLayout extends StatefulWidget {
6 | const ArticleItemLayout(
7 | {Key? key,
8 | required this.itemEntity,
9 | required this.onCollectTap,
10 | this.showCollectBtn})
11 | : super(key: key);
12 |
13 | final ArticleItemEntity itemEntity;
14 |
15 | final void Function() onCollectTap;
16 |
17 | final bool? showCollectBtn;
18 |
19 | @override
20 | State createState() => _ArticleItemState();
21 | }
22 |
23 | class _ArticleItemState extends State {
24 | @override
25 | void initState() {
26 | super.initState();
27 | widget.itemEntity.addListener(_onCollectChange);
28 | }
29 |
30 | @override
31 | void didUpdateWidget(ArticleItemLayout oldWidget) {
32 | super.didUpdateWidget(oldWidget);
33 | if (oldWidget.itemEntity != widget.itemEntity) {
34 | oldWidget.itemEntity.removeListener(_onCollectChange);
35 | widget.itemEntity.addListener(_onCollectChange);
36 | }
37 | }
38 |
39 | @override
40 | void dispose() {
41 | super.dispose();
42 | widget.itemEntity.removeListener(_onCollectChange);
43 | }
44 |
45 | _onCollectChange() {
46 | setState(() {});
47 | }
48 |
49 | @override
50 | Widget build(BuildContext context) {
51 | String publishTime =
52 | DateTime.fromMillisecondsSinceEpoch(widget.itemEntity.publishTime)
53 | .toString();
54 | publishTime = publishTime.substring(0, publishTime.length - 4);
55 | StringBuffer sb = StringBuffer(widget.itemEntity.superChapterName ?? "");
56 | if (sb.isNotEmpty &&
57 | widget.itemEntity.chapterName != null &&
58 | widget.itemEntity.chapterName!.isNotEmpty) {
59 | sb.write("·");
60 | }
61 | sb.write(widget.itemEntity.chapterName ?? "");
62 | return Container(
63 | padding: const EdgeInsets.symmetric(horizontal: 8),
64 | child: Card(
65 | surfaceTintColor: Colors.white,
66 | color: Colors.white,
67 | elevation: 8,
68 | child: Padding(
69 | padding: const EdgeInsets.all(8),
70 | child: Column(
71 | children: [
72 | Row(
73 | children: [
74 | if (widget.itemEntity.type == 1)
75 | const Padding(
76 | padding: EdgeInsets.only(left: 8),
77 | child: Text(
78 | "置顶",
79 | style: TextStyle(color: Colors.red),
80 | )),
81 | Container(
82 | padding: widget.itemEntity.type == 1
83 | ? const EdgeInsets.fromLTRB(8, 0, 0, 0)
84 | : const EdgeInsets.fromLTRB(12, 0, 0, 0),
85 | child: Text(widget.itemEntity.author?.isNotEmpty == true
86 | ? widget.itemEntity.author!
87 | : widget.itemEntity.shareUser ?? ""),
88 | ),
89 | Expanded(
90 | child: Container(
91 | padding: const EdgeInsets.only(right: 8),
92 | alignment: Alignment.centerRight,
93 | child: Text(publishTime),
94 | ),
95 | )
96 | ],
97 | ),
98 | Container(
99 | padding: const EdgeInsets.fromLTRB(10, 8, 8, 8),
100 | child: Row(
101 | children: [
102 | Expanded(
103 | child: Html(
104 | data: widget.itemEntity.title,
105 | style: {
106 | "html": Style(
107 | margin: Margins.zero,
108 | maxLines: 2,
109 | textOverflow: TextOverflow.ellipsis,
110 | fontSize: FontSize(14),
111 | padding: HtmlPaddings.zero,
112 | alignment: Alignment.topLeft),
113 | "body": Style(
114 | margin: Margins.zero,
115 | maxLines: 2,
116 | textOverflow: TextOverflow.ellipsis,
117 | fontSize: FontSize(14),
118 | padding: HtmlPaddings.zero,
119 | alignment: Alignment.topLeft)
120 | },
121 | ))
122 | ],
123 | ),
124 | ),
125 | Row(
126 | children: [
127 | Padding(
128 | padding: const EdgeInsets.only(left: 10),
129 | child: Text(sb.toString())),
130 | Expanded(
131 | child: Container(
132 | width: 24,
133 | height: 24,
134 | alignment: Alignment.topRight,
135 | padding: const EdgeInsets.only(right: 8),
136 | child: Builder(builder: (context) {
137 | if (widget.showCollectBtn == false) {
138 | return Container();
139 | }
140 | return GestureDetector(
141 | onTap: widget.onCollectTap,
142 | child: Image.asset(widget.itemEntity.collect
143 | ? "assets/images/icon_collect.png"
144 | : "assets/images/icon_uncollect.png"),
145 | );
146 | })))
147 | ],
148 | )
149 | ],
150 | ),
151 | ),
152 | ));
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/lib/pages/my_todo_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:easy_refresh/easy_refresh.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:get/get.dart';
4 | import 'package:wan_android_flutter/network/api.dart';
5 | import 'package:wan_android_flutter/network/bean/AppResponse.dart';
6 | import 'package:wan_android_flutter/network/bean/my_todo_data_entity.dart';
7 | import 'package:wan_android_flutter/network/request_util.dart';
8 |
9 | import '../base/base_page.dart';
10 |
11 | const List typeList = ["工作", "生活", "编程", "面试", "娱乐", "剪辑"];
12 | const List levelList = ["低", "中", "高"];
13 |
14 | class MyTodoListPage extends StatefulWidget {
15 | const MyTodoListPage({Key? key}) : super(key: key);
16 |
17 | @override
18 | State createState() => _MyTodoListPageState();
19 | }
20 |
21 | class _MyTodoListPageState extends State {
22 | final EasyRefreshController _refreshController = EasyRefreshController(
23 | controlFinishRefresh: true, controlFinishLoad: true);
24 |
25 | final List _data = [];
26 |
27 | int _currentIndex = 1;
28 |
29 | late final _dataObs = _data.obs;
30 |
31 | Future _requestData() async {
32 | AppResponse res =
33 | await HttpGo.instance.get("${Api.todoList}$_currentIndex/json");
34 | bool isRefresh = _currentIndex == 1;
35 | if (isRefresh) {
36 | _data.clear();
37 | }
38 | if (res.isSuccessful) {
39 | _data.addAll(res.data!.datas);
40 | return true;
41 | }
42 | return false;
43 | }
44 |
45 | @override
46 | Widget build(BuildContext context) {
47 | return Scaffold(
48 | appBar: AppBar(
49 | title: const Text("我的待办", style: TextStyle(color: Colors.white)),
50 | backgroundColor: Theme.of(context).primaryColor,
51 | iconTheme: const IconThemeData(color: Colors.white),
52 | ),
53 | body: SizedBox(
54 | width: double.infinity,
55 | height: double.infinity,
56 | child: _getBody()));
57 | }
58 |
59 | Widget _getBody() {
60 | return FutureBuilder(
61 | future: _requestData(),
62 | builder: (context, snapshot) {
63 | if (snapshot.connectionState == ConnectionState.done) {
64 | if (snapshot.data == false) {
65 | return RetryWidget(onTapRetry: () {
66 | setState(() {});
67 | });
68 | }
69 | return _buildContent();
70 | } else {
71 | return const Center(
72 | widthFactor: 1,
73 | heightFactor: 1,
74 | child: CircularProgressIndicator(),
75 | );
76 | }
77 | });
78 | }
79 |
80 | Widget _buildContent() {
81 | if (_data.isEmpty) {
82 | return const EmptyWidget();
83 | }
84 | return EasyRefresh.builder(
85 | controller: _refreshController,
86 | onRefresh: _onRefresh,
87 | onLoad: _onLoad,
88 | childBuilder: (context, physics) {
89 | return Padding(
90 | padding: const EdgeInsets.only(left: 16, right: 16),
91 | child: Obx(() {
92 | // ignore: invalid_use_of_protected_member
93 | _dataObs.value;
94 | return ListView.builder(
95 | physics: physics,
96 | itemBuilder: (context, index) {
97 | MyTodoDataItem item = _data[index];
98 | return _generateItemLayout(item);
99 | },
100 | itemCount: _data.length,
101 | );
102 | }));
103 | },
104 | );
105 | }
106 |
107 | _onRefresh() async {
108 | _currentIndex = 1;
109 | await _requestData();
110 | _refreshController.finishRefresh();
111 | _dataObs.refresh();
112 | }
113 |
114 | _onLoad() async {
115 | _currentIndex++;
116 | await _requestData();
117 | _refreshController.finishLoad();
118 | _dataObs.refresh();
119 | }
120 |
121 | Widget _generateItemLayout(MyTodoDataItem item) {
122 | bool isFinished = item.status == 1;
123 | return Card(
124 | surfaceTintColor: Colors.white,
125 | color: Colors.white,
126 | elevation: 4,
127 | child: Stack(
128 | alignment: Alignment.center,
129 | children: [
130 | Positioned(
131 | child: Container(
132 | height: 100,
133 | width: double.infinity,
134 | padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
135 | child: Row(
136 | mainAxisSize: MainAxisSize.max,
137 | mainAxisAlignment: MainAxisAlignment.start,
138 | children: [
139 | Expanded(
140 | child: Column(
141 | crossAxisAlignment: CrossAxisAlignment.start,
142 | children: [
143 | Text(
144 | item.title,
145 | style: const TextStyle(fontSize: 16),
146 | ),
147 | Row(
148 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
149 | children: [
150 | Text(item.dateStr),
151 | Text(typeList[
152 | ((item.type ?? 1) - 1) % typeList.length]),
153 | Text(levelList[
154 | ((item.priority ?? 1) - 1) % levelList.length])
155 | ],
156 | )
157 | ],
158 | ),
159 | ),
160 | Column(
161 | children: [
162 | TextButton(onPressed: () {}, child: const Text("标记未完成")),
163 | // TextButton(onPressed: () {}, child: Text("删除"))
164 | ],
165 | )
166 | ],
167 | ),
168 | )),
169 | if (isFinished)
170 | Positioned(
171 | child: Image.asset(
172 | "assets/images/ic_finish.png",
173 | width: 80,
174 | height: 80,
175 | ))
176 | ],
177 | ));
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/lib/generated/json/my_shared_data_entity.g.dart:
--------------------------------------------------------------------------------
1 | import 'package:wan_android_flutter/generated/json/base/json_convert_content.dart';
2 | import 'package:wan_android_flutter/network/bean/my_shared_data_entity.dart';
3 | import 'package:wan_android_flutter/network/bean/article_data_entity.dart';
4 |
5 |
6 | MySharedDataEntity $MySharedDataEntityFromJson(Map json) {
7 | final MySharedDataEntity mySharedDataEntity = MySharedDataEntity();
8 | final MySharedDataCoinInfo? coinInfo = jsonConvert.convert<
9 | MySharedDataCoinInfo>(json['coinInfo']);
10 | if (coinInfo != null) {
11 | mySharedDataEntity.coinInfo = coinInfo;
12 | }
13 | final MySharedDataShareArticles? shareArticles = jsonConvert.convert<
14 | MySharedDataShareArticles>(json['shareArticles']);
15 | if (shareArticles != null) {
16 | mySharedDataEntity.shareArticles = shareArticles;
17 | }
18 | return mySharedDataEntity;
19 | }
20 |
21 | Map $MySharedDataEntityToJson(MySharedDataEntity entity) {
22 | final Map data = {};
23 | data['coinInfo'] = entity.coinInfo.toJson();
24 | data['shareArticles'] = entity.shareArticles.toJson();
25 | return data;
26 | }
27 |
28 | extension MySharedDataEntityExtension on MySharedDataEntity {
29 | MySharedDataEntity copyWith({
30 | MySharedDataCoinInfo? coinInfo,
31 | MySharedDataShareArticles? shareArticles,
32 | }) {
33 | return MySharedDataEntity()
34 | ..coinInfo = coinInfo ?? this.coinInfo
35 | ..shareArticles = shareArticles ?? this.shareArticles;
36 | }
37 | }
38 |
39 | MySharedDataCoinInfo $MySharedDataCoinInfoFromJson(Map json) {
40 | final MySharedDataCoinInfo mySharedDataCoinInfo = MySharedDataCoinInfo();
41 | final int? coinCount = jsonConvert.convert(json['coinCount']);
42 | if (coinCount != null) {
43 | mySharedDataCoinInfo.coinCount = coinCount;
44 | }
45 | final int? level = jsonConvert.convert(json['level']);
46 | if (level != null) {
47 | mySharedDataCoinInfo.level = level;
48 | }
49 | final String? nickname = jsonConvert.convert(json['nickname']);
50 | if (nickname != null) {
51 | mySharedDataCoinInfo.nickname = nickname;
52 | }
53 | final String? rank = jsonConvert.convert(json['rank']);
54 | if (rank != null) {
55 | mySharedDataCoinInfo.rank = rank;
56 | }
57 | final int? userId = jsonConvert.convert(json['userId']);
58 | if (userId != null) {
59 | mySharedDataCoinInfo.userId = userId;
60 | }
61 | final String? username = jsonConvert.convert(json['username']);
62 | if (username != null) {
63 | mySharedDataCoinInfo.username = username;
64 | }
65 | return mySharedDataCoinInfo;
66 | }
67 |
68 | Map $MySharedDataCoinInfoToJson(MySharedDataCoinInfo entity) {
69 | final Map data = {};
70 | data['coinCount'] = entity.coinCount;
71 | data['level'] = entity.level;
72 | data['nickname'] = entity.nickname;
73 | data['rank'] = entity.rank;
74 | data['userId'] = entity.userId;
75 | data['username'] = entity.username;
76 | return data;
77 | }
78 |
79 | extension MySharedDataCoinInfoExtension on MySharedDataCoinInfo {
80 | MySharedDataCoinInfo copyWith({
81 | int? coinCount,
82 | int? level,
83 | String? nickname,
84 | String? rank,
85 | int? userId,
86 | String? username,
87 | }) {
88 | return MySharedDataCoinInfo()
89 | ..coinCount = coinCount ?? this.coinCount
90 | ..level = level ?? this.level
91 | ..nickname = nickname ?? this.nickname
92 | ..rank = rank ?? this.rank
93 | ..userId = userId ?? this.userId
94 | ..username = username ?? this.username;
95 | }
96 | }
97 |
98 | MySharedDataShareArticles $MySharedDataShareArticlesFromJson(
99 | Map json) {
100 | final MySharedDataShareArticles mySharedDataShareArticles = MySharedDataShareArticles();
101 | final int? curPage = jsonConvert.convert(json['curPage']);
102 | if (curPage != null) {
103 | mySharedDataShareArticles.curPage = curPage;
104 | }
105 | final List? datas = (json['datas'] as List?)
106 | ?.map(
107 | (e) => jsonConvert.convert(e) as ArticleItemEntity)
108 | .toList();
109 | if (datas != null) {
110 | mySharedDataShareArticles.datas = datas;
111 | }
112 | final int? offset = jsonConvert.convert(json['offset']);
113 | if (offset != null) {
114 | mySharedDataShareArticles.offset = offset;
115 | }
116 | final bool? over = jsonConvert.convert(json['over']);
117 | if (over != null) {
118 | mySharedDataShareArticles.over = over;
119 | }
120 | final int? pageCount = jsonConvert.convert(json['pageCount']);
121 | if (pageCount != null) {
122 | mySharedDataShareArticles.pageCount = pageCount;
123 | }
124 | final int? size = jsonConvert.convert(json['size']);
125 | if (size != null) {
126 | mySharedDataShareArticles.size = size;
127 | }
128 | final int? total = jsonConvert.convert(json['total']);
129 | if (total != null) {
130 | mySharedDataShareArticles.total = total;
131 | }
132 | return mySharedDataShareArticles;
133 | }
134 |
135 | Map $MySharedDataShareArticlesToJson(
136 | MySharedDataShareArticles entity) {
137 | final Map data = {};
138 | data['curPage'] = entity.curPage;
139 | data['datas'] = entity.datas.map((v) => v.toJson()).toList();
140 | data['offset'] = entity.offset;
141 | data['over'] = entity.over;
142 | data['pageCount'] = entity.pageCount;
143 | data['size'] = entity.size;
144 | data['total'] = entity.total;
145 | return data;
146 | }
147 |
148 | extension MySharedDataShareArticlesExtension on MySharedDataShareArticles {
149 | MySharedDataShareArticles copyWith({
150 | int? curPage,
151 | List? datas,
152 | int? offset,
153 | bool? over,
154 | int? pageCount,
155 | int? size,
156 | int? total,
157 | }) {
158 | return MySharedDataShareArticles()
159 | ..curPage = curPage ?? this.curPage
160 | ..datas = datas ?? this.datas
161 | ..offset = offset ?? this.offset
162 | ..over = over ?? this.over
163 | ..pageCount = pageCount ?? this.pageCount
164 | ..size = size ?? this.size
165 | ..total = total ?? this.total;
166 | }
167 | }
--------------------------------------------------------------------------------
/lib/network/request_util.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'dart:io';
3 |
4 | import 'package:dio/dio.dart';
5 | import 'package:dio_cookie_manager/dio_cookie_manager.dart';
6 | import 'package:cookie_jar/cookie_jar.dart';
7 | import "package:path_provider/path_provider.dart";
8 | import 'package:wan_android_flutter/user.dart';
9 | import 'package:wan_android_flutter/utils/log_util.dart';
10 | import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
11 |
12 | import '../constants/constants.dart';
13 | import 'bean/AppResponse.dart';
14 |
15 | // 是否用compute异步
16 | bool useCompute = false;
17 | bool configured = false;
18 |
19 | Duration _connectTimeout = const Duration(seconds: 10);
20 | Duration _receiveTimeout = const Duration(seconds: 10);
21 | Duration _sendTimeout = const Duration(seconds: 10);
22 | String _baseUrl = "";
23 | List _interceptors = [];
24 |
25 | void configDio({
26 | Duration? connectTimeout,
27 | Duration? receiveTimeout,
28 | Duration? sendTimeout,
29 | String? baseUrl,
30 | List? interceptors,
31 | }) {
32 | configured = true;
33 | _connectTimeout = connectTimeout ?? _connectTimeout;
34 | _receiveTimeout = receiveTimeout ?? _receiveTimeout;
35 | _sendTimeout = sendTimeout ?? _sendTimeout;
36 | _baseUrl = baseUrl ?? _baseUrl;
37 | _interceptors = interceptors ?? _interceptors;
38 | }
39 |
40 | class HttpGo {
41 | late Dio _dio;
42 |
43 | static final HttpGo _singleton = HttpGo._internal();
44 |
45 | static HttpGo get instance => _singleton;
46 |
47 | CookieJar? cookieJar;
48 |
49 | HttpGo._internal() {
50 | if (!configured) {
51 | WanLog.w("you have not config the dio!");
52 | }
53 |
54 | // Global options
55 | final options = CacheOptions(
56 | // A default store is required for interceptor.
57 | store: MemCacheStore(),
58 |
59 | // All subsequent fields are optional.
60 |
61 | // Default.
62 | policy: CachePolicy.request,
63 | // Returns a cached response on error but for statuses 401 & 403.
64 | // Also allows to return a cached response on network errors (e.g. offline usage).
65 | // Defaults to [null].
66 | hitCacheOnErrorExcept: [401, 403],
67 | // Overrides any HTTP directive to delete entry past this duration.
68 | // Useful only when origin server has no cache config or custom behaviour is desired.
69 | // Defaults to [null].
70 | maxStale: const Duration(days: 7),
71 | // Default. Allows 3 cache sets and ease cleanup.
72 | priority: CachePriority.normal,
73 | // Default. Body and headers encryption with your own algorithm.
74 | cipher: null,
75 | // Default. Key builder to retrieve requests.
76 | keyBuilder: CacheOptions.defaultCacheKeyBuilder,
77 | // Default. Allows to cache POST requests.
78 | // Overriding [keyBuilder] is strongly recommended when [true].
79 | allowPostMethod: false,
80 | );
81 |
82 | _dio = Dio(BaseOptions(
83 | //请求的Content-Type,默认值是"application/json; charset=utf-8",Headers.formUrlEncodedContentType会自动编码请求体.
84 | contentType: Headers.formUrlEncodedContentType,
85 | responseType: ResponseType.plain,
86 | baseUrl: _baseUrl,
87 | connectTimeout: _connectTimeout,
88 | receiveTimeout: _receiveTimeout,
89 | sendTimeout: _sendTimeout));
90 | Future dirResult = getApplicationDocumentsDirectory();
91 | dirResult.then((value) {
92 | CookieManager cookieManager = CookieManager(
93 | PersistCookieJar(storage: FileStorage("${value.path}/.cookies/")));
94 | cookieJar = cookieManager.cookieJar;
95 | _dio.interceptors.add(cookieManager);
96 | });
97 | // _dio.interceptors.add(DioCacheInterceptor(options: options));
98 | _dio.interceptors.addAll(_interceptors);
99 | }
100 |
101 | Future> request(String url, String method,
102 | {Object? data,
103 | Map? queryParams,
104 | CancelToken? cancelToken,
105 | Options? options,
106 | ProgressCallback? progressCallback,
107 | ProgressCallback? receiveCallback}) async {
108 | AppResponse result;
109 | try {
110 | Response response = await _dio.request(url,
111 | data: data,
112 | queryParameters: queryParams,
113 | cancelToken: cancelToken,
114 | options: (options ?? Options())..method = method,
115 | onSendProgress: progressCallback,
116 | onReceiveProgress: receiveCallback);
117 | Map map = json.decode(response.data.toString());
118 | result = AppResponse.fromJson(map);
119 | if (result.errorCode == Constant.invalidateToken) {
120 | User().logout();
121 | }
122 | } on DioException catch (error) {
123 | WanLog.e("request error-- $error");
124 | result = AppResponse(Constant.otherError, error.message, null);
125 | }
126 | return result;
127 | }
128 |
129 | Future> get(String url,
130 | {Object? data,
131 | Map? queryParams,
132 | CancelToken? cancelToken,
133 | Options? options,
134 | ProgressCallback? progressCallback,
135 | ProgressCallback? receiveCallback}) async {
136 | return request(url, "GET",
137 | data: data,
138 | queryParams: queryParams,
139 | cancelToken: cancelToken,
140 | options: options,
141 | progressCallback: progressCallback,
142 | receiveCallback: receiveCallback);
143 | }
144 |
145 | Future> post(String url,
146 | {Object? data,
147 | Map? queryParams,
148 | CancelToken? cancelToken,
149 | Options? options,
150 | ProgressCallback? progressCallback,
151 | ProgressCallback? receiveCallback}) async {
152 | return request(url, "POST",
153 | data: data,
154 | queryParams: queryParams,
155 | cancelToken: cancelToken,
156 | options: options,
157 | progressCallback: progressCallback,
158 | receiveCallback: receiveCallback);
159 | }
160 |
161 | Future> delete(String url,
162 | {Object? data,
163 | Map? queryParams,
164 | CancelToken? cancelToken,
165 | Options? options,
166 | ProgressCallback? progressCallback,
167 | ProgressCallback? receiveCallback}) async {
168 | return request(url, "DELETE",
169 | data: data,
170 | queryParams: queryParams,
171 | cancelToken: cancelToken,
172 | options: options,
173 | progressCallback: progressCallback,
174 | receiveCallback: receiveCallback);
175 | }
176 |
177 | Future> put(String url,
178 | {Object? data,
179 | Map? queryParams,
180 | CancelToken? cancelToken,
181 | Options? options,
182 | ProgressCallback? progressCallback,
183 | ProgressCallback? receiveCallback}) async {
184 | return request(url, "PUT",
185 | data: data,
186 | queryParams: queryParams,
187 | cancelToken: cancelToken,
188 | options: options,
189 | progressCallback: progressCallback,
190 | receiveCallback: receiveCallback);
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/lib/pages/tabpage/project_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:easy_refresh/easy_refresh.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_html/flutter_html.dart';
4 | import 'package:get/get.dart';
5 | import 'package:wan_android_flutter/base/base_page.dart';
6 | import 'package:wan_android_flutter/network/api.dart';
7 | import 'package:wan_android_flutter/network/bean/AppResponse.dart';
8 | import 'package:wan_android_flutter/network/bean/project_category_entity.dart';
9 | import 'package:wan_android_flutter/network/bean/project_list_data_entity.dart';
10 | import 'package:wan_android_flutter/network/request_util.dart';
11 | import 'package:wan_android_flutter/pages/detail_page.dart';
12 |
13 | class ProjectPage extends StatefulWidget {
14 | const ProjectPage({super.key});
15 |
16 | @override
17 | State createState() => _ProjectPageState();
18 | }
19 |
20 | class _ProjectPageState extends State
21 | with
22 | BasePage,
23 | AutomaticKeepAliveClientMixin,
24 | SingleTickerProviderStateMixin {
25 | List _tabs = [];
26 |
27 | TabController? tabController;
28 |
29 | @override
30 | void initState() {
31 | super.initState();
32 | HttpGo.instance
33 | .get>(Api.projectCategory)
34 | .then((res) {
35 | if (res.isSuccessful) {
36 | _tabs = res.data!;
37 | }
38 | setState(() {});
39 | });
40 | }
41 |
42 | @override
43 | Widget build(BuildContext context) {
44 | super.build(context);
45 |
46 | if (_tabs.isEmpty) {
47 | return const Center(
48 | widthFactor: 1, heightFactor: 1, child: CircularProgressIndicator());
49 | }
50 |
51 | tabController ??= TabController(length: _tabs.length, vsync: this);
52 |
53 | return Scaffold(
54 | appBar: AppBar(
55 | toolbarHeight: 0,
56 | bottom: TabBar(
57 | isScrollable: true,
58 | tabs: _tabs.map((e) {
59 | return Tab(text: e.name);
60 | }).toList(),
61 | controller: tabController,
62 | ),
63 | ),
64 | body: TabBarView(
65 | controller: tabController,
66 | children: _tabs.map((e) {
67 | return ProjectListPage(e.id);
68 | }).toList()),
69 | );
70 | }
71 |
72 | @override
73 | bool get wantKeepAlive => true;
74 | }
75 |
76 | class ProjectListPage extends StatefulWidget {
77 | const ProjectListPage(this.cid, {super.key});
78 |
79 | final int cid;
80 |
81 | @override
82 | // ignore: no_logic_in_create_state
83 | State createState() => _ProjectListPageState(cid);
84 | }
85 |
86 | class _ProjectListPageState extends State
87 | with BasePage {
88 | _ProjectListPageState(this.cid);
89 |
90 | final int cid;
91 |
92 | int _currentPageIndex = 1;
93 |
94 | List