├── .flutter-plugins-dependencies ├── .gitignore ├── .metadata ├── .travis.yml ├── README.md ├── android ├── app │ ├── build.gradle │ ├── release │ │ ├── app-release.apk │ │ └── output.json │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ └── com │ │ │ └── example │ │ │ └── flutterwanandroid │ │ │ └── MainActivity.kt │ │ └── res │ │ ├── drawable │ │ └── launch_background.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_2.png │ │ └── splash.png │ │ ├── values │ │ └── styles.xml │ │ └── xml │ │ ├── network_security_config.xml │ │ └── provider_paths.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── key.properties ├── res │ └── values │ │ └── strings_en.arb ├── settings.gradle └── settings_aar.gradle ├── assets ├── app.db └── images │ ├── 23038-animatonblue.gif │ ├── 7903-error-404.gif │ ├── 8021-empty-and-lost.gif │ ├── calendar.png │ ├── house.png │ ├── p1.png │ ├── p2.png │ ├── p3.png │ ├── paimaiLogo.png │ └── plane.png ├── ios ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── flutter_export_environment.sh ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ └── contents.xcworkspacedata └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h ├── lib ├── api │ ├── Api.dart │ ├── common_service.dart │ ├── dio_manager.dart │ ├── error_entity.dart │ └── http.dart ├── components │ ├── cate_card.dart │ ├── cate_card_container.dart │ ├── cate_card_item.dart │ ├── category.dart │ ├── disclaimer_msg.dart │ ├── home_banner.dart │ ├── list_refresh.dart │ ├── list_view_item.dart │ ├── navi_list.dart │ ├── pagination.dart │ ├── project_list.dart │ └── search_input.dart ├── constant │ ├── color_config.dart │ └── shared_preferences_keys.dart ├── event │ ├── event_bus.dart │ ├── event_model.dart │ └── event_theme.dart ├── generated │ └── i18n.dart ├── init │ ├── app.dart │ └── app_init.dart ├── main.dart ├── main_page.dart ├── model │ ├── article.dart │ ├── banner.dart │ ├── cat.dart │ ├── coin.dart │ ├── collect.dart │ ├── constant.dart │ ├── navi_bean.dart │ ├── project_model.dart │ ├── provider │ │ └── state_provider.dart │ ├── search_history.dart │ ├── splash.dart │ ├── store.dart │ ├── theme.dart │ ├── tree_list.dart │ ├── user.dart │ └── user_model.dart ├── routers │ ├── application.dart │ ├── navigation_service.dart │ ├── router.dart │ ├── router_handler.dart │ └── router_path.dart ├── utils │ ├── bugly.dart │ ├── event.dart │ ├── image.dart │ ├── provider.dart │ ├── push.dart │ ├── random.dart │ ├── shared_preferences.dart │ ├── sql.dart │ ├── style.dart │ ├── toast.dart │ └── update_dialog.dart ├── views │ ├── about_page │ │ ├── about_page.dart │ │ ├── page_dragger.dart │ │ ├── page_reveal.dart │ │ ├── pager_indicator.dart │ │ └── pages.dart │ ├── article_list_page │ │ └── article_list_page.dart │ ├── cat_page │ │ ├── cat_page.dart │ │ └── cat_sub_page.dart │ ├── coin_rank_page │ │ └── coin_rank_page.dart │ ├── collection_page │ │ └── collection_page.dart │ ├── home_page │ │ └── home_page.dart │ ├── login_page │ │ └── login_page.dart │ ├── mine_page │ │ └── mine_page.dart │ ├── my_collect_list_page │ │ └── my_collect_list_page.dart │ ├── page_not_found.dart │ ├── photo_detail_page │ │ └── photo_detail_page.dart │ ├── photo_page │ │ ├── item_card.dart │ │ └── photo_page.dart │ ├── register_page │ │ └── register_page.dart │ ├── search_page │ │ └── search_page.dart │ ├── splash_page │ │ └── SplashPage.dart │ └── web_page │ │ └── webview_page.dart └── widgets │ ├── error │ ├── error_page.dart │ └── flutter_crash_plugin.dart │ ├── list_item.dart │ ├── loading │ ├── dialog_manager.dart │ └── loading_dialog.dart │ └── state │ ├── empty_view.dart │ └── load_state.dart ├── pubspec.yaml ├── res └── values │ ├── strings_en.arb │ └── strings_zh.arb ├── screenshot ├── about.png ├── cat.png ├── change-color.png ├── home.png ├── login.png ├── mine.png ├── my-collect.png ├── project.png ├── rank.png ├── splash.png ├── theme.png └── web-detail.png └── test └── widget_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.lock 4 | *.log 5 | *.pyc 6 | *.swp 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # Visual Studio Code related 20 | .vscode/ 21 | 22 | # Flutter/Dart/Pub related 23 | **/doc/api/ 24 | .dart_tool/ 25 | .flutter-plugins 26 | .packages 27 | .pub-cache/ 28 | .pub/ 29 | build/ 30 | 31 | # Android related 32 | **/android/**/gradle-wrapper.jar 33 | **/android/.gradle 34 | **/android/captures/ 35 | **/android/gradlew 36 | **/android/gradlew.bat 37 | **/android/local.properties 38 | **/android/**/GeneratedPluginRegistrant.java 39 | 40 | # iOS/XCode related 41 | **/ios/**/*.mode1v3 42 | **/ios/**/*.mode2v3 43 | **/ios/**/*.moved-aside 44 | **/ios/**/*.pbxuser 45 | **/ios/**/*.perspectivev3 46 | **/ios/**/*sync/ 47 | **/ios/**/.sconsign.dblite 48 | **/ios/**/.tags* 49 | **/ios/**/.vagrant/ 50 | **/ios/**/DerivedData/ 51 | **/ios/**/Icon? 52 | **/ios/**/Pods/ 53 | **/ios/**/.symlinks/ 54 | **/ios/**/profile 55 | **/ios/**/xcuserdata 56 | **/ios/.generated/ 57 | **/ios/Flutter/App.framework 58 | **/ios/Flutter/Flutter.framework 59 | **/ios/Flutter/Generated.xcconfig 60 | **/ios/Flutter/app.flx 61 | **/ios/Flutter/app.zip 62 | **/ios/Flutter/flutter_assets/ 63 | **/ios/ServiceDefinitions.json 64 | **/ios/Runner/GeneratedPluginRegistrant.* 65 | 66 | # Exceptions to above rules. 67 | !**/ios/**/default.mode1v3 68 | !**/ios/**/default.mode2v3 69 | !**/ios/**/default.pbxuser 70 | !**/ios/**/default.perspectivev3 71 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 72 | -------------------------------------------------------------------------------- /.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: caf6027d81cc88540193dd42f9952251bc84e6fd 8 | channel: master 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | 2 | matrix: 3 | include: 4 | #声明Android运行环境 5 | - os: linux 6 | language: android 7 | dist: trusty 8 | licenses: 9 | - 'android-sdk-preview-license-.+' 10 | - 'android-sdk-license-.+' 11 | - 'google-gdk-license-.+' 12 | #声明需要安装的Android组件 13 | android: 14 | components: 15 | - tools 16 | - platform-tools 17 | - build-tools-28.0.3 18 | - android-28 19 | - sys-img-armeabi-v7a-google_apis-28 20 | - extra-android-m2repository 21 | - extra-google-m2repository 22 | - extra-google-android-support 23 | jdk: oraclejdk8 24 | sudo: false 25 | addons: 26 | apt: 27 | sources: 28 | - ubuntu-toolchain-r-test 29 | packages: 30 | - libstdc++6 31 | - fonts-droid 32 | #确保sdkmanager是最新的 33 | before_script: 34 | - yes | sdkmanager --update 35 | script: 36 | - yes | flutter doctor --android-licenses 37 | - flutter doctor && flutter -v build apk 38 | 39 | #声明iOS的运行环境 40 | - os: osx 41 | language: objective-c 42 | osx_image: xcode10.2 43 | script: 44 | - flutter doctor && flutter -v build ios --no-codesign 45 | install: 46 | - git clone https://github.com/flutter/flutter.git 47 | - export PATH="$PATH:`pwd`/flutter/bin" 48 | 49 | 50 | #声明构建需要执行的命令 51 | script: 52 | - yes | flutter doctor --android-licenses 53 | - flutter doctor && flutter -v build apk 54 | 55 | #对发布前的构建产物进行预处理,打包成ipa 56 | before_deploy: 57 | - mkdir app && mkdir app/Payload 58 | - cp -r build/ios/iphoneos/Runner.app app/Payload 59 | - pushd app && zip -r -m app.ipa Payload && popd 60 | #将ipa上传至github release 61 | deploy: 62 | provider: releases 63 | api_key: ${GITHUB_TOKEN} 64 | file: 65 | - app/app.ipa 66 | skip_cleanup: true 67 | on: 68 | tags: true 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flutter_wanandroid 2 | 3 | an app that mimics the [flutter-go](https://github.com/alibaba/flutter-go) UI effect and adds some cool effects... 4 | 5 | 6 | 7 | # thx 8 | 9 | - [flutter-go](https://github.com/alibaba/flutter-go) 10 | - [玩Android](https://www.wanandroid.com/) 11 | - open source lib author 12 | 13 | ## screenshot 14 | 15 | talk is cheap,show me the picture!! 16 | 17 | 18 | 19 | ![home](/screenshot/theme.png)![home](/screenshot/splash.png)![cat](/screenshot/login.png)![home](/screenshot/home.png)![home](/screenshot/cat.png)![home](/screenshot/project.png)![home](/screenshot/mine.png)![home](/screenshot/my-collect.png)![home](/screenshot/rank.png)![home](/screenshot/web-detail.png)![home](/screenshot/change-color.png)![mine](/screenshot/about.png) 20 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new FileNotFoundException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '3' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.2' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | def keystorePropertiesFile = rootProject.file("key.properties") 29 | def keystoreProperties = new Properties() 30 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 31 | 32 | android { 33 | compileSdkVersion 28 34 | 35 | sourceSets { 36 | main.java.srcDirs += 'src/main/kotlin' 37 | } 38 | 39 | lintOptions { 40 | disable 'InvalidPackage' 41 | } 42 | 43 | defaultConfig { 44 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 45 | applicationId "com.example.flutterwanandroid" 46 | minSdkVersion 21 47 | targetSdkVersion 28 48 | versionCode flutterVersionCode.toInteger() 49 | versionName flutterVersionName 50 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 51 | ndk { 52 | //设置支持的SO库架构 53 | abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' 54 | } 55 | 56 | manifestPlaceholders = [ 57 | JPUSH_PKGNAME : applicationId, 58 | JPUSH_APPKEY : "e510f2e57ab1cb6d33fdc233", // NOTE: JPush 上注册的包名对应的 Appkey. 59 | JPUSH_CHANNEL : "developer-default", //暂时填写默认值即可. 60 | ] 61 | 62 | // // 自定义输出配置 63 | // android.applicationVariants.all { variant -> 64 | // variant.outputs.all { 65 | // outputFileName = "${variant.name}_${variant.versionName}_${variant.name}.apk" 66 | // } 67 | // } 68 | } 69 | 70 | signingConfigs { 71 | release { 72 | keyAlias keystoreProperties['keyAlias'] 73 | keyPassword keystoreProperties['keyPassword'] 74 | storeFile file(keystoreProperties['storeFile']) 75 | storePassword keystoreProperties['storePassword'] 76 | } 77 | } 78 | 79 | buildTypes { 80 | release { 81 | // Signing with the debug keys for now, so `flutter run --release` works. 82 | signingConfig signingConfigs.release 83 | } 84 | release { 85 | // Signing with the debug keys for now, so `flutter run --debug` works. 86 | signingConfig signingConfigs.debug 87 | } 88 | } 89 | 90 | lintOptions { 91 | checkReleaseBuilds false 92 | abortOnError false 93 | } 94 | } 95 | 96 | flutter { 97 | source '../..' 98 | } 99 | 100 | dependencies { 101 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 102 | testImplementation 'junit:junit:4.12' 103 | androidTestImplementation 'androidx.test:runner:1.1.1' 104 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 105 | // androidTestImplementation 'com.android.support.test:runner:1.0.2' 106 | // androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 107 | } 108 | -------------------------------------------------------------------------------- /android/app/release/app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/android/app/release/app-release.apk -------------------------------------------------------------------------------- /android/app/release/output.json: -------------------------------------------------------------------------------- 1 | [{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":1,"versionName":"1.0.0","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 22 | 28 | 35 | 39 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 54 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/flutterwanandroid/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.flutterwanandroid 2 | 3 | import android.os.Bundle 4 | 5 | import io.flutter.app.FlutterActivity 6 | import io.flutter.plugin.common.MethodChannel 7 | import android.content.Context 8 | import android.content.ContextWrapper 9 | import android.content.Intent 10 | import android.content.IntentFilter 11 | import android.os.BatteryManager 12 | import android.os.Build.VERSION 13 | import android.os.Build.VERSION_CODES 14 | import android.net.Uri 15 | import android.util.Log 16 | import io.flutter.plugins.GeneratedPluginRegistrant 17 | 18 | 19 | class MainActivity: FlutterActivity() { 20 | private val TAG = MainActivity::class.java.simpleName 21 | private val CHANNEL = "com.flutter.method.channel" 22 | 23 | override fun onCreate(savedInstanceState: Bundle?) { 24 | super.onCreate(savedInstanceState) 25 | GeneratedPluginRegistrant.registerWith(this) 26 | MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result -> 27 | when(call.method){ 28 | "getBatteryLevel" ->{ 29 | val batteryLevel = getBatteryLevel() 30 | if (batteryLevel != -1) { 31 | result.success(batteryLevel) 32 | }else { 33 | result.error("UNAVAILABLE", "Battery level not available.", null) 34 | } 35 | } 36 | "openAppStore" ->{ 37 | try { 38 | var appId = "" 39 | var packageName = "com.tencent.mm" 40 | if (call.hasArgument("appId")){ 41 | appId = call.argument("appId").toString() 42 | Log.e(TAG,"appId: $appId") 43 | } 44 | if (call.hasArgument("packageName")){ 45 | packageName = call.argument("packageName").toString() 46 | Log.e(TAG,"packageName: $packageName") 47 | } 48 | val uri = Uri.parse("market://details?id=$packageName") 49 | val intent = Intent(Intent.ACTION_VIEW, uri) 50 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 51 | startActivity(intent) 52 | } catch (e: Exception) { 53 | result.error("UNAVAILABLE", "没有安装应用市场", null) 54 | } 55 | } 56 | else ->{ 57 | result.notImplemented() 58 | } 59 | } 60 | } 61 | } 62 | private fun getBatteryLevel(): Int { 63 | val batteryLevel: Int 64 | batteryLevel = if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { 65 | val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager 66 | batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY) 67 | } else { 68 | val intent = ContextWrapper(applicationContext).registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED)) 69 | intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1) 70 | } 71 | 72 | return batteryLevel 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_2.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/android/app/src/main/res/mipmap-xxxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/res/xml/provider_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | // maven { url 'https://maven.aliyun.com/repository/google' } 7 | // maven { url 'https://maven.aliyun.com/repository/jcenter' } 8 | // maven { url 'http://maven.aliyun.com/nexus/content/groups/public' } 9 | 10 | } 11 | 12 | dependencies { 13 | classpath 'com.android.tools.build:gradle:3.5.3' 14 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | // maven { url 'https://maven.aliyun.com/repository/google' } 23 | // maven { url 'https://maven.aliyun.com/repository/jcenter' } 24 | // maven { url 'http://maven.aliyun.com/nexus/content/groups/public' } 25 | } 26 | } 27 | 28 | rootProject.buildDir = '../build' 29 | subprojects { 30 | project.buildDir = "${rootProject.buildDir}/${project.name}" 31 | } 32 | subprojects { 33 | project.evaluationDependsOn(':app') 34 | } 35 | 36 | task clean(type: Delete) { 37 | delete rootProject.buildDir 38 | } 39 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | #distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip 7 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 8 | -------------------------------------------------------------------------------- /android/key.properties: -------------------------------------------------------------------------------- 1 | storePassword=123456 2 | keyPassword=key123456 3 | keyAlias=key0 4 | storeFile=C:/Users/Aller/AndroidStudioProjects/flutter_wanandroid.jks -------------------------------------------------------------------------------- /android/res/values/strings_en.arb: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /android/settings_aar.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /assets/app.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/assets/app.db -------------------------------------------------------------------------------- /assets/images/23038-animatonblue.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/assets/images/23038-animatonblue.gif -------------------------------------------------------------------------------- /assets/images/7903-error-404.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/assets/images/7903-error-404.gif -------------------------------------------------------------------------------- /assets/images/8021-empty-and-lost.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/assets/images/8021-empty-and-lost.gif -------------------------------------------------------------------------------- /assets/images/calendar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/assets/images/calendar.png -------------------------------------------------------------------------------- /assets/images/house.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/assets/images/house.png -------------------------------------------------------------------------------- /assets/images/p1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/assets/images/p1.png -------------------------------------------------------------------------------- /assets/images/p2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/assets/images/p2.png -------------------------------------------------------------------------------- /assets/images/p3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/assets/images/p3.png -------------------------------------------------------------------------------- /assets/images/paimaiLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/assets/images/paimaiLogo.png -------------------------------------------------------------------------------- /assets/images/plane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/assets/images/plane.png -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/flutter_export_environment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This is a generated file; do not edit or check into version control. 3 | export "FLUTTER_ROOT=D:\dong\flutter\flutter_windows_1.17.0-stable\flutter" 4 | export "FLUTTER_APPLICATION_PATH=D:\dong\android\project_github\Flutter-WanAndroid" 5 | export "FLUTTER_TARGET=lib\main.dart" 6 | export "FLUTTER_BUILD_DIR=build" 7 | export "SYMROOT=${SOURCE_ROOT}/../build\ios" 8 | export "OTHER_LDFLAGS=$(inherited) -framework Flutter" 9 | export "FLUTTER_FRAMEWORK_DIR=D:\dong\flutter\flutter_windows_1.17.0-stable\flutter\bin\cache\artifacts\engine\ios" 10 | export "FLUTTER_BUILD_NAME=1.0.2" 11 | export "FLUTTER_BUILD_NUMBER=3" 12 | export "DART_OBFUSCATION=false" 13 | export "TRACK_WIDGET_CREATION=false" 14 | export "TREE_SHAKE_ICONS=false" 15 | export "PACKAGE_CONFIG=.packages" 16 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 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: [UIApplicationLaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/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/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/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/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/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/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/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/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/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/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/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/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/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/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/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/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/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/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/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/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/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/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/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/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | flutter_wanandroid 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /lib/api/Api.dart: -------------------------------------------------------------------------------- 1 | class Api{ 2 | /// baseUrl 3 | static const String BASE_URL = "http://www.wanandroid.com/"; 4 | /// 首页 banner 5 | static const String HOME_BANNER = "http://www.wanandroid.com/banner/json"; 6 | /// 首页文章列表 7 | static const String HOME_ARTICLE_LIST = "http://www.wanandroid.com/article/list/"; 8 | /// 知识体系 9 | static const String SYSTEM_TREE = "http://www.wanandroid.com/tree/json"; 10 | /// 知识体系详情 11 | static const String SYSTEM_TREE_CONTENT = "http://www.wanandroid.com/article/list/"; 12 | /// 导航列表数据 13 | static const String NAVI_LIST = "http://www.wanandroid.com/navi/json"; 14 | /// 项目分类 15 | static const String PROJECT_TREE = "http://www.wanandroid.com/project/tree/json"; 16 | /// 项目列表 17 | static const String PROJECT_LIST = "http://www.wanandroid.com/project/list/"; 18 | /// 搜索列表 19 | static const String SEARCH_LIST = "https://www.wanandroid.com/article/query/"; 20 | /// 登录接口 21 | static const String LOGIN = "https://www.wanandroid.com/user/login"; 22 | /// 注册接口 23 | static const String REGISTER = "https://www.wanandroid.com/user/register"; 24 | /// 退出接口 25 | static const String LOGOUT = "https://www.wanandroid.com/user/logout/json"; 26 | /// 我的收藏接口 27 | static const String MY_COLLECT = "https://www.wanandroid.com/lg/collect/list/"; 28 | /// 积分排行榜 29 | static const String COIN_RANK = "$BASE_URL coin/rank/"; 30 | /// 每日一图 31 | static const String DAY_IMAGE = "https://cn.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1&mkt=zh-CN"; 32 | } -------------------------------------------------------------------------------- /lib/api/dio_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'package:cookie_jar/cookie_jar.dart'; 4 | import 'package:dio/dio.dart'; 5 | import 'package:dio_cookie_manager/dio_cookie_manager.dart'; 6 | import 'package:flutter_wanandroid/api/Api.dart'; 7 | import 'package:flutter_wanandroid/api/error_entity.dart'; 8 | 9 | class DioManager{ 10 | Dio _dio; 11 | 12 | /// 网络请求配置信息 13 | BaseOptions _getOptions() { 14 | return BaseOptions( 15 | //请求基地址,可以包含子路径 16 | baseUrl: Api.BASE_URL, 17 | //连接服务器超时时间,单位是毫秒. 18 | connectTimeout: 10000, 19 | //响应流上前后两次接受到数据的间隔,单位为毫秒。 20 | receiveTimeout: 5000, 21 | headers: { 22 | HttpHeaders.userAgentHeader: "dio", 23 | "api": "1.0.0", 24 | }, 25 | //请求的Content-Type,默认值是"application/json; charset=utf-8",Headers.formUrlEncodedContentType会自动编码请求体. 26 | contentType: Headers.formUrlEncodedContentType, 27 | //表示期望以那种格式(方式)接受响应数据。接受4种类型 `json`, `stream`, `plain`, `bytes`. 默认值是 `json`, 28 | responseType: ResponseType.json, 29 | ); 30 | } 31 | /// 单例 32 | DioManager._internal() { 33 | _dio = new Dio(_getOptions()); 34 | var cookieJar= CookieJar(); 35 | _dio.interceptors.add(CookieManager(cookieJar)); 36 | _dio.interceptors.add(LogInterceptor(responseBody: false)); //开启请求日志,放在最后一个拦截器 37 | // Print cookies 38 | print("cookie===>>>:${cookieJar.loadForRequest(Uri.parse(Api.BASE_URL))}"); 39 | } 40 | 41 | static DioManager singleton = DioManager._internal(); 42 | 43 | factory DioManager() => singleton; 44 | 45 | get dio { 46 | return _dio; 47 | } 48 | 49 | /// get请求 50 | get(url, {data, options, cancelToken, showLoading, hideLoading, success, Function(ErrorEntity) error}) async { 51 | try { 52 | showLoading(); 53 | Response response = await dio.get(url, queryParameters: data, options: options, cancelToken: cancelToken); 54 | if (response != null) { 55 | if(response.statusCode == 200){ 56 | print('get response:${response.data}'); 57 | success(response.data); 58 | }else{ 59 | error(ErrorEntity(code: -2, message: "未知错误")); 60 | } 61 | } else { 62 | error(ErrorEntity(code: -1, message: "未知错误")); 63 | } 64 | hideLoading(); 65 | } on DioError catch(e) { 66 | /// 格式化输出错误信息 67 | formatError(e); 68 | hideLoading(); 69 | }finally{ 70 | hideLoading(); 71 | } 72 | } 73 | 74 | /// post 请求 75 | post(url, {data, options, cancelToken, showLoading, hideLoading, Function(T) success, Function(ErrorEntity) error}) async { 76 | try { 77 | showLoading(); 78 | Response response = await dio.post(url, queryParameters: data, options: options, cancelToken: cancelToken); 79 | if (response != null) { 80 | if(response.statusCode == 200){ 81 | print('post response:${response.data}'); 82 | success(response.data); 83 | }else{ 84 | error(ErrorEntity(code: -2, message: "未知错误")); 85 | } 86 | } else { 87 | error(ErrorEntity(code: -1, message: "未知错误")); 88 | } 89 | hideLoading(); 90 | } on DioError catch (e) { 91 | formatError(e); 92 | hideLoading(); 93 | } finally { 94 | hideLoading(); 95 | } 96 | } 97 | 98 | /// 下载文件 99 | downloadFile(urlPath, savePath, progress) async { 100 | try { 101 | Response response = await dio.download(urlPath, savePath, onReceiveProgress: (int count, int total){ 102 | //进度 103 | progress(count,total); 104 | }); 105 | print('downloadFile data---------${response.data}'); 106 | } on DioError catch (e) { 107 | print('downloadFile error---------$e'); 108 | formatError(e); 109 | } finally{ 110 | 111 | } 112 | } 113 | 114 | /// error 统一处理 115 | void formatError(DioError e) { 116 | if (e.type == DioErrorType.CONNECT_TIMEOUT) { 117 | print("连接超时"); 118 | } else if (e.type == DioErrorType.SEND_TIMEOUT) { 119 | print("请求超时"); 120 | } else if (e.type == DioErrorType.RECEIVE_TIMEOUT) { 121 | print("响应超时"); 122 | } else if (e.type == DioErrorType.RESPONSE) { 123 | print("出现异常"); 124 | } else if (e.type == DioErrorType.CANCEL) { 125 | print("请求取消"); 126 | } else { 127 | print("未知错误"); 128 | } 129 | } 130 | 131 | 132 | } -------------------------------------------------------------------------------- /lib/api/error_entity.dart: -------------------------------------------------------------------------------- 1 | /// 网络请求返回错误 2 | class ErrorEntity { 3 | int code; 4 | String message; 5 | ErrorEntity({this.code, this.message}); 6 | } -------------------------------------------------------------------------------- /lib/api/http.dart: -------------------------------------------------------------------------------- 1 | import 'package:cookie_jar/cookie_jar.dart'; 2 | import 'package:dio/dio.dart'; 3 | import 'package:dio_cookie_manager/dio_cookie_manager.dart'; 4 | import 'package:flutter_wanandroid/api/error_entity.dart'; 5 | 6 | /// 网络请求工具 7 | class Http{ 8 | /// 单例 9 | Http._internal(); 10 | 11 | static final Dio dio = Dio(BaseOptions( 12 | baseUrl: "https://www.wanandroid.com", 13 | connectTimeout: 5000, 14 | receiveTimeout: 3000, 15 | )); 16 | 17 | ///初始化dio 18 | static init() { 19 | ///初始化cookie 20 | var cookieJar= CookieJar(); 21 | dio.interceptors.add(CookieManager(cookieJar)); 22 | dio.interceptors.add(LogInterceptor(responseBody: false)); //开启请求日志,放在最后一个拦截器 23 | 24 | /// 添加拦截器 25 | dio.interceptors.add(InterceptorsWrapper(onRequest: (RequestOptions options) { 26 | print("请求之前"); 27 | return options; 28 | }, onResponse: (Response response) { 29 | print("响应之前"); 30 | return response; 31 | }, onError: (DioError e) { 32 | print("错误之前"); 33 | handleError(e); 34 | return e; 35 | })); 36 | } 37 | 38 | ///error统一处理 39 | static void handleError(DioError e) { 40 | switch (e.type) { 41 | case DioErrorType.CONNECT_TIMEOUT: 42 | print("连接超时"); 43 | break; 44 | case DioErrorType.SEND_TIMEOUT: 45 | print("请求超时"); 46 | break; 47 | case DioErrorType.RECEIVE_TIMEOUT: 48 | print("响应超时"); 49 | break; 50 | case DioErrorType.RESPONSE: 51 | print("出现异常"); 52 | break; 53 | case DioErrorType.CANCEL: 54 | print("请求取消"); 55 | break; 56 | default: 57 | print("未知错误"); 58 | break; 59 | } 60 | } 61 | 62 | /// get请求 63 | static Future get(String url, [Map params]) async { 64 | Response response; 65 | if (params != null) { 66 | response = await dio.get(url, queryParameters: params); 67 | } else { 68 | response = await dio.get(url); 69 | } 70 | return response.data; 71 | } 72 | 73 | /// get请求 74 | static Future getData(url, {data, options, cancelToken, success, Function(ErrorEntity) error}) async { 75 | try { 76 | Response response = await dio.get(url, queryParameters: data, options: options, cancelToken: cancelToken); 77 | if (response != null) { 78 | if(response.statusCode == 200){ 79 | print('get response:${response.data}'); 80 | success(response.data); 81 | }else{ 82 | error(ErrorEntity(code: -2, message: "未知错误")); 83 | } 84 | } else { 85 | error(ErrorEntity(code: -1, message: "未知错误")); 86 | } 87 | } on DioError catch(e) { 88 | /// 格式化输出错误信息 89 | handleError(e); 90 | }finally{ 91 | } 92 | } 93 | 94 | /// post 表单请求 95 | static Future post(String url, [Map params]) async { 96 | Response response = await dio.post(url, queryParameters: params); 97 | return response.data; 98 | } 99 | 100 | /// post body请求 101 | static Future postJson(String url, [Map data]) async { 102 | Response response = await dio.post(url, data: data); 103 | return response.data; 104 | } 105 | 106 | /// post 请求 107 | static Future postData(url, {data, options, cancelToken, Function(T) success, Function(ErrorEntity) error}) async { 108 | try { 109 | Response response = await dio.post(url, queryParameters: data, options: options, cancelToken: cancelToken); 110 | if (response != null) { 111 | if(response.statusCode == 200){ 112 | print('post response:${response.data}'); 113 | success(response.data); 114 | }else{ 115 | error(ErrorEntity(code: -2, message: "未知错误")); 116 | } 117 | } else { 118 | error(ErrorEntity(code: -1, message: "未知错误")); 119 | } 120 | } on DioError catch (e) { 121 | handleError(e); 122 | } finally { 123 | } 124 | } 125 | 126 | /// 下载文件 127 | static Future downloadFile(urlPath, savePath) async { 128 | Response response; 129 | try { 130 | response = await dio.download(urlPath, savePath, 131 | onReceiveProgress: (int count, int total) { 132 | //进度 133 | print("$count $total"); 134 | }); 135 | } on DioError catch (e) { 136 | handleError(e); 137 | } 138 | return response.data; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /lib/components/cate_card.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_wanandroid/api/common_service.dart'; 5 | import 'package:flutter_wanandroid/components/cate_card_container.dart'; 6 | import 'package:flutter_wanandroid/model/navi_bean.dart'; 7 | import 'package:flutter_wanandroid/model/cat.dart'; 8 | 9 | class CateCard extends StatefulWidget { 10 | // 猫耳标题 11 | final String category; 12 | final List categorieLists; 13 | final List naviLists; 14 | 15 | CateCard({@required this.category, this.categorieLists, this.naviLists}); 16 | 17 | @override 18 | _CateCardState createState() => _CateCardState(); 19 | } 20 | 21 | class _CateCardState extends State { 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | double screenWidth = MediaQuery.of(context).size.width; 26 | 27 | return Container( 28 | width: screenWidth, 29 | padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0), 30 | /// 层叠布局,让猫耳icon在左上角 31 | child: Stack( 32 | children: [ 33 | Container( 34 | width: screenWidth - 20, 35 | margin: const EdgeInsets.only(top: 30.0, bottom: 0.0), 36 | decoration: BoxDecoration( 37 | color: Colors.white, 38 | borderRadius: BorderRadius.circular(4.0), 39 | ), 40 | child: Column( 41 | children: [ 42 | /// 标题 43 | Container( 44 | width: screenWidth - 20, 45 | padding: const EdgeInsets.only(left: 65.0, top: 3.0), 46 | height: 30.0, 47 | // 一级标题 48 | child: Text( 49 | widget.category, 50 | style: TextStyle( 51 | color: Theme.of(context).primaryColor, 52 | fontSize: 18.0, 53 | ), 54 | ), 55 | ), 56 | /// 网格布局 57 | _buildWidgetContainer(), 58 | ], 59 | ), 60 | ), 61 | /// 标题左边的icon 62 | Positioned( 63 | left: 0.0, 64 | top: 0.0, 65 | child: Container( 66 | height: 60.0, 67 | width: 60.0, 68 | decoration: BoxDecoration( 69 | color: Colors.white, 70 | borderRadius: BorderRadius.circular(30.0), 71 | ), 72 | child: Center( 73 | child: Container( 74 | decoration: BoxDecoration( 75 | color: Theme.of(context).primaryColor, 76 | borderRadius: BorderRadius.circular(23.0), 77 | ), 78 | height: 46.0, 79 | width: 46.0, 80 | child: Icon( 81 | Icons.link, 82 | color: Colors.white, 83 | size: 30.0, 84 | ), 85 | ), 86 | ), 87 | ), 88 | ) 89 | ], 90 | ), 91 | ); 92 | } 93 | 94 | Widget _buildWidgetContainer() { 95 | /// 没有数据显示空页面 96 | if(widget.category == "导航"){ 97 | if (widget.naviLists.length == 0) { 98 | return Container(); 99 | } 100 | /// 有数据显示网格布局 101 | return Container( 102 | padding: const EdgeInsets.only(bottom: 10.0, top: 5.0), 103 | decoration: BoxDecoration( 104 | image: DecorationImage( 105 | image: AssetImage('assets/images/paimaiLogo.png'), 106 | alignment: Alignment.bottomRight), 107 | ), 108 | child: CateCardContainer(categories: widget.naviLists, columnCount: 3, type: 0, isWidgetPoint: false), 109 | ); 110 | }else{ 111 | if (widget.categorieLists.length == 0) { 112 | return Container(); 113 | } 114 | /// 有数据显示网格布局 115 | return Container( 116 | padding: const EdgeInsets.only(bottom: 10.0, top: 5.0), 117 | decoration: BoxDecoration( 118 | image: DecorationImage( 119 | image: AssetImage('assets/images/paimaiLogo.png'), 120 | alignment: Alignment.bottomRight), 121 | ), 122 | child: CateCardContainer(categories: widget.categorieLists, columnCount: 3, type: 1, isWidgetPoint: false), 123 | ); 124 | } 125 | 126 | 127 | 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /lib/components/cate_card_container.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:math'; 3 | 4 | /// 猫耳页面 一行 主布局 5 | /// 作者 :龙衣 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:fluro/fluro.dart'; 9 | import 'package:flutter_wanandroid/components/cate_card_item.dart'; 10 | import 'package:flutter_wanandroid/model/cat.dart'; 11 | import 'package:flutter_wanandroid/model/navi_bean.dart'; 12 | import 'package:flutter_wanandroid/routers/router_path.dart'; 13 | import 'package:flutter_wanandroid/views/cat_page/cat_sub_page.dart'; 14 | import '../routers/application.dart'; 15 | 16 | /// 每一个猫耳布局展示的内容 17 | class CateCardContainer extends StatelessWidget { 18 | final int columnCount; //一行几个 19 | final List categories; 20 | final bool isWidgetPoint; 21 | final int type; // 0表示导航,其他表示体系和项目 22 | 23 | 24 | CateCardContainer( 25 | {Key key, 26 | @required this.categories, 27 | @required this.columnCount, 28 | @required this.type, 29 | @required this.isWidgetPoint}) 30 | : super(key: key); 31 | 32 | List _buildColumns(context) { 33 | List _listWidget = []; 34 | List _listRows = []; 35 | int addI; 36 | for (int i = 0, length = categories.length; i < length; i += columnCount) { 37 | _listRows = []; 38 | for (int innerI = 0; innerI < columnCount; innerI++) { 39 | addI = innerI + i; 40 | if (addI < length) { 41 | dynamic item = categories[addI]; 42 | _listRows.add( 43 | Expanded( 44 | flex: 1, 45 | child: CateCardItem( 46 | title: item.name, 47 | onTap: () { 48 | /// 导航 49 | /// 如果有 articles 集合 进入列表页面 50 | /// 51 | /// 项目,体系 52 | /// 如果有 cats 集合 ,有子分类,进入子分类页面 53 | /// 如果有 cats 集合 ,进入列表页面 54 | if(type == 0){ 55 | // 导航 56 | var articles = item.articles as List ; 57 | var name = item.name; 58 | var cid = item.cid; 59 | print("articles--->"+articles.toString()); 60 | if(articles != null){ 61 | // 项目,体系 62 | Application.router.navigateTo(context, "${RouterPath.treeList}?id=${Uri.encodeComponent(cid.toString())}&name=${Uri.encodeComponent(name.toString())}"); 63 | } 64 | }else{ 65 | var cats = item.cats as List ; 66 | print("cats--->"+cats.toString()); 67 | if(cats != null){ 68 | // 项目,体系 69 | if(cats.isNotEmpty){ 70 | // 继续显示猫耳朵 71 | print("跳转猫耳-- 传递list->"+cats.toString()); 72 | Navigator.push(context, MaterialPageRoute(builder: (context) => CatSubPage(name:item.name, cats: cats,))); 73 | }else{ 74 | Application.router.navigateTo(context, "${RouterPath.treeList}?id=${Uri.encodeComponent(item.id.toString())}&name=${Uri.encodeComponent(item.name.toString())}"); 75 | } 76 | } 77 | } 78 | 79 | }, 80 | index: addI, 81 | totalCount: length, 82 | rowLength: columnCount, 83 | textSize: isWidgetPoint ? 'middle' : 'small', 84 | ), 85 | ), 86 | ); 87 | } else { 88 | _listRows.add( 89 | Expanded( 90 | flex: 1, 91 | child: Container(), 92 | ), 93 | ); 94 | } 95 | } 96 | _listWidget.add( 97 | Row( 98 | children: _listRows, 99 | ), 100 | ); 101 | } 102 | return _listWidget; 103 | } 104 | 105 | @override 106 | Widget build(BuildContext context) { 107 | return Column( 108 | children: _buildColumns(context), 109 | ); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /lib/components/cate_card_item.dart: -------------------------------------------------------------------------------- 1 | /// 猫耳网格 item 2 | /// 作者:龙衣 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_wanandroid/utils/style.dart'; 6 | 7 | String _widgetName; 8 | 9 | class CateCardItem extends StatelessWidget { 10 | final String title; 11 | final VoidCallback onTap; 12 | final int index; //用于计算border 13 | final int totalCount; 14 | final int rowLength; 15 | final String textSize; 16 | 17 | CateCardItem( 18 | {this.title, 19 | this.onTap, 20 | this.index, 21 | this.totalCount, 22 | this.rowLength, 23 | this.textSize}); 24 | 25 | Border _buildBorder(context) { 26 | Border _border; 27 | bool isRight = (index % rowLength) == (rowLength - 1); //是不是最右边的,决定是否有右侧边框 28 | var currentRow = (index + 1) % rowLength > 0 29 | ? (index + 1) ~/ rowLength + 1 30 | : (index + 1) ~/ rowLength; 31 | int totalRow = totalCount % rowLength > 0 32 | ? totalCount ~/ rowLength + 1 33 | : totalCount ~/ rowLength; 34 | if (currentRow < totalRow && isRight) { 35 | //不是最后一行,是最右边 36 | _border = Border( 37 | bottom: const BorderSide( 38 | width: 1.0, color: Color(CateCardColor.borderColor)), 39 | ); 40 | } 41 | if (currentRow < totalRow && !isRight) { 42 | _border = Border( 43 | right: const BorderSide( 44 | width: 1.0, color: Color(CateCardColor.borderColor)), 45 | bottom: const BorderSide( 46 | width: 1.0, color: Color(CateCardColor.borderColor)), 47 | ); 48 | } 49 | if (currentRow == totalRow && !isRight) { 50 | _border = Border( 51 | right: const BorderSide( 52 | width: 1.0, color: Color(CateCardColor.borderColor)), 53 | ); 54 | } 55 | return _border; 56 | } 57 | 58 | @override 59 | Widget build(BuildContext context) { 60 | _widgetName = title.replaceFirst( 61 | //首字母转为大写 62 | title.substring(0, 1), 63 | title.substring(0, 1).toUpperCase()); 64 | Icon widgetIcon; 65 | widgetIcon = Icon(Icons.link); 66 | 67 | final textStyle = (textSize == 'middle') 68 | ? TextStyle(fontSize: 13.8, fontFamily: 'MediumItalic') 69 | : TextStyle(fontSize: 16.0); 70 | return InkWell( 71 | onTap: onTap, 72 | child: Container( 73 | decoration: new BoxDecoration( 74 | border: _buildBorder(context), 75 | ), 76 | padding: const EdgeInsets.symmetric(vertical: 30.0, horizontal: 10.0), 77 | height: 150.0, 78 | child: Column( 79 | mainAxisAlignment: MainAxisAlignment.center, 80 | mainAxisSize: MainAxisSize.max, 81 | children: [ 82 | widgetIcon, 83 | SizedBox( 84 | height: 8.0, 85 | ), 86 | Text(_widgetName, style: textStyle), 87 | ], 88 | ), 89 | ), 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /lib/components/category.dart: -------------------------------------------------------------------------------- 1 | /// 从 猫耳Tab 进入的 二级分类页面 2 | /// 作者:龙衣 3 | 4 | import 'dart:async'; 5 | 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_wanandroid/components/cate_card_container.dart'; 8 | import 'package:flutter_wanandroid/model/article.dart'; 9 | import 'package:flutter_wanandroid/model/cat.dart'; 10 | 11 | import '../routers/application.dart'; 12 | 13 | 14 | enum CategoryPage { Cat, WidgetDemo } 15 | 16 | class CategoryHome extends StatefulWidget { 17 | CategoryHome(this.id, this.title); 18 | final int id; 19 | final String title; 20 | 21 | @override 22 | _CategoryHome createState() => new _CategoryHome(); 23 | } 24 | 25 | class _CategoryHome extends State { 26 | String title = ''; 27 | 28 | List cats = []; 29 | /// 分类层级 30 | List catHistory = new List(); 31 | 32 | 33 | @override 34 | void initState() { 35 | super.initState(); 36 | // 初始化加入顶级的name 37 | /// 请求网络,通过 id 去获取,当前下面的 children 是否有数据 38 | // this.getCatByName(widget.name).then((Cat cat) { 39 | // catHistory.add(cat); 40 | // searchCatOrWigdet(); 41 | // }); 42 | } 43 | 44 | 45 | Future back() { 46 | if (catHistory.length == 1) { 47 | return Future.value(true); 48 | } 49 | catHistory.removeLast(); 50 | searchCatOrWigdet(); 51 | return Future.value(false); 52 | } 53 | 54 | void go(Cat cat) { 55 | catHistory.add(cat); 56 | searchCatOrWigdet(); 57 | } 58 | 59 | void searchCatOrWigdet() async { 60 | 61 | List _cats = new List(); 62 | 63 | 64 | this.setState(() { 65 | cats = _cats; 66 | }); 67 | } 68 | 69 | void onCatgoryTap(Cat cat) { 70 | go(cat); 71 | } 72 | 73 | /// item 点击 74 | void onWidgetTap(Cat cat) { 75 | /// 如果 cats 不空 76 | /// 跳转 category 页面:传递 id ,title 77 | // Application.router.navigateTo(context, "$targetRouter"); 78 | } 79 | 80 | /// 内容显示 81 | Widget _buildContent() { 82 | CateCardContainer wiContaienr = CateCardContainer( 83 | categories: cats, 84 | columnCount: 3, 85 | isWidgetPoint:true 86 | ); 87 | return Container( 88 | padding: const EdgeInsets.only(bottom: 10.0, top: 5.0), 89 | decoration: BoxDecoration( 90 | color: Colors.white, 91 | image: DecorationImage( 92 | image: AssetImage('assets/images/paimaiLogo.png'), 93 | alignment: Alignment.bottomRight), 94 | ), 95 | child: wiContaienr, 96 | ); 97 | } 98 | 99 | @override 100 | Widget build(BuildContext context) { 101 | return Scaffold( 102 | appBar: AppBar( 103 | title: Text(title), 104 | ), 105 | body: WillPopScope( 106 | onWillPop: () { 107 | return back(); 108 | }, 109 | child: ListView( 110 | children: [ 111 | _buildContent(), 112 | ], 113 | ), 114 | // child: Container(color: Colors.blue,child: Text('123'),), 115 | ), 116 | ); 117 | } 118 | } 119 | 120 | -------------------------------------------------------------------------------- /lib/components/home_banner.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_wanandroid/model/banner.dart'; 5 | 6 | 7 | 8 | class HomeBanner extends StatefulWidget { 9 | final List bannerStories; 10 | final OnTapBannerItem onTap; 11 | 12 | HomeBanner(this.bannerStories, this.onTap, {Key key}) 13 | :super(key: key); 14 | 15 | @override 16 | State createState() { 17 | return _BannerState(); 18 | } 19 | } 20 | 21 | class _BannerState extends State { 22 | int virtualIndex = 0; 23 | int realIndex = 1; 24 | PageController controller; 25 | Timer timer; 26 | 27 | @override 28 | void initState() { 29 | super.initState(); 30 | controller = PageController(initialPage: realIndex); 31 | timer = Timer.periodic(Duration(seconds: 5), (timer) { // 自动滚动 32 | /// print(realIndex); 33 | controller.animateToPage(realIndex + 1, 34 | duration: Duration(milliseconds: 300), 35 | curve: Curves.linear); 36 | }); 37 | } 38 | 39 | @override 40 | void dispose() { 41 | super.dispose(); 42 | controller.dispose(); 43 | timer.cancel(); 44 | } 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | return Container( 49 | height: 226.0, 50 | child: Stack( 51 | alignment: Alignment.bottomCenter, 52 | children: [ 53 | PageView( 54 | controller: controller, 55 | onPageChanged: _onPageChanged, 56 | children: _buildItems(),), 57 | _buildIndicator(), // 下面的小点 58 | ]), 59 | ); 60 | } 61 | 62 | List _buildItems() { // 排列轮播数组 63 | List items = []; 64 | if (widget.bannerStories.length > 0) { 65 | // 头部添加一个尾部Item,模拟循环 66 | items.add( 67 | _buildItem(widget.bannerStories[widget.bannerStories.length - 1])); 68 | // 正常添加Item 69 | items.addAll( 70 | widget.bannerStories.map((story) => _buildItem(story)).toList( 71 | growable: false)); 72 | // 尾部 73 | items.add( 74 | _buildItem(widget.bannerStories[0])); 75 | } 76 | return items; 77 | } 78 | 79 | Widget _buildItem(BannerData story) { 80 | return GestureDetector( 81 | onTap: () { // 按下 82 | if (widget.onTap != null) { 83 | widget.onTap(story); 84 | } 85 | }, 86 | child: Stack( 87 | fit: StackFit.expand, 88 | children: [ 89 | Image.network(story.imagePath, fit: BoxFit.cover), 90 | _buildItemTitle(story.title), // 内容文字,大意 91 | ],),); 92 | } 93 | 94 | Widget _buildItemTitle(String title) { 95 | return Container( 96 | decoration: BoxDecoration( /// 背景的渐变色 97 | gradient: LinearGradient( 98 | begin: Alignment.bottomCenter, 99 | end: const Alignment(0.0, -0.8), 100 | colors: [const Color(0xa0000000), Colors.transparent], 101 | ), 102 | ), 103 | alignment: Alignment.bottomCenter, 104 | child: Container( 105 | margin: EdgeInsets.symmetric(vertical: 22.0, horizontal: 16.0), 106 | child: Text( 107 | title, style: TextStyle(color: Colors.white, fontSize: 18.0),),), 108 | ); 109 | } 110 | 111 | Widget _buildIndicator() { 112 | List indicators = []; 113 | for (int i = 0; i < widget.bannerStories.length; i++) { 114 | indicators.add(Container( 115 | width: 6.0, 116 | height: 6.0, 117 | margin: EdgeInsets.symmetric(horizontal: 1.5, vertical: 10.0), 118 | decoration: BoxDecoration( 119 | shape: BoxShape.circle, 120 | color: i == virtualIndex ? Colors.white : Colors.grey))); 121 | } 122 | return Row( 123 | mainAxisAlignment: MainAxisAlignment.center, 124 | children: indicators); 125 | } 126 | 127 | _onPageChanged(int index) { 128 | realIndex = index; 129 | int count = widget.bannerStories.length; 130 | if (index == 0) { 131 | virtualIndex = count - 1; 132 | controller.jumpToPage(count); 133 | } else if (index == count + 1) { 134 | virtualIndex = 0; 135 | controller.jumpToPage(1); 136 | } else { 137 | virtualIndex = index - 1; 138 | } 139 | setState(() {}); 140 | } 141 | } 142 | 143 | typedef void OnTapBannerItem(BannerData story); -------------------------------------------------------------------------------- /lib/components/list_view_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_wanandroid/routers/router_path.dart'; 3 | import '../routers/application.dart'; 4 | import 'dart:core'; 5 | 6 | /// 自定义组件,首页 card 中显示用 7 | class ListViewItem extends StatelessWidget { 8 | final int itemId; 9 | final String itemUrl; 10 | final String itemTitle; 11 | final String itemShareUser; 12 | final String itemNiceDate; 13 | 14 | const ListViewItem({Key key, this.itemId, this.itemUrl, this.itemTitle, this.itemShareUser,this.itemNiceDate}) 15 | : super(key: key); 16 | 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return Card( 21 | color: Colors.white, 22 | elevation: 4.0, 23 | margin: new EdgeInsets.symmetric(horizontal: 10.0, vertical: 6.0), 24 | child: ListTile( 25 | onTap: () { 26 | // _launchURL(itemUrl, context); 27 | Application.router.navigateTo(context, '${RouterPath.webViewPage}?id=${Uri.encodeComponent(itemId.toString())}&title=${Uri.encodeComponent(itemTitle)}&link=${Uri.encodeComponent(itemUrl)}'); 28 | }, 29 | title: Padding( 30 | child: Text( 31 | itemTitle, 32 | style: TextStyle(color: Colors.black, fontSize: 15.0), 33 | ), 34 | padding: EdgeInsets.only(top: 10.0), 35 | ), 36 | subtitle: Row( 37 | children: [ 38 | Padding( 39 | child: Text(itemShareUser, 40 | style: TextStyle(color: Colors.black54, fontSize: 10.0)), 41 | padding: EdgeInsets.only(top: 10.0, bottom: 10.0), 42 | ), 43 | Padding( 44 | child: Text(itemNiceDate, 45 | style: TextStyle(color: Colors.black54, fontSize: 10.0)), 46 | padding: EdgeInsets.only(top: 10.0, bottom: 10.0, left: 10.0), 47 | ) 48 | ], 49 | ), 50 | trailing: 51 | Icon(Icons.keyboard_arrow_right, color: Colors.grey, size: 30.0), 52 | ), 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/components/navi_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// 导航文章列表 4 | 5 | class NaviListPage extends StatefulWidget{ 6 | 7 | final int id; 8 | final String title; 9 | 10 | NaviListPage(this.id, this.title); 11 | @override 12 | State createState() { 13 | return new NaviListPageState(); 14 | } 15 | 16 | } 17 | 18 | class NaviListPageState extends State { 19 | @override 20 | Widget build(BuildContext context) { 21 | return Text("导航文章列表"); 22 | } 23 | } -------------------------------------------------------------------------------- /lib/components/pagination.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_wanandroid/api/Api.dart'; 4 | import 'package:flutter_wanandroid/api/common_service.dart'; 5 | import 'package:flutter_wanandroid/components/home_banner.dart'; 6 | import 'package:flutter_wanandroid/model/banner.dart'; 7 | import 'package:url_launcher/url_launcher.dart'; 8 | 9 | 10 | // Banner 11 | class Pagination extends StatefulWidget { 12 | @override 13 | State createState() { 14 | return BannerState(); 15 | } 16 | } 17 | 18 | class BannerState extends State { 19 | List _bannerList = new List(); 20 | 21 | @override 22 | void initState() { 23 | _getBanner(); 24 | } 25 | 26 | Future _getBanner() async{ 27 | _bannerList.clear(); 28 | CommonService().getBanner((BannerModel _bean) { 29 | if (_bean.data.length > 0) { 30 | if(mounted){ 31 | setState(() { 32 | _bannerList = _bean.data; 33 | }); 34 | } 35 | } 36 | }); 37 | } 38 | 39 | 40 | /// 打开本地浏览器 41 | void _launchURL(String url) async { 42 | if (await canLaunch(url)) { 43 | await launch(url); 44 | } else { 45 | throw 'Could not launch $url'; 46 | } 47 | } 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | return Column( 52 | key:Key('__header__'), 53 | //physics: AlwaysScrollableScrollPhysics(), 54 | //padding: EdgeInsets.only(), 55 | children: _pageSelector(context) 56 | ); 57 | } 58 | 59 | List _pageSelector(BuildContext context) { 60 | List list = []; 61 | if (_bannerList.length > 0) { 62 | list.add(HomeBanner(_bannerList, (story) { 63 | _launchURL('${story.url}'); 64 | })); 65 | } 66 | return list; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/components/project_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// 项目列表 4 | 5 | class ProjectListPage extends StatefulWidget{ 6 | 7 | final int id; 8 | final String title; 9 | 10 | ProjectListPage(this.id, this.title); 11 | 12 | @override 13 | State createState() { 14 | return new ProjectListPageState(); 15 | } 16 | 17 | } 18 | 19 | class ProjectListPageState extends State { 20 | @override 21 | Widget build(BuildContext context) { 22 | return Text("项目列表"); 23 | } 24 | } -------------------------------------------------------------------------------- /lib/constant/color_config.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ColorConfig{ 4 | static Color BLUE_200 = Colors.blue[200]; 5 | } -------------------------------------------------------------------------------- /lib/constant/shared_preferences_keys.dart: -------------------------------------------------------------------------------- 1 | //enum DateType { 2 | // Int, 3 | // Double, 4 | // Bool, 5 | // String, 6 | // Dynamic 7 | //} 8 | 9 | //class spKey { 10 | // String name; 11 | // DateType type; 12 | // 13 | // spKey({this.name, this.type}); 14 | //} 15 | 16 | class SharedPreferencesKeys { 17 | /// boolean 18 | /// 用于欢迎页面. 只有第一次访问才会显示. 或者手动将这个值设为false 19 | static String showWelcome = 'loginWelcone'; 20 | /// json 21 | /// 用于存放搜索页的搜索数据. 22 | /// [{ 23 | /// name: 'name' 24 | /// 25 | /// }] 26 | static String searchHistory = 'searchHistory'; 27 | static String userName = 'userName'; 28 | 29 | static String splash_date = "key_splash_date"; 30 | static String splash_image = "key_splash_image"; 31 | } 32 | 33 | -------------------------------------------------------------------------------- /lib/event/event_bus.dart: -------------------------------------------------------------------------------- 1 | import 'package:event_bus/event_bus.dart'; 2 | 3 | class ApplicationEvent{ 4 | static EventBus event; 5 | } -------------------------------------------------------------------------------- /lib/event/event_model.dart: -------------------------------------------------------------------------------- 1 | class CollectionEvent{ 2 | final String widgetName; 3 | final String router; 4 | final bool isRemove; 5 | // token uid... 6 | CollectionEvent(this.widgetName,this.router,this.isRemove); 7 | } -------------------------------------------------------------------------------- /lib/event/event_theme.dart: -------------------------------------------------------------------------------- 1 | /// EventBus 消息数据类,用于更新主题颜色 2 | class ThemeEvent { 3 | final int themeColor; 4 | ThemeEvent(this.themeColor); 5 | } -------------------------------------------------------------------------------- /lib/generated/i18n.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | // ignore_for_file: non_constant_identifier_names 7 | // ignore_for_file: camel_case_types 8 | // ignore_for_file: prefer_single_quotes 9 | 10 | // This file is automatically generated. DO NOT EDIT, all your changes would be lost. 11 | class S implements WidgetsLocalizations { 12 | const S(); 13 | 14 | static S current; 15 | 16 | static const GeneratedLocalizationsDelegate delegate = 17 | GeneratedLocalizationsDelegate(); 18 | 19 | static S of(BuildContext context) => Localizations.of(context, S); 20 | 21 | @override 22 | TextDirection get textDirection => TextDirection.ltr; 23 | 24 | String get app_title => "app_title"; 25 | String get main_title => "My Main Title"; 26 | String message_tip(String count) => "You have pushed the button many times: $count"; 27 | } 28 | 29 | class $en extends S { 30 | const $en(); 31 | } 32 | 33 | class $zh extends S { 34 | const $zh(); 35 | 36 | @override 37 | TextDirection get textDirection => TextDirection.ltr; 38 | 39 | @override 40 | String get app_title => "App标题"; 41 | @override 42 | String get main_title => "主标题"; 43 | @override 44 | String message_tip(String count) => "点击按钮次数: $count"; 45 | } 46 | 47 | class GeneratedLocalizationsDelegate extends LocalizationsDelegate { 48 | const GeneratedLocalizationsDelegate(); 49 | 50 | List get supportedLocales { 51 | return const [ 52 | Locale("en", ""), 53 | Locale("zh", ""), 54 | ]; 55 | } 56 | 57 | LocaleListResolutionCallback listResolution({Locale fallback, bool withCountry = true}) { 58 | return (List locales, Iterable supported) { 59 | if (locales == null || locales.isEmpty) { 60 | return fallback ?? supported.first; 61 | } else { 62 | return _resolve(locales.first, fallback, supported, withCountry); 63 | } 64 | }; 65 | } 66 | 67 | LocaleResolutionCallback resolution({Locale fallback, bool withCountry = true}) { 68 | return (Locale locale, Iterable supported) { 69 | return _resolve(locale, fallback, supported, withCountry); 70 | }; 71 | } 72 | 73 | @override 74 | Future load(Locale locale) { 75 | final String lang = getLang(locale); 76 | if (lang != null) { 77 | switch (lang) { 78 | case "en": 79 | S.current = const $en(); 80 | return SynchronousFuture(S.current); 81 | case "zh": 82 | S.current = const $zh(); 83 | return SynchronousFuture(S.current); 84 | default: 85 | // NO-OP. 86 | } 87 | } 88 | S.current = const S(); 89 | return SynchronousFuture(S.current); 90 | } 91 | 92 | @override 93 | bool isSupported(Locale locale) => _isSupported(locale, true); 94 | 95 | @override 96 | bool shouldReload(GeneratedLocalizationsDelegate old) => false; 97 | 98 | /// 99 | /// Internal method to resolve a locale from a list of locales. 100 | /// 101 | Locale _resolve(Locale locale, Locale fallback, Iterable supported, bool withCountry) { 102 | if (locale == null || !_isSupported(locale, withCountry)) { 103 | return fallback ?? supported.first; 104 | } 105 | 106 | final Locale languageLocale = Locale(locale.languageCode, ""); 107 | if (supported.contains(locale)) { 108 | return locale; 109 | } else if (supported.contains(languageLocale)) { 110 | return languageLocale; 111 | } else { 112 | final Locale fallbackLocale = fallback ?? supported.first; 113 | return fallbackLocale; 114 | } 115 | } 116 | 117 | /// 118 | /// Returns true if the specified locale is supported, false otherwise. 119 | /// 120 | bool _isSupported(Locale locale, bool withCountry) { 121 | if (locale != null) { 122 | for (Locale supportedLocale in supportedLocales) { 123 | // Language must always match both locales. 124 | if (supportedLocale.languageCode != locale.languageCode) { 125 | continue; 126 | } 127 | 128 | // If country code matches, return this locale. 129 | if (supportedLocale.countryCode == locale.countryCode) { 130 | return true; 131 | } 132 | 133 | // If no country requirement is requested, check if this locale has no country. 134 | if (true != withCountry && (supportedLocale.countryCode == null || supportedLocale.countryCode.isEmpty)) { 135 | return true; 136 | } 137 | } 138 | } 139 | return false; 140 | } 141 | } 142 | 143 | String getLang(Locale l) => l == null 144 | ? null 145 | : l.countryCode != null && l.countryCode.isEmpty 146 | ? l.languageCode 147 | : l.toString(); 148 | -------------------------------------------------------------------------------- /lib/init/app.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_localizations/flutter_localizations.dart'; 4 | import 'package:flutter_wanandroid/generated/i18n.dart'; 5 | import 'package:flutter_wanandroid/model/search_history.dart'; 6 | import 'package:flutter_wanandroid/model/store.dart'; 7 | import 'package:flutter_wanandroid/model/theme.dart'; 8 | import 'package:flutter_wanandroid/routers/application.dart'; 9 | import 'package:flutter_wanandroid/routers/navigation_service.dart'; 10 | import 'package:flutter_wanandroid/routers/router.dart'; 11 | import 'package:flutter_wanandroid/utils/bugly.dart'; 12 | import 'package:flutter_wanandroid/utils/provider.dart'; 13 | import 'package:flutter_wanandroid/utils/push.dart'; 14 | import 'package:flutter_wanandroid/api/http.dart'; 15 | import 'package:flutter_wanandroid/utils/shared_preferences.dart'; 16 | import 'package:flutter_wanandroid/views/page_not_found.dart'; 17 | import 'package:flutter_wanandroid/views/splash_page/SplashPage.dart'; 18 | import 'package:provider/provider.dart'; 19 | 20 | class App{ 21 | //运行app 22 | static void run() { 23 | WidgetsFlutterBinding.ensureInitialized(); 24 | /// 全局 provider 初始化 25 | SPUtils.init().then((value) => runApp(Store.init(MyApp()))); 26 | initApp(); 27 | 28 | } 29 | 30 | 31 | 32 | //程序初始化操作 33 | static void initApp() { 34 | // 搜索历史 35 | SearchHistoryList(); 36 | 37 | DBProvider().init(true); 38 | Http.init(); 39 | XRouter.init(); 40 | XPush.init(); 41 | Bugly.init(); 42 | } 43 | } 44 | 45 | 46 | class MyApp extends StatelessWidget { 47 | // This widget is the root of your application. 48 | @override 49 | Widget build(BuildContext context) { 50 | return Consumer( 51 | builder: (context, appTheme, _) { 52 | return MaterialApp( 53 | title: 'Wan-Android', 54 | navigatorKey: NavigationService.navigatorKey, 55 | theme: appTheme.themeDate,//根据平台选择不同主题 56 | localizationsDelegates: [ 57 | S.delegate,//应用程序的翻译回调 58 | // 本地化的代理类 59 | GlobalMaterialLocalizations.delegate,//Material组件的翻译回调 60 | GlobalWidgetsLocalizations.delegate,//普通Widget的翻译回调 61 | ], 62 | supportedLocales: S.delegate.supportedLocales,//支持语系 63 | // title的国际化回调 64 | onGenerateTitle: (context){ return S.of(context).app_title; }, 65 | // 闪屏页 66 | home:Scaffold(body: SplashPage()), 67 | // 生成路由的回调函数,当导航的命名路由的时候,会使用这个来生成界面 68 | onGenerateRoute: Application.router.generator, 69 | // 页面找不到显示的 404 页面 70 | onUnknownRoute: (RouteSettings setting) => 71 | MaterialPageRoute(builder: (context) => PageNotFound()), 72 | ); 73 | }, 74 | ); 75 | } 76 | } -------------------------------------------------------------------------------- /lib/init/app_init.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_bugly/flutter_bugly.dart'; 5 | import 'package:flutter_wanandroid/init/app.dart'; 6 | 7 | /// 应用初始化 8 | class AppInit{ 9 | static void run() { 10 | // catchException(() => App.run()); 11 | // //Bugly的异常捕获上传 12 | // Bugly.postCatchedException(() => NormalApp.run()); 13 | 14 | /// 程序的主入口 15 | FlutterBugly.postCatchedException((){ 16 | /// bugly 数据上报 17 | App.run(); 18 | }); 19 | 20 | } 21 | 22 | /// 异常捕获处理 23 | static void catchException(T callback()) { 24 | 25 | /// 捕获异常的回调 26 | FlutterError.onError = (FlutterErrorDetails details) { 27 | reportErrorAndLog(details); 28 | }; 29 | runZoned>(() async { 30 | callback(); 31 | }, 32 | zoneSpecification: ZoneSpecification( 33 | print: (Zone self, ZoneDelegate parent, Zone zone, String line) { 34 | /// 收集日志 35 | collectLog(parent, zone, line); 36 | }, 37 | ), 38 | /// 未捕获的异常的回调 39 | onError: (Object obj, StackTrace stack) { 40 | var details = makeDetails(obj, stack); 41 | reportErrorAndLog(details); 42 | }, 43 | ); 44 | 45 | /// 重写异常页面 46 | ErrorWidget.builder = (FlutterErrorDetails flutterErrorDetails) { 47 | print(flutterErrorDetails.toString()); 48 | return Scaffold( 49 | body: Center( 50 | child: Text("出了点问题,我们马上修复~"), 51 | )); 52 | }; 53 | } 54 | 55 | 56 | /// 日志拦截, 收集日志 57 | static void collectLog(ZoneDelegate parent, Zone zone, String line) { 58 | parent.print(zone, "日志拦截: $line"); 59 | } 60 | 61 | /// 上报错误和日志逻辑 62 | static void reportErrorAndLog(FlutterErrorDetails details) { 63 | print('上报错误和日志逻辑: $details'); 64 | print(details); 65 | } 66 | 67 | /// 构建错误信息 68 | static FlutterErrorDetails makeDetails(Object obj, StackTrace stack) { 69 | return FlutterErrorDetails(stack: stack); 70 | } 71 | 72 | 73 | } -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_wanandroid/init/app_init.dart'; 2 | 3 | /// 程序的主入口 4 | void main() => AppInit.run(); 5 | -------------------------------------------------------------------------------- /lib/main_page.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/rendering.dart'; 4 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 5 | import 'package:flutter_wanandroid/routers/navigation_service.dart'; 6 | import 'package:flutter_wanandroid/utils/shared_preferences.dart'; 7 | import 'package:flutter_wanandroid/views/cat_page/cat_page.dart'; 8 | import 'package:flutter_wanandroid/views/collection_page/collection_page.dart'; 9 | import 'package:flutter_wanandroid/views/home_page/home_page.dart'; 10 | import 'package:flutter_wanandroid/views/mine_page/mine_page.dart'; 11 | import 'package:flutter_wanandroid/views/photo_page/photo_page.dart'; 12 | 13 | import 'package:flutter_wanandroid/model/search_history.dart'; 14 | 15 | 16 | const int ThemeColor = 0xFFC91B3A; 17 | 18 | class MainPage extends StatefulWidget { 19 | @override 20 | State createState() { 21 | return _MyHomePageState(); 22 | } 23 | } 24 | 25 | class _MyHomePageState extends State with SingleTickerProviderStateMixin { 26 | 27 | 28 | TabController controller; 29 | SearchHistoryList searchHistoryList; 30 | 31 | String data = '无'; 32 | String appBarTitle = tabData[0]['text']; 33 | static List tabData = [ 34 | {'text': '首页', 'icon': new Icon(Icons.home)}, 35 | {'text': '猫耳', 'icon': new Icon(Icons.category)}, 36 | {'text': '收藏', 'icon': new Icon(Icons.favorite)}, 37 | {'text': '图库', 'icon': new Icon(Icons.photo)}, 38 | {'text': '我的', 'icon': new Icon(Icons.person)} 39 | ]; 40 | 41 | List myTabs = []; 42 | 43 | @override 44 | void initState() { 45 | super.initState(); 46 | 47 | controller = new TabController( 48 | initialIndex: 0, vsync: this, length: 5); // 这里的length 决定有多少个底导 submenus 49 | for (int i = 0; i < tabData.length; i++) { 50 | myTabs.add(new Tab(text: tabData[i]['text'], icon: tabData[i]['icon'])); 51 | } 52 | 53 | /// 滑动监听 54 | controller.addListener(() { 55 | if (controller.indexIsChanging) { 56 | _onTabChange(); 57 | } 58 | }); 59 | } 60 | 61 | @override 62 | void dispose() { 63 | controller.dispose(); 64 | super.dispose(); 65 | } 66 | 67 | 68 | 69 | 70 | @override 71 | Widget build(BuildContext context) { 72 | //设置适配尺寸 (填入设计稿中设备的屏幕尺寸) 此处假如设计稿是按iPhone6的尺寸设计的(iPhone6 750*1334) 73 | //ScreenUtil.init(context, width: 750, height: 1334, allowFontScaling: false); 74 | ScreenUtil.init(context); 75 | /// 保存全局context 76 | NavigationService.mContext = context; 77 | return new Scaffold( 78 | body: new TabBarView(controller: controller, children: [ 79 | new HomePage(), 80 | new CatPage(), 81 | new CollectionPage(), 82 | new PhotoPage(), 83 | new MinePage(), 84 | ]), 85 | bottomNavigationBar: Material( 86 | color: const Color(0xFFF0EEEF), //底部导航栏主题颜色 87 | child: SafeArea( 88 | child: Container( 89 | height: 65.0, 90 | /// 渐变效果 91 | decoration: BoxDecoration( 92 | color: const Color(0xFFF0F0F0), 93 | boxShadow: [ 94 | BoxShadow( 95 | color: const Color(0xFFd0d0d0), 96 | blurRadius: 3.0, 97 | spreadRadius: 2.0, 98 | offset: Offset(-1.0, -1.0), 99 | ), 100 | ], 101 | ), 102 | child: TabBar( 103 | controller: controller, 104 | indicatorColor: Theme.of(context).primaryColor, 105 | //tab标签的下划线颜色 106 | // labelColor: const Color(0xFF000000), 107 | indicatorWeight: 3.0, 108 | labelColor: Theme.of(context).primaryColor, 109 | unselectedLabelColor: const Color(0xFF8E8E8E), 110 | tabs: myTabs), 111 | ), 112 | ), 113 | ), 114 | ); 115 | 116 | } 117 | 118 | /// 底部tab点击 119 | void _onTabChange() { 120 | if (this.mounted) { 121 | this.setState(() { 122 | appBarTitle = tabData[controller.index]['text']; 123 | }); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /lib/model/banner.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert' show json; 2 | 3 | class BannerModel { 4 | 5 | int errorCode; 6 | String errorMsg; 7 | List data; 8 | 9 | BannerModel.fromParams({this.errorCode, this.errorMsg, this.data}); 10 | 11 | factory BannerModel(jsonStr) => jsonStr == null ? null : jsonStr is String ? new BannerModel.fromJson(json.decode(jsonStr)) : new BannerModel.fromJson(jsonStr); 12 | 13 | BannerModel.fromJson(jsonRes) { 14 | errorCode = jsonRes['errorCode']; 15 | errorMsg = jsonRes['errorMsg']; 16 | data = jsonRes['data'] == null ? null : []; 17 | 18 | for (var dataItem in data == null ? [] : jsonRes['data']){ 19 | data.add(dataItem == null ? null : new BannerData.fromJson(dataItem)); 20 | } 21 | } 22 | 23 | @override 24 | String toString() { 25 | return '{"errorCode": $errorCode,"errorMsg": ${errorMsg != null?'${json.encode(errorMsg)}':'null'},"data": $data}'; 26 | } 27 | } 28 | 29 | class BannerData { 30 | 31 | int id; 32 | int isVisible; 33 | int order; 34 | int type; 35 | String desc; 36 | String imagePath; 37 | String title; 38 | String url; 39 | 40 | BannerData.fromParams({this.id, this.isVisible, this.order, this.type, this.desc, this.imagePath, this.title, this.url}); 41 | 42 | BannerData.fromJson(jsonRes) { 43 | id = jsonRes['id']; 44 | isVisible = jsonRes['isVisible']; 45 | order = jsonRes['order']; 46 | type = jsonRes['type']; 47 | desc = jsonRes['desc']; 48 | imagePath = jsonRes['imagePath']; 49 | title = jsonRes['title']; 50 | url = jsonRes['url']; 51 | } 52 | 53 | @override 54 | String toString() { 55 | return '{"id": $id,"isVisible": $isVisible,"order": $order,"type": $type,"desc": ${desc != null?'${json.encode(desc)}':'null'},"imagePath": ${imagePath != null?'${json.encode(imagePath)}':'null'},"title": ${title != null?'${json.encode(title)}':'null'},"url": ${url != null?'${json.encode(url)}':'null'}}'; 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /lib/model/cat.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'dart:convert' show json; 3 | 4 | import 'package:flutter_wanandroid/model/article.dart'; 5 | 6 | class CatModel{ 7 | 8 | int errorCode; 9 | String errorMsg; 10 | List data; 11 | 12 | CatModel.fromParams({this.errorCode, this.errorMsg, this.data}); 13 | factory CatModel(jsonStr) => jsonStr == null ? null : jsonStr is String ? new CatModel.fromJson(json.decode(jsonStr)) : new CatModel.fromJson(jsonStr); 14 | 15 | CatModel.fromJson(jsonRes) { 16 | errorCode = jsonRes['errorCode']; 17 | errorMsg = jsonRes['errorMsg']; 18 | data = jsonRes['data'] == null ? null :[]; 19 | 20 | /// 项目和体系的集合 21 | for (var dataItem in data == null ? [] : jsonRes['data']){ 22 | data.add(dataItem == null ? null : new Cat.fromJson(dataItem)); 23 | } 24 | 25 | } 26 | @override 27 | String toString() { 28 | return '{"errorCode": $errorCode,"errorMsg": ${errorMsg != null?'${json.encode(errorMsg)}':'null'},"data": $data}'; 29 | } 30 | } 31 | 32 | class Cat { 33 | 34 | int id; 35 | /// 导航的 id 36 | int cid; 37 | int courseId; 38 | int order; 39 | int superChapterId; 40 | int parentChapterId; 41 | int visible; 42 | bool userControlSetTop; 43 | String name; 44 | String title; 45 | String link; 46 | List cats; 47 | 48 | 49 | Cat.fromParams({this.id, this.cid, this.courseId, this.order, this.superChapterId, this.parentChapterId, this.visible, this.userControlSetTop, this.name, this.title, this.link}); 50 | Cat.fromJson(jsonRes) { 51 | superChapterId = jsonRes['superChapterId'] == null ? -1 : jsonRes['superChapterId']; 52 | parentChapterId = jsonRes['parentChapterId'] == null ? -1 : jsonRes['parentChapterId']; 53 | order = jsonRes['order']; 54 | id = jsonRes['id']; 55 | cid = jsonRes['cid'] == null ? -1 : jsonRes['cid']; 56 | courseId = jsonRes['courseId'] == null ? -1 : jsonRes['courseId']; 57 | userControlSetTop = jsonRes['userControlSetTop']; 58 | visible = jsonRes['visible']; 59 | name = jsonRes['name'] == null ? null : jsonRes['name']; 60 | link = jsonRes['link'] == null ? null : jsonRes['link']; 61 | title = jsonRes['title'] == null ? null : jsonRes['title']; 62 | cats = jsonRes['children'] == null ? null :[]; 63 | 64 | 65 | /// 获取项目、体系下的集合 66 | for (var dataItem in cats == null ? [] : jsonRes['children']){ 67 | cats.add(dataItem == null ? null : new Cat.fromJson(dataItem)); 68 | } 69 | 70 | } 71 | 72 | String toString() { 73 | return '{"superChapterId": $superChapterId,"parentChapterId": $parentChapterId,"order": $order,' 74 | '"id": $id,"courseId": ${courseId != null?'${json.encode(courseId)}':'-1'},"userControlSetTop": ${userControlSetTop != null?'${json.encode(userControlSetTop)}':'false'},' 75 | '"visible": ${visible != null?'${json.encode(visible)}':'0'},"link": ${link != null?'${json.encode(link)}':'null'},"title": ${title != null?'${json.encode(title)}':'null'},' 76 | '"name": ${name != null?'${json.encode(name)}':'null'},"cats": $cats}'; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/model/coin.dart: -------------------------------------------------------------------------------- 1 | class Coin { 2 | String errorMsg; 3 | int errorCode; 4 | DataBean data; 5 | 6 | Coin({this.errorMsg, this.errorCode, this.data}); 7 | 8 | Coin.fromJson(Map json) { 9 | this.errorMsg = json['errorMsg']; 10 | this.errorCode = json['errorCode']; 11 | this.data = json['data'] != null ? DataBean.fromJson(json['data']) : null; 12 | } 13 | 14 | Map toJson() { 15 | final Map data = new Map(); 16 | data['errorMsg'] = this.errorMsg; 17 | data['errorCode'] = this.errorCode; 18 | if (this.data != null) { 19 | data['data'] = this.data.toJson(); 20 | } 21 | return data; 22 | } 23 | 24 | } 25 | 26 | class DataBean { 27 | bool over; 28 | int curPage; 29 | int offset; 30 | int pageCount; 31 | int size; 32 | int total; 33 | List datas; 34 | 35 | DataBean({this.over, this.curPage, this.offset, this.pageCount, this.size, this.total, this.datas}); 36 | 37 | DataBean.fromJson(Map json) { 38 | this.over = json['over']; 39 | this.curPage = json['curPage']; 40 | this.offset = json['offset']; 41 | this.pageCount = json['pageCount']; 42 | this.size = json['size']; 43 | this.total = json['total']; 44 | this.datas = (json['datas'] as List)!=null?(json['datas'] as List).map((i) => CoinInfo.fromJson(i)).toList():null; 45 | } 46 | 47 | Map toJson() { 48 | final Map data = new Map(); 49 | data['over'] = this.over; 50 | data['curPage'] = this.curPage; 51 | data['offset'] = this.offset; 52 | data['pageCount'] = this.pageCount; 53 | data['size'] = this.size; 54 | data['total'] = this.total; 55 | data['datas'] = this.datas != null?this.datas.map((i) => i.toJson()).toList():null; 56 | return data; 57 | } 58 | } 59 | 60 | class CoinInfo { 61 | String username; 62 | int coinCount; 63 | int level; 64 | String rank; 65 | int userId; 66 | 67 | CoinInfo({this.username, this.coinCount, this.level, this.rank, this.userId}); 68 | 69 | CoinInfo.fromJson(Map json) { 70 | this.username = json['username']; 71 | this.coinCount = json['coinCount']; 72 | this.level = json['level']; 73 | this.rank = json['rank']; 74 | this.userId = json['userId']; 75 | } 76 | 77 | Map toJson() { 78 | final Map data = new Map(); 79 | data['username'] = this.username; 80 | data['coinCount'] = this.coinCount; 81 | data['level'] = this.level; 82 | data['rank'] = this.rank; 83 | data['userId'] = this.userId; 84 | return data; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /lib/model/collect.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter_wanandroid/utils/sql.dart'; 4 | 5 | 6 | abstract class CollectionInterface { 7 | String get id; 8 | String get title; 9 | String get link; 10 | } 11 | 12 | class Collection implements CollectionInterface { 13 | String id; 14 | String title; 15 | String link; 16 | 17 | Collection({this.id, this.title, this.link}); 18 | 19 | factory Collection.fromJSON(Map json){ 20 | return Collection(id: json['id'],title: json['title'],link: json['link']); 21 | } 22 | 23 | Object toMap() { 24 | return {'id': id, 'title': title, 'link':link}; 25 | } 26 | 27 | } 28 | 29 | class CollectionControlModel { 30 | final String table = 'collection'; 31 | Sql sql; 32 | 33 | CollectionControlModel() { 34 | sql = Sql.setTable(table); 35 | } 36 | 37 | // 获取所有的收藏 38 | 39 | // 插入新收藏 40 | Future insert(Collection collection) { 41 | var result = 42 | sql.insert({'id': collection.id, 'title': collection.title, 'link':collection.link}); 43 | return result; 44 | } 45 | 46 | // 获取全部的收藏 47 | Future> getAllCollection() async { 48 | List list = await sql.getByCondition(); 49 | List resultList = []; 50 | list.forEach((item){ 51 | print(item); 52 | resultList.add(Collection.fromJSON(item)); 53 | }); 54 | return resultList; 55 | } 56 | 57 | // 通过 ID 获取router 58 | Future getRouterById(int id) async { 59 | List list = await sql.getByCondition(conditions: {'id': id}); 60 | return list; 61 | } 62 | 63 | // 删除 64 | Future deleteById(int id) async{ 65 | return await sql.delete(id.toString(),'id'); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/model/constant.dart: -------------------------------------------------------------------------------- 1 | /// 常量类 2 | class Constant{ 3 | static final String heroPhotoDetail = "hero_photo_detail"; 4 | } -------------------------------------------------------------------------------- /lib/model/provider/state_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// 跨 widget 状态管理封装 4 | class IProvider extends InheritedWidget { 5 | // 数据 6 | final T data; 7 | // 方法 8 | final Function() doSomeThing; 9 | 10 | IProvider({Key key, Widget child, this.data, this.doSomeThing}) 11 | : super(key: key, child: child); 12 | 13 | @override 14 | bool updateShouldNotify(IProvider oldWidget) { 15 | return data != oldWidget.data; 16 | } 17 | static IProvider of(BuildContext context) { 18 | return context.dependOnInheritedWidgetOfExactType>(); 19 | } 20 | } -------------------------------------------------------------------------------- /lib/model/search_history.dart: -------------------------------------------------------------------------------- 1 | /// target: 搜索WidgetDemo中的历史记录model 2 | import 'dart:convert'; 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_wanandroid/utils/shared_preferences.dart'; 6 | 7 | import '../constant/shared_preferences_keys.dart'; 8 | 9 | 10 | class SearchHistory { 11 | final String name; 12 | final String targetRouter; 13 | 14 | SearchHistory({@required this.name, @required this.targetRouter}); 15 | } 16 | 17 | class SearchHistoryList { 18 | static SearchHistoryList _instance; 19 | static List _searchHistoryList = []; 20 | 21 | factory SearchHistoryList() { 22 | if (_instance == null) { 23 | print(new ArgumentError(['SearchHistoryList need instantiatied SpUtil at first timte '])); 24 | } 25 | return _getInstance(); 26 | } 27 | 28 | static SearchHistoryList _getInstance() { 29 | if (_instance == null) { 30 | String json = SPUtils.get(SharedPreferencesKeys.searchHistory); 31 | print("SearchHistoryList json---->"+json.toString()); 32 | _instance = new SearchHistoryList.fromJSON(json); 33 | } 34 | return _instance; 35 | } 36 | 37 | 38 | // 存放的最大数量 39 | int _count = 10; 40 | 41 | SearchHistoryList.fromJSON(String jsonData) { 42 | _searchHistoryList = []; 43 | if (jsonData == null) { 44 | return; 45 | } 46 | List jsonList = json.decode(jsonData); 47 | jsonList.forEach((value) { 48 | _searchHistoryList.add(SearchHistory( 49 | name: value['name'], targetRouter: value['targetRouter'])); 50 | }); 51 | } 52 | 53 | List getList() { 54 | return _searchHistoryList; 55 | } 56 | 57 | clear() { 58 | SPUtils.remove(SharedPreferencesKeys.searchHistory); 59 | _searchHistoryList = []; 60 | } 61 | 62 | save() { 63 | SPUtils.putString(SharedPreferencesKeys.searchHistory, this.toJson()); 64 | } 65 | 66 | add(SearchHistory item) { 67 | print("_searchHistoryList> ${_searchHistoryList.length}"); 68 | for (SearchHistory value in _searchHistoryList) { 69 | if (value.name == item.name) { 70 | return; 71 | } 72 | } 73 | if (_searchHistoryList.length > _count) { 74 | _searchHistoryList.removeAt(0); 75 | } 76 | _searchHistoryList.add(item); 77 | save(); 78 | } 79 | 80 | toJson() { 81 | List> jsonList = []; 82 | _searchHistoryList.forEach((SearchHistory value) { 83 | jsonList.add({'name': value.name, 'targetRouter': value.targetRouter}); 84 | }); 85 | return json.encode(jsonList); 86 | } 87 | 88 | @override 89 | String toString() { 90 | return this.toJson(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /lib/model/splash.dart: -------------------------------------------------------------------------------- 1 | /// 每日一图 2 | class Splash { 3 | List images; 4 | Tooltips tooltips; 5 | 6 | Splash({this.images, this.tooltips}); 7 | 8 | Splash.fromJson(Map json) { 9 | if (json['images'] != null) { 10 | images = new List(); 11 | json['images'].forEach((v) { 12 | images.add(new Images.fromJson(v)); 13 | }); 14 | } 15 | tooltips = json['tooltips'] != null 16 | ? new Tooltips.fromJson(json['tooltips']) 17 | : null; 18 | } 19 | 20 | Map toJson() { 21 | final Map data = new Map(); 22 | if (this.images != null) { 23 | data['images'] = this.images.map((v) => v.toJson()).toList(); 24 | } 25 | if (this.tooltips != null) { 26 | data['tooltips'] = this.tooltips.toJson(); 27 | } 28 | return data; 29 | } 30 | } 31 | 32 | class Images { 33 | String startdate; 34 | String fullstartdate; 35 | String enddate; 36 | String url; 37 | String urlbase; 38 | String copyright; 39 | String copyrightlink; 40 | String title; 41 | String quiz; 42 | bool wp; 43 | String hsh; 44 | int drk; 45 | int top; 46 | int bot; 47 | List hs; 48 | 49 | Images( 50 | {this.startdate, 51 | this.fullstartdate, 52 | this.enddate, 53 | this.url, 54 | this.urlbase, 55 | this.copyright, 56 | this.copyrightlink, 57 | this.title, 58 | this.quiz, 59 | this.wp, 60 | this.hsh, 61 | this.drk, 62 | this.top, 63 | this.bot, 64 | this.hs}); 65 | 66 | Images.fromJson(Map json) { 67 | startdate = json['startdate']; 68 | fullstartdate = json['fullstartdate']; 69 | enddate = json['enddate']; 70 | url = json['url']; 71 | urlbase = json['urlbase']; 72 | copyright = json['copyright']; 73 | copyrightlink = json['copyrightlink']; 74 | title = json['title']; 75 | quiz = json['quiz']; 76 | wp = json['wp']; 77 | hsh = json['hsh']; 78 | drk = json['drk']; 79 | top = json['top']; 80 | bot = json['bot']; 81 | if (json['hs'] != null) { 82 | hs = new List(); 83 | // json['hs'].forEach((v) { 84 | // hs.add(new Null.fromJson(v)); 85 | // }); 86 | } 87 | } 88 | 89 | Map toJson() { 90 | final Map data = new Map(); 91 | data['startdate'] = this.startdate; 92 | data['fullstartdate'] = this.fullstartdate; 93 | data['enddate'] = this.enddate; 94 | data['url'] = this.url; 95 | data['urlbase'] = this.urlbase; 96 | data['copyright'] = this.copyright; 97 | data['copyrightlink'] = this.copyrightlink; 98 | data['title'] = this.title; 99 | data['quiz'] = this.quiz; 100 | data['wp'] = this.wp; 101 | data['hsh'] = this.hsh; 102 | data['drk'] = this.drk; 103 | data['top'] = this.top; 104 | data['bot'] = this.bot; 105 | if (this.hs != null) { 106 | // data['hs'] = this.hs.map((v) => v.toJson()).toList(); 107 | } 108 | return data; 109 | } 110 | } 111 | 112 | class Tooltips { 113 | String loading; 114 | String previous; 115 | String next; 116 | String walle; 117 | String walls; 118 | 119 | Tooltips({this.loading, this.previous, this.next, this.walle, this.walls}); 120 | 121 | Tooltips.fromJson(Map json) { 122 | loading = json['loading']; 123 | previous = json['previous']; 124 | next = json['next']; 125 | walle = json['walle']; 126 | walls = json['walls']; 127 | } 128 | 129 | Map toJson() { 130 | final Map data = new Map(); 131 | data['loading'] = this.loading; 132 | data['previous'] = this.previous; 133 | data['next'] = this.next; 134 | data['walle'] = this.walle; 135 | data['walls'] = this.walls; 136 | return data; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /lib/model/store.dart: -------------------------------------------------------------------------------- 1 | // 状态管理 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:provider/provider.dart'; 5 | import 'package:flutter_wanandroid/model/theme.dart'; 6 | 7 | class Store { 8 | /// 单例 9 | Store._internal(); 10 | 11 | /// 全局初始化 12 | static init(Widget child) { 13 | //多个Provider 14 | return MultiProvider( 15 | providers: [ 16 | ChangeNotifierProvider(create: (_) => AppTheme(AppTheme.getDefaultTheme())), 17 | ], 18 | child: child, 19 | ); 20 | } 21 | 22 | //获取值 of(context) 这个会引起页面的整体刷新,如果全局是页面级别的 23 | static T value(BuildContext context, {bool listen = false}) { 24 | return Provider.of(context, listen: listen); 25 | } 26 | 27 | //获取值 of(context) 这个会引起页面的整体刷新,如果全局是页面级别的 28 | static T of(BuildContext context, {bool listen = true}) { 29 | return Provider.of(context, listen: listen); 30 | } 31 | 32 | // 不会引起页面的刷新,只刷新了 Consumer 的部分,极大地缩小你的控件刷新范围 33 | static Consumer connect({builder, child}) { 34 | return Consumer(builder: builder, child: child); 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /lib/model/theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_wanandroid/utils/shared_preferences.dart'; 5 | 6 | /// provider 主题修改 7 | 8 | class AppTheme with ChangeNotifier{ 9 | 10 | static final List materialColors = [ 11 | Colors.blue, 12 | Colors.lightBlue, 13 | Colors.red, 14 | Colors.pink, 15 | Colors.purple, 16 | Colors.grey, 17 | Colors.orange, 18 | Colors.amber, 19 | Colors.yellow, 20 | Colors.lightGreen, 21 | Colors.green, 22 | Colors.lime 23 | ]; 24 | 25 | MaterialColor _mThemeColor; 26 | AppTheme(this._mThemeColor); 27 | 28 | // defaultTargetPlatform == TargetPlatform.iOS ? kIOSTheme :kAndroidTheme, 29 | MaterialColor get themeColor => _mThemeColor ; 30 | 31 | // 根据 Platform 设置主题 32 | ThemeData get themeDate => defaultTargetPlatform == TargetPlatform.iOS ? getIOSTheme() :getAndroidTheme(); 33 | // iOS浅色主题 34 | ThemeData getIOSTheme(){ 35 | return ThemeData( 36 | primarySwatch: getDefaultTheme(), 37 | buttonColor: getDefaultTheme(), 38 | brightness: Brightness.dark, 39 | //深色主题 40 | accentColor: Color(0xFF888888) 41 | ); 42 | } 43 | ThemeData getAndroidTheme(){ 44 | return ThemeData( 45 | primarySwatch: getDefaultTheme(), 46 | buttonColor: getDefaultTheme(), 47 | brightness: Brightness.light, 48 | //亮色主题 49 | accentColor: Color(0xFF888888) 50 | ); 51 | } 52 | 53 | 54 | 55 | /// 获取默认主题 56 | static MaterialColor getDefaultTheme() { 57 | return materialColors[SPUtils.getThemeColorIndex()]; 58 | } 59 | 60 | 61 | /// 修改主题颜色 62 | void changeTheme(int colorIndex){ 63 | _mThemeColor = materialColors[colorIndex]; 64 | /// 保存主题索引值 65 | SPUtils.saveThemeColorIndex(colorIndex); 66 | notifyListeners(); 67 | } 68 | } -------------------------------------------------------------------------------- /lib/model/user.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:flutter_wanandroid/model/user_model.dart'; 3 | import 'package:flutter_wanandroid/utils/shared_preferences.dart'; 4 | import 'package:shared_preferences/shared_preferences.dart'; 5 | 6 | import '../constant/shared_preferences_keys.dart'; 7 | 8 | class User { 9 | static final User singleton = User._internal(); 10 | 11 | factory User() { 12 | return singleton; 13 | } 14 | 15 | User._internal(); 16 | 17 | List cookie; 18 | String userName; 19 | bool isLogin = false; 20 | 21 | void setLogin(bool login) { 22 | this.isLogin = login; 23 | } 24 | 25 | bool isUserLogin() { 26 | return isLogin; 27 | } 28 | 29 | void saveUserInfo(UserModel _userModel, Response response) { 30 | List cookies = response.headers["set-cookie"]; 31 | print("cookie---->:"+cookies.toString()); 32 | cookie = cookies; 33 | userName = _userModel.data.username; 34 | _saveInfo(); 35 | } 36 | 37 | Future getUserInfo() async { 38 | SharedPreferences sp = await SharedPreferences.getInstance(); 39 | List cookies = sp.getStringList("cookies"); 40 | if (cookies != null) { 41 | cookie = cookies; 42 | } 43 | String username = sp.getString("username"); 44 | if (username != null) { 45 | userName = username; 46 | } 47 | return userName; 48 | } 49 | 50 | _saveInfo() async { 51 | print("登录成功,保存用户信息:" + userName); 52 | SharedPreferences sp = await SharedPreferences.getInstance(); 53 | sp.setStringList("cookies", cookie); 54 | sp.setString("username", userName); 55 | } 56 | 57 | /// sp 保存用户名 58 | void saveUserName(UserModel _userModel) { 59 | SPUtils.putString(SharedPreferencesKeys.userName, _userModel.data.username); 60 | } 61 | 62 | /// 获取用户名 63 | // String getUserName() { 64 | // String username = ""; 65 | // SpUtil.getInstance().then((_sp) { 66 | // username = _sp.getString(SharedPreferencesKeys.userName); 67 | // print("用户username:" + username.toString()); 68 | // if (username != null && username.isNotEmpty) { 69 | // _setLogin(true); 70 | // } else { 71 | // _setLogin(false); 72 | // } 73 | // }); 74 | // print("用户username--->:" + username.toString()); 75 | // return username; 76 | // } 77 | 78 | void clearUserInfor() { 79 | cookie = null; 80 | userName = null; 81 | _clearInfo(); 82 | } 83 | 84 | _clearInfo() async { 85 | SharedPreferences sp = await SharedPreferences.getInstance(); 86 | sp.setStringList("cookies", null); 87 | sp.setString("username", null); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib/model/user_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert' show json; 2 | 3 | class UserModel { 4 | 5 | int errorCode; 6 | String errorMsg; 7 | UserData data; 8 | 9 | UserModel.fromParams({this.errorCode, this.errorMsg, this.data}); 10 | 11 | factory UserModel(jsonStr) => jsonStr == null ? null : jsonStr is String ? new UserModel.fromJson(json.decode(jsonStr)) : new UserModel.fromJson(jsonStr); 12 | 13 | UserModel.fromJson(jsonRes) { 14 | errorCode = jsonRes['errorCode']; 15 | errorMsg = jsonRes['errorMsg']; 16 | data = jsonRes['data'] == null ? null : new UserData.fromJson(jsonRes['data']); 17 | } 18 | 19 | @override 20 | String toString() { 21 | return '{"errorCode": $errorCode,"errorMsg": ${errorMsg != null?'${json.encode(errorMsg)}':'null'},"data": $data}'; 22 | } 23 | } 24 | 25 | class UserData { 26 | 27 | bool admin; 28 | int id; 29 | int type; 30 | String email; 31 | String icon; 32 | String password; 33 | String token; 34 | String nickname; 35 | String publicName; 36 | String username; 37 | List chapterTops; 38 | List collectIds; 39 | 40 | UserData.fromParams({this.admin, this.id, this.type, this.email, this.icon, this.password, this.token, this.nickname,this.publicName,this.username, this.chapterTops, this.collectIds}); 41 | 42 | UserData.fromJson(jsonRes) { 43 | admin = jsonRes['admin']; 44 | id = jsonRes['id']; 45 | type = jsonRes['type']; 46 | email = jsonRes['email']; 47 | icon = jsonRes['icon']; 48 | password = jsonRes['password']; 49 | token = jsonRes['token']; 50 | nickname = jsonRes['nickname']; 51 | publicName = jsonRes['publicName']; 52 | username = jsonRes['username']; 53 | chapterTops = jsonRes['chapterTops'] == null ? null : []; 54 | 55 | for (var chapterTopsItem in chapterTops == null ? [] : jsonRes['chapterTops']){ 56 | chapterTops.add(chapterTopsItem); 57 | } 58 | 59 | collectIds = jsonRes['collectIds'] == null ? null : []; 60 | 61 | for (var collectIdsItem in collectIds == null ? [] : jsonRes['collectIds']){ 62 | collectIds.add(collectIdsItem); 63 | } 64 | } 65 | 66 | @override 67 | String toString() { 68 | return '{"id": $id,"type": $type,"email": ${email != null?'${json.encode(email)}':'null'},"icon": ${icon != null?'${json.encode(icon)}':'null'},"password": ${password != null?'${json.encode(password)}':'null'},"token": ${token != null?'${json.encode(token)}':'null'},"username": ${username != null?'${json.encode(username)}':'null'},"chapterTops": $chapterTops,"collectIds": $collectIds}'; 69 | } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /lib/routers/application.dart: -------------------------------------------------------------------------------- 1 | import 'package:event_bus/event_bus.dart'; 2 | import 'package:fluro/fluro.dart'; 3 | import 'package:flutter/material.dart' hide Router; 4 | import 'package:flutter/cupertino.dart' hide Router; 5 | import 'package:flutter_wanandroid/utils/shared_preferences.dart'; 6 | 7 | /// 全局变量 8 | class Application { 9 | static Router router; 10 | static EventBus eventBus; 11 | 12 | 13 | } -------------------------------------------------------------------------------- /lib/routers/navigation_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | /// 无 context 跳转页面 6 | class NavigationService { 7 | 8 | static GlobalKey navigatorKey = GlobalKey(); 9 | /// 全局 context 10 | static dynamic mContext; 11 | // 12 | // static Future navigateTo(Route route) { 13 | // return navigatorKey.currentState.push(route); 14 | // } 15 | // 16 | // static bool goBack() { 17 | // return navigatorKey.currentState.pop(); 18 | // } 19 | } -------------------------------------------------------------------------------- /lib/routers/router.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluro/fluro.dart'; 2 | import 'package:flutter/material.dart' hide Router; 3 | import 'package:flutter/cupertino.dart' hide Router; 4 | import 'package:flutter_wanandroid/routers/application.dart'; 5 | import 'package:flutter_wanandroid/routers/router_handler.dart'; 6 | import 'package:flutter_wanandroid/routers/router_path.dart'; 7 | 8 | class XRouter { 9 | static Router router; 10 | 11 | 12 | static void init() { 13 | router = Router(); 14 | Application.router = router; 15 | configureRoutes(router); 16 | } 17 | 18 | static void configureRoutes(Router router) { 19 | router.notFoundHandler = Handler( 20 | handlerFunc: (BuildContext context, Map> params) { 21 | print("route is not find !"); 22 | return null; 23 | }); 24 | var mTransitiontype = TransitionType.inFromRight; 25 | // 首页 26 | router.define(RouterPath.root, handler: rootHandler); 27 | // home 页 28 | router.define(RouterPath.home, handler: homeHandler ); 29 | // web页面 30 | router.define(RouterPath.webViewPage,handler:webViewPageHand, transitionType:mTransitiontype); 31 | router.define(RouterPath.category,handler:categoryHandler, transitionType:mTransitiontype); 32 | router.define(RouterPath.treeList,handler:treeListHandler, transitionType:mTransitiontype); 33 | router.define(RouterPath.naviList,handler:naviListHandler, transitionType:mTransitiontype); 34 | router.define(RouterPath.projectList,handler:projectHandler, transitionType:mTransitiontype); 35 | router.define(RouterPath.errorPage,handler:pageNotFoundHandler, transitionType:mTransitiontype); 36 | // 项目页面 37 | router.define(RouterPath.photoDetailPage,handler:photoDetailPageHand, transitionType:mTransitiontype); 38 | router.define(RouterPath.login,handler:loginHandler, transitionType:mTransitiontype); 39 | router.define(RouterPath.register,handler:registerHandler, transitionType:mTransitiontype); 40 | // 关于页面 41 | router.define(RouterPath.about,handler:aboutHandler, transitionType:mTransitiontype); 42 | router.define(RouterPath.coinRank,handler:coinRankHandler, transitionType:mTransitiontype); 43 | router.define(RouterPath.myCollect,handler:myCollectsHandler, transitionType:mTransitiontype); 44 | // 二级分类 45 | router.define(RouterPath.subCat,handler:subCatHandler, transitionType:mTransitiontype); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /lib/routers/router_path.dart: -------------------------------------------------------------------------------- 1 | /// 路由路径 2 | class RouterPath{ 3 | 4 | static String root = "/main"; 5 | static String home = "/home"; 6 | static String subCat = "/cat-sub"; 7 | static String category = '/category'; 8 | static String treeList = '/tree-list'; 9 | static String naviList = '/navi-list'; 10 | static String projectList = '/project-list'; 11 | static String webViewPage = '/web-view-page'; 12 | static String thirdPage = '/third-page'; 13 | static String errorPage = '/error-page'; 14 | static String photoDetailPage = '/photo-detail-page'; 15 | 16 | static String register = '/register'; 17 | static String login = '/login'; 18 | static String about = '/about'; 19 | static String coinRank = '/coin-rank'; 20 | static String myCollect = '/my-collect'; 21 | } -------------------------------------------------------------------------------- /lib/utils/bugly.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_bugly/flutter_bugly.dart'; 2 | 3 | class Bugly { 4 | Bugly._internal(); 5 | 6 | static const String BUGLY_APP_ID_ANDROID = "15792a0daa"; 7 | static const String BUGLY_APP_ID_IOS = "15792a0daa";// iOS暂未申请id 8 | 9 | //============================统计==================================// 10 | 11 | ///初始化Bugly 12 | static void init() { 13 | FlutterBugly.init( 14 | androidAppId: BUGLY_APP_ID_ANDROID, 15 | iOSAppId: BUGLY_APP_ID_IOS) 16 | .then((_result) { 17 | print("Bugly初始化结果: " + _result.message); 18 | print("Bugly初始化结果: ${_result.isSuccess}" ); 19 | }); 20 | 21 | } 22 | 23 | //============================更新==================================// 24 | 25 | ///检查更新 26 | static Future checkUpgrade() { 27 | return FlutterBugly.checkUpgrade(); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /lib/utils/event.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:event_bus/event_bus.dart'; 4 | 5 | //EventBus工具类 6 | class XEvent { 7 | XEvent._internal(); 8 | 9 | static Map sEventPool = {}; 10 | 11 | static Map> sStreamPool = {}; 12 | 13 | static EventBus getEvent(String eventName, {bool isSync = false}) { 14 | EventBus event = sEventPool[eventName]; 15 | if (event == null) { 16 | event = new EventBus(sync: isSync); 17 | sEventPool[eventName] = event; 18 | } 19 | return event; 20 | } 21 | 22 | //订阅信息, 默认是异步的 23 | static StreamSubscription on(String eventName, void onData(T event), 24 | {bool isSync = false, 25 | Function onError, 26 | void onDone(), 27 | bool cancelOnError}) { 28 | StreamSubscription stream = getEvent(eventName, isSync: isSync) 29 | .on() 30 | .listen(onData, 31 | onError: onError, onDone: onDone, cancelOnError: cancelOnError); 32 | 33 | List streams = sStreamPool[eventName]; 34 | if (streams == null) { 35 | streams = []; 36 | streams.add(stream); 37 | sStreamPool[eventName] = streams; 38 | } else { 39 | streams.add(stream); 40 | } 41 | return stream; 42 | } 43 | 44 | //事件发送 45 | static void post(String eventName, event) { 46 | EventBus eventBus = getEvent(eventName); 47 | eventBus.fire(event); 48 | } 49 | 50 | //订阅取消 51 | static void cancelAll(String eventName) { 52 | List streams = sStreamPool[eventName]; 53 | if (streams != null) { 54 | for (StreamSubscription item in streams) { 55 | item.cancel(); 56 | } 57 | streams.clear(); 58 | } 59 | } 60 | 61 | //订阅取消 62 | static void cancel(String eventName, StreamSubscription subscription) { 63 | if (subscription == null) return; 64 | List streams = sStreamPool[eventName]; 65 | if (streams != null) { 66 | subscription.cancel(); 67 | streams.remove(subscription); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/utils/image.dart: -------------------------------------------------------------------------------- 1 | import 'package:extended_image/extended_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 4 | 5 | class ImageUtil{ 6 | 7 | static showBasicImage(String url){ 8 | ExtendedImage.network( 9 | url, 10 | fit: BoxFit.fill, 11 | ); 12 | } 13 | 14 | static showCircleImage(String url,){ 15 | ExtendedImage.network( 16 | url, 17 | width: ScreenUtil().setWidth(400), 18 | height: ScreenUtil().setWidth(400), 19 | fit: BoxFit.fill, 20 | cache: true, 21 | border: Border.all(color: Colors.red, width: 1.0), 22 | borderRadius: BorderRadius.all(Radius.circular(30.0)), 23 | //cancelToken: cancellationToken, 24 | ); 25 | } 26 | } -------------------------------------------------------------------------------- /lib/utils/provider.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'dart:typed_data'; 4 | 5 | import 'package:path/path.dart'; 6 | import 'package:sqflite/sqflite.dart'; 7 | import 'package:flutter/services.dart' show rootBundle; 8 | 9 | 10 | /// 数据库提供者 11 | class DBProvider { 12 | static Database db; 13 | 14 | // 获取数据库中所有的表 15 | Future getTables() async { 16 | if (db == null) { 17 | return Future.value([]); 18 | } 19 | List tables = await db.rawQuery('SELECT name FROM sqlite_master WHERE type = "table"'); 20 | List targetList = []; 21 | tables.forEach((item) { 22 | targetList.add(item['name']); 23 | }); 24 | print("所有表格:"+targetList.toString()); 25 | return targetList; 26 | } 27 | 28 | // 检查数据库中, 表是否完整, 在部份android中, 会出现表丢失的情况 29 | Future checkTableIsRight() async { 30 | List expectTables = ['collection']; 31 | 32 | List tables = await getTables(); 33 | 34 | for(int i = 0; i < expectTables.length; i++) { 35 | if (!tables.contains(expectTables[i])) { 36 | return false; 37 | } 38 | } 39 | return true; 40 | 41 | } 42 | 43 | //初始化数据库 44 | 45 | Future init(bool isCreate) async { 46 | //Get a location using getDatabasesPath 47 | String databasesPath = await getDatabasesPath(); 48 | String path = join(databasesPath, 'flutter.db'); 49 | print("数据库的路径:"+path); 50 | try { 51 | db = await openDatabase(path); 52 | } catch (e) { 53 | print("错误信息 Error $e"); 54 | } 55 | bool tableIsRight = await this.checkTableIsRight(); 56 | 57 | print("表格是否存在:"+tableIsRight.toString()); 58 | if (!tableIsRight) { 59 | // 关闭上面打开的db,否则无法执行open 60 | db.close(); 61 | // Delete the database 62 | await deleteDatabase(path); 63 | ByteData data = await rootBundle.load(join("assets", "app.db")); 64 | List bytes = 65 | data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); 66 | await new File(path).writeAsBytes(bytes); 67 | 68 | db = await openDatabase(path, version: 1, 69 | onCreate: (Database db, int version) async { 70 | print('数据库的版本号:db created version is $version'); 71 | }, onOpen: (Database db) async { 72 | print('新的数据库:new db opened'); 73 | }); 74 | } else { 75 | print("打开旧的数据库:Opening existing database"); 76 | } 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /lib/utils/push.dart: -------------------------------------------------------------------------------- 1 | //消息推送 2 | import 'package:flutter_wanandroid/utils/event.dart'; 3 | import 'package:jpush_flutter/jpush_flutter.dart'; 4 | 5 | const String EVENT_NOTIFICATION_MESSAGE = "event_notification_message"; 6 | const String EVENT_NOTIFICATION_OPEN = "event_notification_open"; 7 | const String EVENT_CUSTOM_MESSAGE = "event_custom_message"; 8 | 9 | class XPush { 10 | XPush._internal(); 11 | 12 | static final JPush jpush = JPush(); 13 | 14 | //================初始化=====================// 15 | 16 | ///初始化 17 | static void init() async { 18 | initJPushSDK(); 19 | registerEvent(); 20 | } 21 | 22 | ///初始化JPush 23 | static void initJPushSDK() { 24 | jpush.setup( 25 | appKey: "e510f2e57ab1cb6d33fdc233", //你自己应用的 AppKey 26 | channel: "developer-default", 27 | production: false, 28 | debug: true, 29 | ); 30 | //通知权限的申请 31 | jpush.applyPushAuthority( 32 | NotificationSettingsIOS(sound: true, alert: true, badge: true)); 33 | } 34 | 35 | ///注册事件监听 36 | static void registerEvent() { 37 | jpush.addEventHandler( 38 | onReceiveNotification: notificationEvent, 39 | onOpenNotification: notificationOpenEvent, 40 | onReceiveMessage: customMessageEvent); 41 | } 42 | 43 | //通知消息 44 | static EventHandler notificationEvent = (Map message) async { 45 | print("JPush onReceiveNotification: $message"); 46 | XEvent.post(EVENT_NOTIFICATION_MESSAGE, message); 47 | }; 48 | 49 | //通知被点击 50 | static EventHandler notificationOpenEvent = 51 | (Map message) async { 52 | print("JPush onOpenNotification: $message"); 53 | XEvent.post(EVENT_NOTIFICATION_OPEN, message); 54 | }; 55 | 56 | //自定义消息 57 | static EventHandler customMessageEvent = 58 | (Map message) async { 59 | print("JPush onReceiveMessage: $message"); 60 | XEvent.post(EVENT_CUSTOM_MESSAGE, message); 61 | }; 62 | 63 | static Future getRegistrationID() { 64 | return jpush.getRegistrationID(); 65 | } 66 | 67 | static List string2List(String value) { 68 | return value.split(","); 69 | } 70 | 71 | //================别名=====================// 72 | 73 | ///绑定别名 74 | static Future setAlias(String alias) async { 75 | Map reslut = await jpush.setAlias(alias); 76 | return reslut != null && reslut.containsKey("alias"); 77 | } 78 | 79 | ///解绑别名 80 | static Future deleteAlias() async { 81 | Map reslut = await jpush.deleteAlias(); 82 | return reslut != null && reslut.containsKey("alias"); 83 | } 84 | 85 | //================标签=====================// 86 | 87 | ///添加标签 88 | static Future addTags(List tags) async { 89 | Map reslut = await jpush.addTags(tags); 90 | return reslut != null && reslut.containsKey("tags"); 91 | } 92 | 93 | ///删除标签 94 | static Future deleteTags(List tags) async { 95 | Map reslut = await jpush.deleteTags(tags); 96 | return reslut != null && reslut.containsKey("tags"); 97 | } 98 | 99 | ///获取标签 100 | static Future> getTags() { 101 | return jpush.getAllTags(); 102 | } 103 | 104 | ///设置标签 105 | static Future setTags(List tags) async { 106 | Map reslut = await jpush.setTags(tags); 107 | return reslut != null && reslut.containsKey("tags"); 108 | } 109 | 110 | ///清除标签 111 | static Future cleanTags() async { 112 | Map reslut = await jpush.cleanTags(); 113 | return reslut != null && reslut.containsKey("tags"); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /lib/utils/shared_preferences.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'dart:async'; 3 | 4 | import 'package:shared_preferences/shared_preferences.dart'; 5 | 6 | 7 | /// 用来做shared_preferences的存储 8 | class SPUtils { 9 | /// 内部构造方法,可避免外部暴露构造函数,进行实例化 10 | SPUtils._internal(); 11 | 12 | static SharedPreferences _spf; 13 | 14 | static Future init() async { 15 | if (_spf == null) { 16 | _spf = await SharedPreferences.getInstance(); 17 | } 18 | return _spf; 19 | } 20 | 21 | 22 | static bool _beforeCheck() { 23 | if (_spf == null) { 24 | return true; 25 | } 26 | return false; 27 | } 28 | // 判断是否存在数据 29 | static bool hasKey(String key) { 30 | Set keys = getKeys(); 31 | return keys.contains(key); 32 | } 33 | 34 | static Set getKeys() { 35 | if (_beforeCheck()) return null; 36 | return _spf.getKeys(); 37 | } 38 | 39 | static get(String key) { 40 | if (_beforeCheck()) return null; 41 | return _spf.get(key); 42 | } 43 | 44 | static getString(String key) { 45 | if (_beforeCheck()) return null; 46 | return _spf.getString(key); 47 | } 48 | 49 | static Future putString(String key, String value) { 50 | if (_beforeCheck()) return null; 51 | return _spf.setString(key, value); 52 | } 53 | 54 | static bool getBool(String key) { 55 | if (_beforeCheck()) return null; 56 | return _spf.getBool(key); 57 | } 58 | 59 | static Future putBool(String key, bool value) { 60 | if (_beforeCheck()) return null; 61 | return _spf.setBool(key, value); 62 | } 63 | 64 | static int getInt(String key) { 65 | if (_beforeCheck()) return null; 66 | return _spf.getInt(key); 67 | } 68 | 69 | static Future putInt(String key, int value) { 70 | if (_beforeCheck()) return null; 71 | return _spf.setInt(key, value); 72 | } 73 | 74 | static double getDouble(String key) { 75 | if (_beforeCheck()) return null; 76 | return _spf.getDouble(key); 77 | } 78 | 79 | static Future putDouble(String key, double value) { 80 | if (_beforeCheck()) return null; 81 | return _spf.setDouble(key, value); 82 | } 83 | 84 | static List getStringList(String key) { 85 | return _spf.getStringList(key); 86 | } 87 | 88 | static Future putStringList(String key, List value) { 89 | if (_beforeCheck()) return null; 90 | return _spf.setStringList(key, value); 91 | } 92 | 93 | static dynamic getDynamic(String key) { 94 | if (_beforeCheck()) return null; 95 | return _spf.get(key); 96 | } 97 | 98 | /// 保存主题颜色,颜色值在 ThemeModel 中 99 | static Future saveThemeColorIndex(int value) { 100 | return _spf.setInt('key_theme_color', value); 101 | } 102 | /// 获取主题颜色,颜色值在 ThemeModel 中 103 | static int getThemeColorIndex() { 104 | if (hasKey('key_theme_color')) { 105 | return _spf.getInt('key_theme_color'); 106 | } 107 | return 0; 108 | } 109 | 110 | static Future remove(String key) { 111 | if (_beforeCheck()) return null; 112 | return _spf.remove(key); 113 | } 114 | 115 | static Future clear() { 116 | if (_beforeCheck()) return null; 117 | return _spf.clear(); 118 | } 119 | } -------------------------------------------------------------------------------- /lib/utils/sql.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:sqflite/sqflite.dart'; 4 | 5 | import './provider.dart'; 6 | 7 | class BaseModel{ 8 | Database db; 9 | final String table = ''; 10 | var query; 11 | BaseModel(this.db){ 12 | query = db.query; 13 | } 14 | } 15 | 16 | class Sql extends BaseModel { 17 | final String tableName; 18 | Sql.setTable(String name) 19 | : tableName = name, 20 | super(DBProvider.db); 21 | 22 | // sdf 23 | Future get() async{ 24 | return await this.query(tableName); 25 | } 26 | String getTableName () { 27 | return tableName; 28 | } 29 | 30 | /// 删除数据 31 | Future delete(String value,String key) async{ 32 | return await this.db.delete(tableName,where:'$key = ?',whereArgs:[value]); 33 | } 34 | /// 更新数据 35 | Future> update(Map json) async { 36 | var id = await this.db.update(tableName, json); 37 | json['id'] = id; 38 | return json; 39 | } 40 | 41 | /// 获取数据 42 | Future getByCondition({Map conditions}) async { 43 | if (conditions == null || conditions.isEmpty) { 44 | return this.get(); 45 | } 46 | String stringConditions = ''; 47 | 48 | int index = 0; 49 | conditions.forEach((key, value) { 50 | if (value == null) { 51 | return ; 52 | } 53 | if (value.runtimeType == String) { 54 | stringConditions = '$stringConditions $key = "$value"'; 55 | } 56 | if (value.runtimeType == int) { 57 | stringConditions = '$stringConditions $key = $value'; 58 | } 59 | 60 | if (index >= 0 && index < conditions.length -1) { 61 | stringConditions = '$stringConditions and'; 62 | } 63 | index++; 64 | }); 65 | // print("this is string condition for sql > $stringConditions"); 66 | return await this.query(tableName, where: stringConditions); 67 | } 68 | /// 插入数据 69 | Future> insert(Map json) async { 70 | var id = await this.db.insert(tableName, json); 71 | json['id'] = id; 72 | return json; 73 | } 74 | /// 75 | /// 搜索 76 | /// @param Object condition 77 | /// @mods [And, Or] default is Or 78 | /// search({'name': "hanxu', 'id': 1}; 79 | /// 80 | Future search({Map conditions, String mods = 'Or'}) async { 81 | if (conditions == null || conditions.isEmpty) { 82 | return this.get(); 83 | } 84 | String stringConditions = ''; 85 | int index = 0; 86 | conditions.forEach((key, value) { 87 | if (value == null) { 88 | return ; 89 | } 90 | 91 | if (value.runtimeType == String) { 92 | stringConditions = '$stringConditions $key like "%$value%"'; 93 | } 94 | if (value.runtimeType == int) { 95 | stringConditions = '$stringConditions $key = "%$value%"'; 96 | } 97 | 98 | if (index >= 0 && index < conditions.length -1) { 99 | stringConditions = '$stringConditions $mods'; 100 | } 101 | index++; 102 | }); 103 | 104 | return await this.query(tableName, where: stringConditions); 105 | } 106 | } -------------------------------------------------------------------------------- /lib/utils/style.dart: -------------------------------------------------------------------------------- 1 | /// 样式配置文件 相当于 常量类 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | //颜色配置 6 | class AppColor{ 7 | static const int white = 0xFFFFFFFF; 8 | static const int mainTextColor = 0xFF121917; 9 | static const int subTextColor = 0xff959595; 10 | } 11 | 12 | //文本设置 13 | class AppText{ 14 | static const middleSize = 16.0; 15 | 16 | static const middleText = TextStyle( 17 | color: Color(AppColor.mainTextColor), 18 | fontSize: middleSize, 19 | ); 20 | 21 | static const middleSubText = TextStyle( 22 | color: Color(AppColor.subTextColor), 23 | fontSize: middleSize, 24 | ); 25 | } 26 | 27 | /// CateCard 颜色设置 28 | class CateCardColor { 29 | static const int fontColor = 0xFF607173; 30 | static const int iconColor = 0xFF607173; 31 | static const int borderColor = 0xFFEFEFEF; 32 | 33 | } 34 | -------------------------------------------------------------------------------- /lib/utils/toast.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | import 'package:fluttertoast/fluttertoast.dart'; 4 | 5 | class ToastUtil { 6 | 7 | static showBasicToast(String msg){ 8 | Fluttertoast.showToast( 9 | msg: msg, 10 | toastLength: Toast.LENGTH_SHORT, 11 | gravity: ToastGravity.BOTTOM, 12 | timeInSecForIos: 1, 13 | backgroundColor: Colors.red, 14 | textColor: Colors.white, 15 | fontSize: 16.0 16 | ); 17 | } 18 | 19 | static showGravityToast(String msg,ToastGravity gravity){ 20 | Fluttertoast.showToast( 21 | msg: msg, 22 | toastLength: Toast.LENGTH_SHORT, 23 | gravity: gravity, 24 | timeInSecForIos: 1, 25 | backgroundColor: Colors.red, 26 | textColor: Colors.white, 27 | fontSize: 16.0 28 | ); 29 | } 30 | 31 | /// cancel all the toasts call 32 | static cancelAllToast(){ 33 | Fluttertoast.cancel(); 34 | } 35 | } -------------------------------------------------------------------------------- /lib/utils/update_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class UpdateDialog extends StatefulWidget { 4 | final key; 5 | final version; 6 | final feature; 7 | final Function onClickWhenDownload; 8 | final Function onClickWhenNotDownload; 9 | 10 | UpdateDialog({ 11 | this.key, 12 | this.version, 13 | this.feature, 14 | this.onClickWhenDownload, 15 | this.onClickWhenNotDownload, 16 | }); 17 | 18 | @override 19 | State createState() => new UpdateDialogState(); 20 | } 21 | 22 | class UpdateDialogState extends State { 23 | var _downloadProgress = 0.0; 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | var _textStyle = 28 | new TextStyle(color: Theme.of(context).textTheme.body1.color); 29 | 30 | return new AlertDialog( 31 | title: new Text( 32 | "有新的更新", 33 | style: _textStyle, 34 | ), 35 | content: _downloadProgress == 0.0 36 | ? new Text( 37 | "版本:${widget.version}\n更新:${widget.feature}", 38 | style: _textStyle, 39 | ) 40 | : new LinearProgressIndicator( 41 | value: _downloadProgress, 42 | ), 43 | actions: [ 44 | new FlatButton( 45 | child: new Text( 46 | '更新', 47 | style: _textStyle, 48 | ), 49 | onPressed: () { 50 | if (_downloadProgress != 0.0) { 51 | widget.onClickWhenDownload("正在更新中"); 52 | return; 53 | } 54 | widget.onClickWhenNotDownload(); 55 | // Navigator.of(context).pop(); 56 | }, 57 | ), 58 | new FlatButton( 59 | child: new Text('取消'), 60 | onPressed: () { 61 | Navigator.of(context).pop(); 62 | }, 63 | ), 64 | ], 65 | ); 66 | } 67 | 68 | set progress(_progress) { 69 | setState(() { 70 | _downloadProgress = _progress; 71 | if (_downloadProgress == 1) { 72 | Navigator.of(context).pop(); 73 | _downloadProgress = 0.0; 74 | } 75 | }); 76 | } 77 | } -------------------------------------------------------------------------------- /lib/views/about_page/page_reveal.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | class PageReveal extends StatelessWidget { 6 | 7 | final double revealPercent; 8 | final Widget child; 9 | 10 | PageReveal({ 11 | this.revealPercent, 12 | this.child 13 | }); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return ClipOval( 18 | clipper: new CircleRevealClipper(revealPercent), 19 | child: child, 20 | ); 21 | } 22 | } 23 | 24 | class CircleRevealClipper extends CustomClipper{ 25 | 26 | // 显示的百分比 27 | final double revealPercent; 28 | 29 | 30 | CircleRevealClipper( 31 | this.revealPercent 32 | ); 33 | 34 | @override 35 | Rect getClip(Size size) { 36 | 37 | final epicenter = new Offset(size.width / 2, size.height * 0.9); 38 | 39 | double theta = atan(epicenter.dy / epicenter.dx); 40 | final distanceToCorner = epicenter.dy / sin(theta); 41 | 42 | final radius = distanceToCorner * revealPercent; 43 | final diameter = 2 * radius; 44 | 45 | return new Rect.fromLTWH(epicenter.dx - radius, epicenter.dy - radius, diameter, diameter); 46 | } 47 | 48 | @override 49 | bool shouldReclip(CustomClipper oldClipper) { 50 | return true; 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /lib/views/about_page/pager_indicator.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_wanandroid/views/about_page/pages.dart'; 5 | 6 | 7 | class PagerIndicator extends StatelessWidget { 8 | 9 | final PagerIndicatorViewModel viewModel; 10 | 11 | PagerIndicator({ 12 | this.viewModel, 13 | }); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | 18 | List bubbles = []; 19 | for(var i = 0; i < viewModel.pages.length; ++i ){ 20 | final page = viewModel.pages[i]; 21 | 22 | var percentActive; 23 | 24 | if(i == viewModel.activeIndex){ 25 | percentActive = 1.0 - viewModel.slidePercent; 26 | } else if (i == viewModel.activeIndex - 1 && viewModel.slideDirection == SlideDirection.leftToRight){ 27 | percentActive = viewModel.slidePercent; 28 | } else if (i == viewModel.activeIndex + 1 && viewModel.slideDirection == SlideDirection.rightToLeft){ 29 | percentActive = viewModel.slidePercent; 30 | }else { 31 | percentActive = 0.0; 32 | } 33 | 34 | // 是否显示圆圈 35 | // i > viewModel.activeIndex 从右向左滑动 返回 true 36 | // i == viewModel.activeIndex && viewModel.slideDirection == SlideDirection.leftToRight) 从左向右滑动 返回 true 37 | bool isHollow = i > viewModel.activeIndex || (i == viewModel.activeIndex && viewModel.slideDirection == SlideDirection.leftToRight); 38 | 39 | 40 | 41 | bubbles.add( 42 | new PageBubble( 43 | viewModel: new PageBubbleViewModel( 44 | page.iconAssetPath, 45 | page.color, 46 | isHollow, 47 | percentActive, 48 | ), 49 | ), 50 | ); 51 | } 52 | 53 | final bubbleWidth = 55.0 ; 54 | final baseTranslation = ((viewModel.pages.length * bubbleWidth) / 2) - (bubbleWidth / 2) ; 55 | var translation = baseTranslation - (viewModel.activeIndex * bubbleWidth); 56 | 57 | if (viewModel.slideDirection == SlideDirection.leftToRight){ 58 | translation = bubbleWidth * viewModel.slidePercent + translation; 59 | }else if (viewModel.slideDirection == SlideDirection.rightToLeft){ 60 | translation = bubbleWidth * viewModel.slidePercent - translation; 61 | } 62 | 63 | return new Column( 64 | children: [ 65 | new Expanded(child: new Container()), 66 | new Transform( 67 | transform: new Matrix4.translationValues(0, 0.0, 0.0), 68 | child: new Row( 69 | mainAxisAlignment: MainAxisAlignment.center, 70 | children: bubbles, 71 | ), 72 | ), 73 | ], 74 | ); 75 | } 76 | } 77 | 78 | /// 枚举方向 79 | enum SlideDirection{ 80 | leftToRight, 81 | rightToLeft, 82 | none, 83 | } 84 | 85 | 86 | class PagerIndicatorViewModel{ 87 | final List pages; 88 | final int activeIndex; 89 | final SlideDirection slideDirection; 90 | final double slidePercent; 91 | 92 | PagerIndicatorViewModel( 93 | this.pages, 94 | this.activeIndex, 95 | this.slideDirection, 96 | this.slidePercent 97 | ); 98 | } 99 | 100 | class PageBubble extends StatelessWidget { 101 | 102 | final PageBubbleViewModel viewModel; 103 | 104 | 105 | PageBubble({ 106 | this.viewModel 107 | }); 108 | 109 | @override 110 | Widget build(BuildContext context) { 111 | return new Container( 112 | width: 55.0, 113 | height: 65.0, 114 | child: new Center( 115 | child: new Container( 116 | width: lerpDouble(20.0,45.0,viewModel.activePercent), 117 | height: lerpDouble(20.0,45.0,viewModel.activePercent), 118 | decoration: new BoxDecoration( 119 | shape: BoxShape.circle, 120 | color: viewModel.isHollow 121 | ? const Color(0x88FFFFFF).withAlpha(0x88 * viewModel.activePercent.round()) 122 | : const Color(0x88FFFFFF), 123 | border: new Border.all( 124 | color: viewModel.isHollow 125 | ? const Color(0x88FFFFFF).withAlpha((0x88 * (1.0 - viewModel.activePercent)).round()) 126 | : Colors.transparent, 127 | width: 3.0, 128 | ), 129 | ), 130 | child: new Opacity( 131 | opacity: viewModel.activePercent, 132 | child: Image.asset( 133 | viewModel.iconAssetPath, 134 | color: viewModel.color, 135 | ), 136 | ), 137 | ), 138 | ), 139 | ); 140 | } 141 | } 142 | 143 | 144 | class PageBubbleViewModel { 145 | final String iconAssetPath; 146 | final Color color; 147 | final bool isHollow; 148 | final double activePercent; 149 | 150 | PageBubbleViewModel ( 151 | this.iconAssetPath, 152 | this.color, 153 | this.isHollow, 154 | this.activePercent, 155 | ); 156 | 157 | 158 | } 159 | 160 | -------------------------------------------------------------------------------- /lib/views/cat_page/cat_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_wanandroid/api/common_service.dart'; 4 | import 'package:flutter_wanandroid/components/cate_card.dart'; 5 | import 'package:flutter_wanandroid/components/search_input.dart'; 6 | import 'package:flutter_wanandroid/model/cat.dart'; 7 | import 'package:flutter_wanandroid/model/navi_bean.dart'; 8 | import 'package:flutter_wanandroid/views/search_page/search_page.dart'; 9 | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; 10 | 11 | /// 猫耳布局 12 | /// 展示 体系、项目、导航 三大分类 13 | /// 作者:龙衣 14 | 15 | class CatPage extends StatefulWidget { 16 | @override 17 | State createState() { 18 | return new CatPageState(); 19 | } 20 | } 21 | 22 | class CatPageState extends State with AutomaticKeepAliveClientMixin { 23 | TextEditingController controller; 24 | String active = 'test'; 25 | String data = '无'; 26 | 27 | List categorieTrees = []; 28 | List categorieProjects = []; 29 | List categorieNavs = []; 30 | 31 | @override 32 | bool get wantKeepAlive => true; 33 | 34 | @override 35 | void initState() { 36 | super.initState(); 37 | _getCatPageData(); 38 | } 39 | 40 | void _getCatPageData() { 41 | if (!mounted) { 42 | return; 43 | } 44 | /// 获取体系数据 45 | CommonService().getSystemTree((CatModel catModel) { 46 | if(mounted){ 47 | categorieTrees.addAll(catModel.data); 48 | setState(() { 49 | categorieTrees = categorieTrees; 50 | }); 51 | } 52 | 53 | }); 54 | /// 获取导航数据 55 | CommonService().getNaviList((NaviBeanModel naviBean) { 56 | if(mounted){ 57 | categorieNavs.addAll(naviBean.datas); 58 | setState(() { 59 | categorieNavs = categorieNavs; 60 | }); 61 | } 62 | 63 | }); 64 | /// 获取项目数据 65 | CommonService().getProjectTree((CatModel catModel) { 66 | if(mounted){ 67 | categorieProjects.addAll(catModel.data); 68 | setState(() { 69 | categorieProjects = categorieProjects; 70 | }); 71 | } 72 | 73 | }); 74 | } 75 | 76 | /// 构建网格布局 77 | Widget buildGrid() { 78 | // 存放最后的widget 79 | List tiles = []; 80 | tiles.add(new CateCard(category: "体系", categorieLists: categorieTrees,naviLists: categorieNavs)); 81 | tiles.add(new CateCard(category: "导航", categorieLists: categorieTrees,naviLists: categorieNavs)); 82 | tiles.add(new CateCard(category: "项目", categorieLists: categorieProjects,naviLists:categorieNavs )); 83 | return new ListView( 84 | children: tiles, 85 | ); 86 | } 87 | 88 | // TODO 使用 WebView 测试 89 | // @override 90 | // Widget build(BuildContext context) { 91 | // super.build(context); 92 | // return Scaffold( 93 | // appBar: new AppBar(title: SearchPage(),), 94 | // body: Container( 95 | // color: Theme.of(context).backgroundColor, 96 | // child: WebviewScaffold( 97 | // url:'https://www.wanandroid.com/navi', 98 | // withZoom: false, 99 | // withLocalStorage: true, 100 | // withJavascript: true, 101 | // ), 102 | // ), 103 | // ); 104 | // } 105 | 106 | @override 107 | Widget build(BuildContext context) { 108 | super.build(context); 109 | return Scaffold( 110 | appBar: new AppBar(title: SearchPage(),), 111 | body: Container( 112 | color: Theme.of(context).backgroundColor, 113 | child: this.buildGrid(), 114 | ), 115 | ); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /lib/views/cat_page/cat_sub_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | 4 | import 'package:flutter/cupertino.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_wanandroid/components/cate_card.dart'; 7 | import 'package:flutter_wanandroid/model/cat.dart'; 8 | 9 | /// 猫耳二级布局 10 | /// 作者:龙衣 11 | 12 | class CatSubPage extends StatefulWidget { 13 | final List cats; 14 | final String name; 15 | 16 | CatSubPage({@required this.name, this.cats}); 17 | 18 | @override 19 | _CatSubPageState createState() => _CatSubPageState(); 20 | } 21 | 22 | class _CatSubPageState extends State 23 | with AutomaticKeepAliveClientMixin { 24 | TextEditingController controller; 25 | String active = 'test'; 26 | String data = '无'; 27 | StreamSubscription subscription; 28 | 29 | @override 30 | bool get wantKeepAlive => true; 31 | 32 | 33 | @override 34 | initState() { 35 | super.initState(); 36 | } 37 | 38 | 39 | /// 构建网格布局 40 | Widget buildGrid() { 41 | // 存放最后的widget 42 | print("list----构建网格布局--->:" + widget.cats.toString()); 43 | 44 | List tiles = []; 45 | tiles.add(new CateCard(category: widget.name, categorieLists: widget.cats)); 46 | return new ListView( 47 | children: tiles, 48 | ); 49 | } 50 | 51 | @override 52 | Widget build(BuildContext context) { 53 | return Scaffold( 54 | appBar: AppBar( 55 | title: Text(widget.name), 56 | ), 57 | body: Container( 58 | color: Theme.of(context).backgroundColor, 59 | child: this.buildGrid(), 60 | )); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/views/coin_rank_page/coin_rank_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_easyrefresh/easy_refresh.dart'; 4 | import 'package:flutter_wanandroid/api/Api.dart'; 5 | import 'package:flutter_wanandroid/widgets/state/load_state.dart'; 6 | import 'package:flutter_wanandroid/api/http.dart'; 7 | import 'package:flutter_wanandroid/model/coin.dart'; 8 | import 'package:flutter_wanandroid/routers/navigation_service.dart'; 9 | import 'package:flutter_wanandroid/widgets/loading/dialog_manager.dart'; 10 | 11 | /// 排行榜页面 12 | class CoinRankPage extends StatefulWidget { 13 | @override 14 | CoinRankPageState createState() => new CoinRankPageState(); 15 | } 16 | 17 | class CoinRankPageState extends State { 18 | EasyRefreshController _controller = EasyRefreshController(); 19 | 20 | // 页码 21 | int _page = 1; 22 | 23 | // 控制结束 24 | bool _enableControlFinish = false; 25 | 26 | List _coinList = []; 27 | 28 | @override 29 | void initState() { 30 | super.initState(); 31 | _getCoinList(_page); 32 | } 33 | 34 | Future _getCoinList(int page) async { 35 | /// 获取排行榜的数据 36 | Http.getData(Api.COIN_RANK + "$page/json", success: (data) { 37 | var coin = Coin.fromJson(data); 38 | _coinList.addAll(coin.data.datas); 39 | if(_coinList.length <= 0){ 40 | _layoutState = LoadState.State_Empty; 41 | }else{ 42 | _layoutState = LoadState.State_Success; 43 | } 44 | setState(() { 45 | _layoutState = _layoutState; 46 | _coinList = _coinList; 47 | }); 48 | }, error: (e) { 49 | print("接口出错:${e.message}"); 50 | }); 51 | } 52 | 53 | Widget _renderItem(context, index) { 54 | return Container( 55 | padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 5.0), 56 | margin: const EdgeInsets.only(bottom: 7.0), 57 | decoration: BoxDecoration( 58 | color: Colors.white, 59 | boxShadow: [ 60 | new BoxShadow( 61 | color: const Color(0xFFd0d0d0), 62 | blurRadius: 1.0, 63 | spreadRadius: 2.0, 64 | offset: Offset(3.0, 2.0), 65 | ), 66 | ], 67 | ), 68 | child: ListTile( 69 | leading: CircleAvatar( 70 | backgroundColor: Colors.red, 71 | child: Text( 72 | '${_coinList[index].rank}', 73 | style: TextStyle(color: Colors.white), 74 | ), 75 | ), 76 | title: Text( 77 | '🔥 ${_coinList[index].coinCount}', 78 | overflow: TextOverflow.ellipsis, 79 | style: TextStyle(fontSize: 17.0), 80 | ), 81 | subtitle: Text('🎅 ${_coinList[index].username}'), 82 | ), 83 | ); 84 | } 85 | 86 | //页面加载状态,默认为加载中 87 | LoadState _layoutState = LoadState.State_Loading; 88 | 89 | void _retry() { 90 | setState(() { 91 | _layoutState = LoadState.State_Loading; 92 | }); 93 | _getCoinList(1); 94 | } 95 | 96 | Future _refreshData() async { 97 | _coinList.clear(); 98 | _page = 1; 99 | await _getCoinList(_page); 100 | if (!_enableControlFinish) { 101 | _controller.resetLoadState(); 102 | _controller.finishRefresh(); 103 | } 104 | } 105 | 106 | Future _loadMoreData() async { 107 | _page++; 108 | await _getCoinList(_page); 109 | if (!_enableControlFinish) { 110 | _controller.finishLoad(); 111 | } 112 | } 113 | 114 | @override 115 | Widget build(BuildContext context) { 116 | return Scaffold( 117 | appBar: AppBar( 118 | title: Text('积分排行榜'), 119 | ), 120 | body: LoadStateLayout( 121 | state: _layoutState, 122 | emptyRetry: () { 123 | _retry(); 124 | }, 125 | errorRetry: () { 126 | _retry(); 127 | }, //错误按钮点击过后进行重新加载 128 | successWidget: Center( 129 | child: EasyRefresh.custom( 130 | enableControlFinishRefresh: true, 131 | enableControlFinishLoad: true, 132 | controller: _controller, 133 | onRefresh: () async { 134 | await _refreshData(); 135 | }, 136 | onLoad: () async { 137 | await _loadMoreData(); 138 | }, 139 | slivers: [ 140 | SliverList( 141 | delegate: SliverChildBuilderDelegate( 142 | (context, index) { 143 | return _renderItem(context, index); 144 | }, 145 | childCount: _coinList.length, 146 | ), 147 | ), 148 | ], 149 | ), 150 | ), 151 | )); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /lib/views/collection_page/collection_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:event_bus/event_bus.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_wanandroid/event/event_bus.dart'; 5 | import 'package:flutter_wanandroid/event/event_model.dart'; 6 | import 'package:flutter_wanandroid/model/collect.dart'; 7 | import 'package:flutter_wanandroid/routers/application.dart'; 8 | import 'package:flutter_wanandroid/routers/router_path.dart'; 9 | import 'package:flutter_wanandroid/views/search_page/search_page.dart'; 10 | 11 | class CollectionPage extends StatefulWidget{ 12 | @override 13 | CollectionPageState createState() => new CollectionPageState(); 14 | } 15 | 16 | class CollectionPageState extends State with AutomaticKeepAliveClientMixin{ 17 | @override 18 | bool get wantKeepAlive => true; 19 | 20 | CollectionPageState() { 21 | final eventBus = new EventBus(); 22 | ApplicationEvent.event = eventBus; 23 | } 24 | CollectionControlModel _collectionControl = new CollectionControlModel(); 25 | List _collectionList = []; 26 | ScrollController _scrollController = new ScrollController(); 27 | var _icons; 28 | 29 | @override 30 | void initState() { 31 | super.initState(); 32 | _getList(); 33 | ApplicationEvent.event.on().listen((event) { 34 | _getList(); 35 | }); 36 | } 37 | 38 | @override 39 | void dispose() { 40 | _scrollController.dispose(); 41 | super.dispose(); 42 | } 43 | 44 | void _getList() { 45 | _collectionList.clear(); 46 | _collectionControl.getAllCollection().then((resultList) { 47 | resultList.forEach((item) { 48 | _collectionList.add(item); 49 | }); 50 | if (this.mounted) { 51 | setState(() { 52 | _collectionList = _collectionList; 53 | }); 54 | } 55 | }); 56 | } 57 | 58 | Widget _renderList(context, index) { 59 | if (index == 0) { 60 | return Container( 61 | height: 40.0, 62 | padding: const EdgeInsets.only(left: 10.0), 63 | child: Row( 64 | children: [ 65 | Icon( 66 | Icons.warning, 67 | size: 22.0, 68 | ), 69 | SizedBox( 70 | width: 5.0, 71 | ), 72 | Text('模拟器重新运行会丢失收藏'), 73 | ], 74 | ), 75 | ); 76 | } 77 | _icons = Icons.language; 78 | 79 | return Container( 80 | padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 5.0), 81 | margin: const EdgeInsets.only(bottom: 7.0), 82 | decoration: BoxDecoration( 83 | color: Colors.white, 84 | boxShadow: [ 85 | new BoxShadow( 86 | color: const Color(0xFFd0d0d0), 87 | blurRadius: 1.0, 88 | spreadRadius: 2.0, 89 | offset: Offset(3.0, 2.0), 90 | ), 91 | ], 92 | ), 93 | child: ListTile( 94 | leading: Icon( 95 | _icons, 96 | size: 30.0, 97 | color: Theme.of(context).primaryColor, 98 | ), 99 | title: Text( 100 | _collectionList[index - 1].title, 101 | overflow: TextOverflow.ellipsis, 102 | style: TextStyle(fontSize: 17.0), 103 | ), 104 | trailing: 105 | Icon(Icons.keyboard_arrow_right, color: Colors.grey, size: 30.0), 106 | onTap: () { 107 | // 需要转义 Uri.encodeComponent 108 | var collection = _collectionList[index - 1]; 109 | Application.router.navigateTo(context, 110 | '${RouterPath.webViewPage}?id=${Uri.encodeComponent(collection.id)}&title=${Uri.encodeComponent(collection.title)}&link=${Uri.encodeComponent(collection.link)}'); 111 | 112 | }, 113 | ), 114 | ); 115 | } 116 | 117 | @override 118 | Widget build(BuildContext context) { 119 | super.build(context); 120 | if (_collectionList.length == 0) { 121 | return Scaffold( 122 | appBar: new AppBar(title: SearchPage(),), 123 | body: ListView( 124 | children: [ 125 | Column( 126 | children: [ 127 | Image.asset( 128 | 'assets/images/nothing.png', 129 | fit: BoxFit.contain, 130 | width: MediaQuery.of(context).size.width / 2, 131 | ), 132 | Text('暂无收藏,赶紧去收藏一个吧!'), 133 | ], 134 | ), 135 | ], 136 | ), 137 | ); 138 | } 139 | return Scaffold( 140 | appBar: new AppBar(title: SearchPage(),), 141 | body:ListView.builder( 142 | itemBuilder: _renderList, 143 | itemCount: _collectionList.length + 1, 144 | controller: _scrollController, 145 | ), 146 | ); 147 | } 148 | 149 | } -------------------------------------------------------------------------------- /lib/views/home_page/home_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_wanandroid/api/Api.dart'; 6 | import 'package:flutter_wanandroid/api/http.dart'; 7 | import 'package:flutter_wanandroid/components/disclaimer_msg.dart'; 8 | import 'package:flutter_wanandroid/components/list_view_item.dart'; 9 | import 'package:flutter_wanandroid/components/list_refresh.dart' as listComp; 10 | import 'package:flutter_wanandroid/components/pagination.dart'; 11 | import 'package:flutter_wanandroid/model/splash.dart'; 12 | import 'package:flutter_wanandroid/utils/shared_preferences.dart'; 13 | import 'package:flutter_wanandroid/views/search_page/search_page.dart'; 14 | import 'package:shared_preferences/shared_preferences.dart'; 15 | 16 | import '../../constant/shared_preferences_keys.dart'; 17 | 18 | 19 | 20 | 21 | class HomePage extends StatefulWidget { 22 | @override 23 | FirstPageState createState() => new FirstPageState(); 24 | } 25 | 26 | class FirstPageState extends State with AutomaticKeepAliveClientMixin{ 27 | Future _prefs = SharedPreferences.getInstance(); 28 | Future _unKnow; 29 | GlobalKey key; 30 | 31 | @override 32 | bool get wantKeepAlive => true; 33 | 34 | @override 35 | void initState() { 36 | super.initState(); 37 | 38 | if (key == null) { 39 | key = GlobalKey(); 40 | //获取sharePre 41 | _unKnow = _prefs.then((SharedPreferences prefs) { 42 | return (prefs.getBool('disclaimer::Boolean') ?? false); 43 | }); 44 | 45 | /// 判断是否需要弹出免责声明,已经勾选过不在显示,就不会主动弹 46 | _unKnow.then((bool value) { 47 | new Future.delayed(const Duration(seconds: 1),(){ 48 | if (!value) { 49 | key.currentState.showAlertDialog(context); 50 | } 51 | }); 52 | }); 53 | } 54 | _getImage(); 55 | } 56 | 57 | Future _getImage() async { 58 | /// 当天是否已经获取过 59 | print(_getDateTime()); 60 | print(SPUtils.getString(SharedPreferencesKeys.splash_date)); 61 | if(SPUtils.getString(SharedPreferencesKeys.splash_date) != _getDateTime()){ 62 | Http.getData(Api.DAY_IMAGE, success: (data) { 63 | var splash = Splash.fromJson(data); 64 | final enddate = splash.images[0].enddate; 65 | final imageUrl = "https://cn.bing.com${splash.images[0].url}"; 66 | SPUtils.putString(SharedPreferencesKeys.splash_date, enddate); 67 | SPUtils.putString(SharedPreferencesKeys.splash_image, imageUrl); 68 | }, error: (e) { 69 | print("DAY_IMAGE 接口出错:${e.message}"); 70 | }); 71 | } 72 | } 73 | /// 获取当前时间 格式:20200531 74 | String _getDateTime(){ 75 | return DateTime.now().toString().replaceAll("-", "").substring(0,8); 76 | } 77 | 78 | 79 | /// 列表中的卡片item 80 | Widget makeCard(index,item){ 81 | 82 | var mId = item.id; 83 | var mTitle = '${item.title}'; 84 | var mShareUserName = '${'👲'}: ${item.shareUser} '; 85 | 86 | if(item.shareUser == ""){ 87 | mShareUserName = '${'👲'}: ${item.author} '; 88 | } 89 | var mLikeUrl = '${item.link}'; 90 | var mNiceDate = '${'🔔'}: ${item.niceDate}'; 91 | return new ListViewItem(itemId: mId, itemTitle: mTitle, itemUrl:mLikeUrl,itemShareUser: mShareUserName, itemNiceDate: mNiceDate); 92 | } 93 | 94 | /// banner 95 | headerView(){ 96 | return 97 | Column( 98 | children: [ 99 | Stack( 100 | children: [ 101 | // banner 102 | Pagination(), 103 | Positioned(//方法二 104 | top: 10.0, 105 | left: 0.0, 106 | child: DisclaimerMsg(key:key,pWidget:this) 107 | ), 108 | ]), 109 | SizedBox(height: 1, child:Container(color: Theme.of(context).primaryColor)), 110 | SizedBox(height: 10), 111 | ], 112 | ); 113 | 114 | } 115 | 116 | @override 117 | Widget build(BuildContext context) { 118 | super.build(context); 119 | return Scaffold( 120 | appBar: new AppBar(title: SearchPage(),), 121 | body: new Column( 122 | children: [ 123 | SizedBox(height: 2, child:Container(color: Theme.of(context).primaryColor)), 124 | new Expanded( 125 | child: listComp.ListRefresh(makeCard,headerView) 126 | ) 127 | ] 128 | 129 | ), 130 | ); 131 | } 132 | } 133 | 134 | 135 | -------------------------------------------------------------------------------- /lib/views/page_not_found.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// 页面路由出错显示的页面 4 | class PageNotFound extends StatelessWidget { 5 | 6 | Widget build(BuildContext context) { 7 | return Scaffold( 8 | appBar: AppBar( 9 | title: Text("page not found"), 10 | ), 11 | body: Container( 12 | child: Text("page not found") 13 | ) 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/views/photo_detail_page/photo_detail_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:extended_image/extended_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_wanandroid/model/constant.dart'; 4 | import 'dart:math' as math; 5 | 6 | class PhotoDetailPage extends StatefulWidget { 7 | final String id; 8 | final String title; 9 | final String desc; 10 | final String projectLink; 11 | final String envelopePic; 12 | final String niceDate; 13 | final String author; 14 | // 主构造函数 15 | PhotoDetailPage(this.id,this.title, this.desc, this.projectLink, this.envelopePic, 16 | this.niceDate, this.author); 17 | 18 | @override 19 | _PhotoDetailPageState createState() => new _PhotoDetailPageState(); 20 | } 21 | 22 | class _PhotoDetailPageState extends State { 23 | @override 24 | Widget build(BuildContext context) { 25 | return Material( 26 | child: CustomScrollView( 27 | slivers: [ 28 | SliverAppBar( 29 | pinned: true, 30 | expandedHeight: 450, 31 | flexibleSpace: FlexibleSpaceBar( 32 | title: Text('${widget.title}',maxLines: 1,overflow: TextOverflow.ellipsis,style: TextStyle(fontSize: 14),), 33 | background: Hero( 34 | tag: '${Constant.heroPhotoDetail} ${widget.id}', 35 | child: ExtendedImage.network( 36 | widget.envelopePic, 37 | fit: BoxFit.cover, 38 | ), 39 | ), 40 | ), 41 | ), 42 | SliverPersistentHeader( 43 | pinned: false, 44 | floating: false, 45 | delegate: _SliverAppBarDelegate( 46 | minHeight: 60.0, 47 | maxHeight: 250.0, 48 | child: Container( 49 | color: Colors.blue, 50 | child: Center( 51 | child: Text('header',style: TextStyle(color: Colors.white),), 52 | ) 53 | ), 54 | ), 55 | ), 56 | SliverList( 57 | delegate: new SliverChildBuilderDelegate( 58 | (BuildContext context, int index) { 59 | //创建列表项 60 | return Column( 61 | children: [ 62 | SizedBox( 63 | height: 50, 64 | ), 65 | Text('${widget.desc}',style: TextStyle(height: 2),), 66 | SizedBox( 67 | height: 50, 68 | ), 69 | Text('${widget.niceDate}'), 70 | SizedBox( 71 | height: 50, 72 | ), 73 | Text('${widget.author}'), 74 | SizedBox( 75 | height: 50, 76 | ), 77 | ], 78 | ); 79 | }, childCount: 1 //50个列表项 80 | ), 81 | ) 82 | ], 83 | ), 84 | ); 85 | } 86 | } 87 | 88 | class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate{ 89 | _SliverAppBarDelegate({ 90 | @required this.minHeight, 91 | @required this.maxHeight, 92 | @required this.child, 93 | }); 94 | 95 | final double minHeight; 96 | final double maxHeight; 97 | final Widget child; 98 | 99 | @override 100 | double get minExtent => minHeight; 101 | 102 | @override 103 | double get maxExtent => math.max(maxHeight, minHeight); 104 | 105 | @override 106 | Widget build( 107 | BuildContext context, double shrinkOffset, bool overlapsContent) { 108 | return new SizedBox.expand(child: child); 109 | } 110 | 111 | @override 112 | bool shouldRebuild(_SliverAppBarDelegate oldDelegate) { 113 | return maxHeight != oldDelegate.maxHeight || 114 | minHeight != oldDelegate.minHeight || 115 | child != oldDelegate.child; 116 | } 117 | } -------------------------------------------------------------------------------- /lib/views/photo_page/item_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:extended_image/extended_image.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/painting.dart'; 5 | import 'package:flutter_html/flutter_html.dart'; 6 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 7 | import 'package:flutter_wanandroid/model/constant.dart'; 8 | import 'package:flutter_wanandroid/routers/application.dart'; 9 | import 'package:flutter_wanandroid/routers/router_path.dart'; 10 | 11 | /// 瀑布流item 12 | class TileCard extends StatelessWidget with WidgetsBindingObserver { 13 | final String id; 14 | final String envelopePic; 15 | final String title; 16 | final String desc; 17 | final String author; 18 | final String projectLink; 19 | final String link; 20 | final String niceDate; 21 | 22 | TileCard( 23 | { 24 | this.id, 25 | this.envelopePic, 26 | this.title, 27 | this.desc, 28 | this.author, 29 | this.projectLink, 30 | this.link, 31 | this.niceDate}); 32 | 33 | 34 | @override 35 | void didChangeAppLifecycleState(AppLifecycleState state) async { 36 | print("TileCard $state"); 37 | if (state == AppLifecycleState.resumed) { 38 | // do sth 39 | } 40 | } 41 | 42 | Widget _buildBottomLayout(BuildContext context,String msg){ 43 | return Container( 44 | child: Text( 45 | msg, 46 | overflow: TextOverflow.ellipsis, 47 | style: TextStyle( 48 | fontSize: ScreenUtil().setSp(30), 49 | ), 50 | ), 51 | ); 52 | } 53 | 54 | @override 55 | Widget build(BuildContext context) { 56 | return Card( 57 | child: InkWell( 58 | onTap: (){ 59 | // 跳转 WebView 60 | Application.router.navigateTo(context, '${RouterPath.webViewPage}?id=${Uri.encodeComponent(id)}&title=${Uri.encodeComponent(title)}&link=${Uri.encodeComponent(link)}'); 61 | // 跳转 Hero 实现的动画效果 62 | // Application.router.navigateTo(context, 63 | // '${Routes.photoDetailPage}?id=${Uri.encodeComponent(id)}' 64 | // '&title=${Uri.encodeComponent(title)}' 65 | // '&desc=${Uri.encodeComponent(desc)}' 66 | // '&projectLink=${Uri.encodeComponent(projectLink)}' 67 | // '&envelopePic=${Uri.encodeComponent(envelopePic)}' 68 | // '&niceDate=${Uri.encodeComponent(niceDate)}' 69 | // '&author=${Uri.encodeComponent(author)}'); 70 | }, 71 | child: Column( 72 | crossAxisAlignment: CrossAxisAlignment.start, 73 | mainAxisAlignment: MainAxisAlignment.start, 74 | children: [ 75 | // 图片 76 | Container( 77 | color: Colors.grey, 78 | child:Hero( 79 | tag: '${Constant.heroPhotoDetail} $id', 80 | child: ExtendedImage.network( 81 | envelopePic, 82 | fit: BoxFit.fitWidth, 83 | ), 84 | ) 85 | ), 86 | // 描述 87 | Container( 88 | margin: EdgeInsets.all(6.0), 89 | child: Html(data: title,defaultTextStyle: TextStyle(height: 1.50),) 90 | ), 91 | // 作者,时间 92 | Container( 93 | padding: EdgeInsets.only( 94 | left: ScreenUtil().setWidth(20), 95 | bottom: ScreenUtil().setWidth(20)), 96 | child: Row( 97 | mainAxisAlignment: MainAxisAlignment.start, 98 | children: [ 99 | // 作者 100 | _buildBottomLayout(context,'👲 作者:'+author), 101 | ], 102 | ) 103 | ) 104 | ], 105 | ), 106 | ) 107 | ); 108 | } 109 | } -------------------------------------------------------------------------------- /lib/views/photo_page/photo_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; 5 | import 'package:flutter_wanandroid/api/common_service.dart'; 6 | import 'package:flutter_wanandroid/components/search_input.dart'; 7 | import 'package:flutter_wanandroid/model/project_model.dart'; 8 | import 'package:flutter_wanandroid/routers/navigation_service.dart'; 9 | import 'package:flutter_wanandroid/views/photo_page/item_card.dart'; 10 | import 'package:flutter_wanandroid/views/search_page/search_page.dart'; 11 | import 'package:flutter_wanandroid/widgets/loading/dialog_manager.dart'; 12 | 13 | class PhotoPage extends StatefulWidget { 14 | @override 15 | _PhotoPageState createState() => new _PhotoPageState(); 16 | } 17 | 18 | class _PhotoPageState extends State with AutomaticKeepAliveClientMixin{ 19 | @override 20 | bool get wantKeepAlive => true; 21 | 22 | // 当前页码 23 | int _page = 1; 24 | // 每页请求的个数 25 | int _size = 10; 26 | // 完整项目的id 27 | int _projectId = 294; 28 | // 总页码 29 | int _pageTotal = 0; 30 | List items = new List(); 31 | var _scrollController = new ScrollController(initialScrollOffset: 0); 32 | 33 | 34 | @override 35 | void initState() { 36 | super.initState(); 37 | 38 | /// 首次拉取数据 39 | _getProjectData(true); 40 | 41 | _scrollController.addListener(() { 42 | var px = _scrollController.position.pixels; 43 | if (px == _scrollController.position.maxScrollExtent) { 44 | print("加载更多!"); 45 | _addMoreData(); 46 | } 47 | }); 48 | } 49 | 50 | @override 51 | void didChangeDependencies() { 52 | super.didChangeDependencies(); 53 | 54 | 55 | } 56 | 57 | /// 下拉刷新数据 58 | Future _refreshData() async { 59 | _page = 0; 60 | _getProjectData(false); 61 | } 62 | 63 | /// 上拉加载数据 64 | Future _addMoreData() async { 65 | _page++; 66 | _getProjectData(true); 67 | } 68 | 69 | /// 获取项目数据 70 | void _getProjectData(bool _isAdd) async { 71 | Future.delayed(Duration(milliseconds: 200)).then((e) { 72 | DialogManager.showBasicDialog(NavigationService.mContext, "正在加载中..."); 73 | }); 74 | /// 获取新的数据 75 | CommonService().getProjectList((ProjectModel _projectModel) { 76 | _pageTotal = _projectModel.data.total; 77 | if(mounted){ 78 | setState(() { 79 | items.addAll(_projectModel.data.datas); 80 | }); 81 | } 82 | /// 关闭弹窗 83 | Navigator.pop(NavigationService.mContext); 84 | }, _page, _projectId); 85 | } 86 | 87 | @override 88 | Widget build(BuildContext context) { 89 | super.build(context); 90 | return Scaffold( 91 | appBar: new AppBar(title: SearchPage(),), 92 | body: Column( 93 | children: [ 94 | Expanded( 95 | child: RefreshIndicator( 96 | onRefresh: _refreshData, 97 | child: StaggeredGridView.countBuilder( 98 | controller: _scrollController, 99 | padding: EdgeInsets.all(8), 100 | itemCount: items.length, 101 | crossAxisCount: 4, 102 | mainAxisSpacing: 10.0, 103 | crossAxisSpacing: 4.0, 104 | itemBuilder: (context, index) => TileCard( 105 | id: '${items[index].id}', 106 | envelopePic: '${items[index].envelopePic}', 107 | title: '${items[index].title}', 108 | desc: '${items[index].desc}', 109 | author: '${items[index].author}', 110 | projectLink: '${items[index].projectLink}', 111 | link: '${items[index].link}', 112 | niceDate: '${items[index].niceDate}'), 113 | staggeredTileBuilder: (index) => StaggeredTile.fit(2), 114 | ), 115 | ) 116 | ) 117 | ], 118 | ) 119 | ); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /lib/views/search_page/search_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | 4 | import 'package:dio/dio.dart'; 5 | import 'package:flutter/cupertino.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_wanandroid/api/Api.dart'; 8 | import 'package:flutter_wanandroid/api/common_service.dart'; 9 | import 'package:flutter_wanandroid/components/search_input.dart'; 10 | import 'package:flutter_wanandroid/model/article.dart'; 11 | import 'package:flutter_wanandroid/model/search_history.dart'; 12 | import 'package:flutter_wanandroid/api/dio_manager.dart'; 13 | import 'package:flutter_wanandroid/routers/application.dart'; 14 | import 'package:flutter_wanandroid/routers/router_path.dart'; 15 | import 'package:flutter_wanandroid/utils/shared_preferences.dart'; 16 | 17 | import '../../constant/shared_preferences_keys.dart'; 18 | 19 | class SearchPage extends StatefulWidget{ 20 | @override 21 | State createState() { 22 | return _SearchPageState(); 23 | } 24 | } 25 | 26 | class _SearchPageState extends State{ 27 | SearchHistoryList searchHistoryList; 28 | bool isSearch = false; 29 | 30 | @override 31 | void initState() { 32 | super.initState(); 33 | 34 | initSearchHistory(); 35 | 36 | } 37 | /// 初始化搜索历史列表 38 | initSearchHistory() async { 39 | String json = SPUtils.getString(SharedPreferencesKeys.searchHistory); 40 | print("initSearchHistory json $json"); 41 | searchHistoryList = SearchHistoryList.fromJSON(json); 42 | } 43 | 44 | /// 搜索列表中的 item 点击 45 | void onSearchTap(Article article, BuildContext context) { 46 | 47 | searchHistoryList.add(SearchHistory(name: article.title, targetRouter: article.link)); 48 | print("searchHistoryList ${searchHistoryList.toString()}"); 49 | print("点击搜索结果"); 50 | 51 | toWebPage(article); 52 | // Application.router.navigateTo(context, "$targetRouter"); 53 | } 54 | /// 点击跳转web页面 55 | void toWebPage(Article article){ 56 | Application.router.navigateTo(context, '${RouterPath.webViewPage}?id=${Uri.encodeComponent("0")}&title=${Uri.encodeComponent("详情")}&link=${Uri.encodeComponent(article.link)}'); 57 | } 58 | 59 | Future> postSearch(String name) async { 60 | List
items = new List(); 61 | Response response = await DioManager.singleton.dio.post(Api.SEARCH_LIST+"${0}/json?k=$name"); 62 | var articleModel = ArticleModel(response.data); 63 | items.addAll(articleModel.data.datas); 64 | return items; 65 | } 66 | 67 | 68 | @override 69 | Widget build(BuildContext context) { 70 | return new SearchInput((value) async{ 71 | if (value != '') { 72 | // TODO 发起网络请求,搜索结果 73 | print("----------发起网络请求,搜索结果------>>>>>>>"+value); 74 | List
articles = await postSearch(value); 75 | print("----------数据排版---articles--->>>>>>>"+articles.toString()); 76 | 77 | return articles.map((item) => new MaterialSearchResult( 78 | value: item.title, 79 | icon: Icons.link, 80 | author: item.author, 81 | onTap: () { 82 | // item 点击 83 | onSearchTap(item, context); 84 | }, 85 | )) 86 | .toList(); 87 | } else { 88 | return null; 89 | } 90 | }, (value){},(){}); 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /lib/views/splash_page/SplashPage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:extended_image/extended_image.dart'; 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_wanandroid/api/common_service.dart'; 6 | import 'package:flutter_wanandroid/model/user.dart'; 7 | import 'package:flutter_wanandroid/routers/router_path.dart'; 8 | import 'package:flutter_wanandroid/utils/shared_preferences.dart'; 9 | 10 | import '../../constant/shared_preferences_keys.dart'; 11 | 12 | /// 闪屏页,首页之前的广告页面 13 | class SplashPage extends StatefulWidget{ 14 | @override 15 | _SplashPageState createState() => _SplashPageState(); 16 | 17 | } 18 | 19 | class _SplashPageState extends State { 20 | // splash 默认图片 21 | var mImagesUrl = ''; 22 | Timer _timer; 23 | int _countdownTime = 5; 24 | 25 | @override 26 | void initState() { 27 | super.initState(); 28 | 29 | getSplashImage(); 30 | /// 开始倒计时 31 | startCountdownTimer(); 32 | 33 | } 34 | 35 | @override 36 | void dispose() { 37 | _timer.cancel(); 38 | _timer = null; 39 | super.dispose(); 40 | } 41 | 42 | /// 倒计时 43 | void startCountdownTimer() { 44 | const duration = const Duration(seconds: 1); 45 | var callback = (timer) => { 46 | setState(() { 47 | if (_countdownTime > 0) { 48 | _countdownTime --; 49 | } else { 50 | _timer.cancel(); 51 | goHomePage(); 52 | } 53 | }) 54 | }; 55 | 56 | _timer = Timer.periodic(duration, callback); 57 | } 58 | 59 | /// 获取 splash 图片 60 | void getSplashImage(){ 61 | /// 从缓存中获取,缓存中没有直接跳转首页 62 | var splashImage = SPUtils.getString(SharedPreferencesKeys.splash_image); 63 | if(splashImage != null){ 64 | setState(() { 65 | mImagesUrl = splashImage; 66 | print("图片mImagesUrl:$mImagesUrl"); 67 | }); 68 | }else{ 69 | goHomePage(); 70 | } 71 | } 72 | 73 | @override 74 | Widget build(BuildContext context) { 75 | return Stack( 76 | fit: StackFit.expand, 77 | children: [ 78 | Container( 79 | width: double.infinity, 80 | height: double.infinity, 81 | child: ExtendedImage.network( 82 | mImagesUrl, 83 | enableLoadState: false, 84 | fit: BoxFit.cover, 85 | ), 86 | ), 87 | Positioned( 88 | right: 38.0, 89 | bottom:50.0, 90 | child: RaisedButton( 91 | color: Colors.blue, // 按钮背景色 92 | highlightColor: Colors.blue[700],// 按钮高亮后的背景色 93 | colorBrightness: Brightness.dark,// 使用深色主题,保证按钮文字颜色为浅色 94 | splashColor: Colors.grey,// 点击时,水波动画中水波的颜色 95 | child: Text('跳过($_countdownTime s)'),// 文本 96 | shape: RoundedRectangleBorder( 97 | borderRadius: BorderRadius.circular(20.0)), //圆角矩形 98 | onPressed: () { 99 | _timer.cancel(); 100 | goHomePage(); 101 | }, 102 | ), 103 | ), 104 | ],); 105 | } 106 | 107 | 108 | //页面跳转 109 | void goHomePage() { 110 | print("判断跳转那个页面"); 111 | User().getUserInfo().then((value){ 112 | if(value != null && value.length > 0){ 113 | print("首页"); 114 | 115 | Navigator.pushReplacementNamed(context, RouterPath.root); 116 | }else{ 117 | print("登录页面"); 118 | 119 | Navigator.pushReplacementNamed(context, RouterPath.login); 120 | } 121 | }); 122 | 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /lib/views/web_page/webview_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_wanandroid/event/event_bus.dart'; 4 | import 'package:flutter_wanandroid/event/event_model.dart'; 5 | import 'package:flutter_wanandroid/model/collect.dart'; 6 | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; 7 | 8 | class WebViewPage extends StatefulWidget{ 9 | final int id; 10 | final String title; 11 | final String link; 12 | //重定向到主构造函数 13 | WebViewPage.alongXAxis(String title, String link) : this(0, title, link); 14 | // 主构造函数 15 | WebViewPage(this.id, this.title, this.link); 16 | _WebViewPageState createState() => _WebViewPageState(); 17 | 18 | } 19 | class _WebViewPageState extends State { 20 | 21 | bool _hasCollected = false; 22 | String _link = ''; 23 | var _collectionIcons; 24 | CollectionControlModel _collectionControl = new CollectionControlModel(); 25 | 26 | final GlobalKey _scaffoldKey = GlobalKey(); 27 | @override 28 | void initState() { 29 | super.initState(); 30 | _collectionControl 31 | .getRouterById(widget.id) 32 | .then((list) { 33 | list.forEach((item) { 34 | if (widget.title.trim() == item['title']) { 35 | _link = item['link']; 36 | } 37 | }); 38 | if (mounted) { 39 | setState(() { 40 | _hasCollected = list.length > 0; 41 | }); 42 | } 43 | }); 44 | } 45 | 46 | // 点击收藏按钮 47 | _getCollection() { 48 | if (_hasCollected) { 49 | // 删除操作 50 | _collectionControl 51 | .deleteById(widget.id) 52 | .then((result) { 53 | if (result > 0 && this.mounted) { 54 | setState(() { 55 | _hasCollected = false; 56 | }); 57 | print("已取消收藏"); 58 | 59 | /// 目前 WebView 插件还没有解决在 WebView 上显示弹窗这个问题 可查看 issue 60 | /// https://github.com/fluttercommunity/flutter_webview_plugin/issues/69 61 | // _scaffoldKey.currentState.showSnackBar(SnackBar(content: Text('已取消收藏'))); 62 | if (ApplicationEvent.event != null) { 63 | ApplicationEvent.event 64 | .fire(CollectionEvent(widget.title, _link, true)); 65 | } 66 | return; 67 | } 68 | print('删除错误'); 69 | }); 70 | } else { 71 | // 插入操作 72 | _collectionControl 73 | .insert(Collection( 74 | id: widget.id.toString(), 75 | title: widget.title, 76 | link: widget.link)) 77 | .then((result) { 78 | if (this.mounted) { 79 | setState(() { 80 | _hasCollected = true; 81 | }); 82 | print("收藏成功"); 83 | /// 目前 WebView 插件还没有解决在 WebView 上显示弹窗这个问题 可查看 issue 84 | /// https://github.com/fluttercommunity/flutter_webview_plugin/issues/69 85 | // _scaffoldKey.currentState.showSnackBar(SnackBar(content: Text('收藏成功'))); 86 | 87 | if (ApplicationEvent.event != null) { 88 | ApplicationEvent.event 89 | .fire(CollectionEvent(widget.title, _link, false)); 90 | } 91 | 92 | } 93 | }); 94 | } 95 | } 96 | 97 | @override 98 | Widget build(BuildContext context) { 99 | if (_hasCollected) { 100 | _collectionIcons = Icons.favorite; 101 | } else { 102 | _collectionIcons = Icons.favorite_border; 103 | } 104 | return Scaffold( 105 | key: _scaffoldKey, 106 | appBar: AppBar( 107 | title: Text(widget.title), 108 | actions: [ 109 | new IconButton( 110 | tooltip: 'goBack home', 111 | onPressed: _getCollection, 112 | icon: Icon( 113 | _collectionIcons, 114 | ), 115 | ), 116 | ], 117 | ), 118 | body: WebviewScaffold( 119 | url: widget.link, 120 | withZoom: false, 121 | withLocalStorage: true, 122 | withJavascript: true, 123 | ), 124 | ); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /lib/widgets/error/error_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | /// 自定义的崩溃页面 5 | class ErrorPage extends StatelessWidget{ 6 | @override 7 | Widget build(BuildContext context) { 8 | return Scaffold( 9 | appBar: AppBar( 10 | title: Text("出了点问题"), 11 | ), 12 | body: Center( 13 | child: Text("出了点问题~\n我们会马上修复的。"), 14 | ) 15 | ); 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /lib/widgets/error/flutter_crash_plugin.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/services.dart'; 3 | 4 | /// 崩溃上报插件 5 | class FlutterCrashPlugin { 6 | //初始化方法通道 7 | static const MethodChannel _channel = 8 | const MethodChannel('flutter_crash_plugin'); 9 | 10 | static void setUp(appID) { 11 | //使用app_id进行SDK注册 12 | _channel.invokeMethod("setUp",{'app_id':appID}); 13 | } 14 | static void postException(error, stack) { 15 | //将异常和堆栈上报至Bugly 16 | _channel.invokeMethod("postException",{'crash_message':error.toString(),'crash_detail':stack.toString()}); 17 | } 18 | } -------------------------------------------------------------------------------- /lib/widgets/list_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// 列表项 4 | class ListItem extends StatefulWidget { 5 | // 点击事件 6 | final VoidCallback onPressed; 7 | // 图标 8 | final Widget icon; 9 | // 标题 10 | final String title; 11 | final Color titleColor; 12 | // 描述 13 | final String describe; 14 | final double describeSpace; 15 | final Color describeColor; 16 | // 右侧控件 17 | final Widget rightWidget; 18 | 19 | // 构造函数 20 | ListItem({ 21 | Key key, 22 | this.onPressed, 23 | this.icon, 24 | this.title, 25 | this.titleColor: Colors.black, 26 | this.describe, 27 | this.describeSpace: 3.0, 28 | this.describeColor: Colors.grey, 29 | this.rightWidget, 30 | }) : super(key: key); 31 | 32 | @override 33 | _ListItemState createState() => _ListItemState(); 34 | } 35 | 36 | class _ListItemState extends State { 37 | @override 38 | Widget build(BuildContext context) { 39 | return FlatButton( 40 | onPressed: widget.onPressed, 41 | padding: EdgeInsets.all(0.0), 42 | shape: Border.all( 43 | color: Colors.transparent, 44 | width: 0.0, 45 | style: BorderStyle.none, 46 | ), 47 | child: Container( 48 | height: 60.0, 49 | width: double.infinity, 50 | child: Row( 51 | children: [ 52 | widget.icon != null 53 | ? Container( 54 | padding: EdgeInsets.all(14.0), 55 | child: SizedBox( 56 | height: 32.0, 57 | width: 32.0, 58 | child: widget.icon, 59 | ), 60 | ) 61 | : Container( 62 | width: 14.0, 63 | ), 64 | Expanded( 65 | flex: 1, 66 | child: Column( 67 | mainAxisAlignment: MainAxisAlignment.center, 68 | crossAxisAlignment: CrossAxisAlignment.start, 69 | children: [ 70 | widget.title != null 71 | ? Text( 72 | widget.title, 73 | style: TextStyle( 74 | color: widget.titleColor, 75 | fontSize: 14.0, 76 | fontWeight: FontWeight.bold, 77 | ), 78 | ) 79 | : Container(), 80 | widget.describe != null 81 | ? Padding( 82 | padding: EdgeInsets.only(top: widget.describeSpace), 83 | child: Text( 84 | widget.describe, 85 | maxLines: 2, 86 | style: TextStyle( 87 | color: widget.describeColor, fontSize: 12.0), 88 | )) 89 | : Container(), 90 | ], 91 | ), 92 | ), 93 | widget.rightWidget == null ? Container() : widget.rightWidget, 94 | Container( 95 | width: 14.0, 96 | ), 97 | ], 98 | )), 99 | ); 100 | } 101 | } 102 | 103 | /// 空图标 104 | class EmptyIcon extends Icon { 105 | const EmptyIcon() : super(Icons.hourglass_empty); 106 | @override 107 | Widget build(BuildContext context) { 108 | return Container(); 109 | } 110 | } -------------------------------------------------------------------------------- /lib/widgets/loading/dialog_manager.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_wanandroid/widgets/loading/loading_dialog.dart'; 3 | 4 | /// 弹窗管理组件 5 | class DialogManager{ 6 | static void showBasicDialog(BuildContext context, String msg){ 7 | showDialog( 8 | context: context, //BuildContext对象 9 | barrierDismissible: false, 10 | builder: (BuildContext context) { 11 | return new LoadingDialog( //调用对话框 12 | text: msg, 13 | ); 14 | }); 15 | } 16 | } -------------------------------------------------------------------------------- /lib/widgets/loading/loading_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class LoadingDialog extends Dialog { 4 | String text; 5 | 6 | LoadingDialog({Key key, @required this.text}) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return new Material( //创建透明层 11 | type: MaterialType.transparency, //透明类型 12 | child: new Center( //保证控件居中效果 13 | child: new SizedBox( 14 | width: 120.0, 15 | height: 120.0, 16 | child: new Container( 17 | decoration: ShapeDecoration( 18 | color: Color(0xffffffff), 19 | shape: RoundedRectangleBorder( 20 | borderRadius: BorderRadius.all( 21 | Radius.circular(8.0), 22 | ), 23 | ), 24 | ), 25 | child: new Column( 26 | mainAxisAlignment: MainAxisAlignment.center, 27 | crossAxisAlignment: CrossAxisAlignment.center, 28 | children: [ 29 | new CircularProgressIndicator(), 30 | new Padding( 31 | padding: const EdgeInsets.only( 32 | top: 20.0, 33 | ), 34 | child: new Text( 35 | text, 36 | style: new TextStyle(fontSize: 12.0), 37 | ), 38 | ), 39 | ], 40 | ), 41 | ), 42 | ), 43 | ), 44 | ); 45 | } 46 | } -------------------------------------------------------------------------------- /lib/widgets/state/empty_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | import 'package:flutter_wanandroid/constant/color_config.dart'; 4 | 5 | class EmptyView extends StatefulWidget { 6 | 7 | 8 | final VoidCallback emptyRetry; //无数据事件处理 9 | 10 | EmptyView(this.emptyRetry); 11 | 12 | @override 13 | _EmptyViewViewState createState() => _EmptyViewViewState(); 14 | } 15 | 16 | class _EmptyViewViewState extends State { 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return Container( 21 | color: Colors.white, 22 | width: ScreenUtil().setWidth(750), 23 | height: double.infinity, 24 | child: InkWell( 25 | onTap: widget.emptyRetry,// 回调,重试 26 | child: Column( 27 | mainAxisAlignment: MainAxisAlignment.center, 28 | children: [ 29 | Container( 30 | // width: ScreenUtil().setWidth(405), 31 | // height: ScreenUtil().setHeight(281), 32 | child: Image.asset('assets/images/8021-empty-and-lost.gif'), 33 | // child: Image.asset('assets/images/p1.png'), 34 | ), 35 | SizedBox( 36 | height: 30.0, 37 | ), 38 | Text('暂无相关数据,轻触重试~',style: TextStyle(color: ColorConfig.BLUE_200,fontSize: ScreenUtil().setSp(40)),) 39 | ], 40 | ), 41 | ) 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/widgets/state/load_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | import 'package:flutter_wanandroid/constant/color_config.dart'; 4 | import 'package:flutter_wanandroid/widgets/state/empty_view.dart'; 5 | 6 | ///四种视图状态 7 | enum LoadState { State_Success, State_Error, State_Loading, State_Empty } 8 | 9 | /// 根据不同状态来展示不同的视图 10 | class LoadStateLayout extends StatefulWidget { 11 | /// 页面状态 12 | final LoadState state; 13 | 14 | /// 成功视图 15 | final Widget successWidget; 16 | 17 | /// 错误事件处理 18 | final VoidCallback errorRetry; 19 | 20 | /// 空数据事件处理 21 | final VoidCallback emptyRetry; 22 | 23 | LoadStateLayout( 24 | {Key key, 25 | this.state = LoadState.State_Loading, //默认为加载状态 26 | this.successWidget, 27 | this.errorRetry, 28 | this.emptyRetry}) 29 | : super(key: key); 30 | 31 | @override 32 | _LoadStateLayoutState createState() => _LoadStateLayoutState(); 33 | } 34 | 35 | class _LoadStateLayoutState extends State { 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | return Container( 40 | //宽高都充满屏幕剩余空间 41 | width: double.infinity, 42 | height: double.infinity, 43 | child: _buildWidget, 44 | ); 45 | } 46 | 47 | /// 根据不同状态来显示不同的视图 48 | Widget get _buildWidget { 49 | switch (widget.state) { 50 | case LoadState.State_Success: 51 | return widget.successWidget; 52 | break; 53 | case LoadState.State_Error: 54 | return _errorView; 55 | break; 56 | case LoadState.State_Loading: 57 | return _loadingView; 58 | break; 59 | case LoadState.State_Empty: 60 | return EmptyView(widget.emptyRetry); 61 | break; 62 | default: 63 | return null; 64 | } 65 | } 66 | 67 | /// 加载中视图 68 | Widget get _loadingView { 69 | return Container( 70 | width: double.infinity, 71 | height: double.infinity, 72 | alignment: Alignment.center, 73 | color: Colors.white, 74 | child: Container( 75 | width: ScreenUtil().setWidth(750), 76 | height: double.infinity, 77 | padding: EdgeInsets.all(10), 78 | child: Column( 79 | mainAxisAlignment: MainAxisAlignment.spaceAround, 80 | children: [ 81 | Container( 82 | // width: 150, 83 | // height: 150, 84 | child: Image.asset('assets/images/23038-animatonblue.gif'),), 85 | SizedBox( 86 | height: 30.0, 87 | ), 88 | Text( 89 | '拼命加载中...', 90 | style: TextStyle( 91 | color: ColorConfig.BLUE_200, 92 | fontSize: ScreenUtil().setSp(40)), 93 | ) 94 | ], 95 | ), 96 | ), 97 | ); 98 | } 99 | 100 | /// 错误视图 101 | Widget get _errorView { 102 | return Container( 103 | width: double.infinity, 104 | height: double.infinity, 105 | color: Colors.white, 106 | child: InkWell( 107 | onTap: widget.errorRetry, // 回调,重试 108 | child: Column( 109 | mainAxisAlignment: MainAxisAlignment.center, 110 | children: [ 111 | Container( 112 | // width: ScreenUtil().setWidth(405), 113 | // height: ScreenUtil().setHeight(317), 114 | child: Image.asset('assets/images/7903-error-404.gif'),), 115 | SizedBox( 116 | height: 30.0, 117 | ), 118 | Text( 119 | "加载失败,请轻触重试!", 120 | style: TextStyle( 121 | color: ColorConfig.BLUE_200, 122 | fontSize: ScreenUtil().setSp(24)), 123 | ), 124 | ], 125 | ), 126 | )); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_wanandroid 2 | description: A new Flutter application. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # Read more about versioning at semver.org. 10 | version: 1.0.2+3 11 | 12 | environment: 13 | sdk: ">=2.2.0 <3.0.0" 14 | 15 | dependencies: 16 | flutter: 17 | sdk: flutter 18 | # 本地语言包 19 | flutter_localizations: 20 | sdk: flutter 21 | # The following adds the Cupertino Icons font to your application. 22 | # Use with the CupertinoIcons class for iOS style icons. 23 | cupertino_icons: ^0.1.2 24 | event_bus: ^1.0.1 25 | fluro: ^1.3.4 26 | # image_picker: ^0.4.10 27 | sqflite: ^1.3.0+1 28 | flutter_markdown: ^0.2.0 29 | url_launcher: ^5.4.10 30 | # 本地存储、收藏功能 31 | shared_preferences: 0.5.3 32 | # 网络请求库 33 | dio: ^3.0.9 34 | # cookie 管理 35 | cookie_jar: ^1.0.1 36 | dio_cookie_manager: 1.0.0 37 | flutter_webview_plugin: ^0.3.11 38 | # 日期格式化 39 | # intl: 0.15.7 40 | # city_pickers: ^0.1.0 41 | # 下拉刷新库 42 | flutter_easyrefresh: ^2.0.9 43 | # 图片库 44 | extended_image: ^0.7.1 45 | # Toast 46 | fluttertoast: ^3.1.3 47 | # 屏幕适配 48 | flutter_screenutil: ^1.0.2 49 | # 瀑布流 50 | flutter_staggered_grid_view: ^0.3.0 51 | # 显示 html 52 | flutter_html: ^0.11.1 53 | # google 图标 54 | groovin_material_icons: ^1.1.5 55 | # 获取临时目录和文档目录 56 | path_provider: ^1.6.9 57 | # provider 依赖注入和状态管理 58 | provider: ^4.0.5 59 | # # 相机 60 | # camera: ^0.5.8+1 61 | # # 视频 62 | # video_player: ^0.10.11 63 | # 极光推送 64 | jpush_flutter: 0.5.5 65 | # jpush_flutter: 66 | # git: 67 | # url: git://github.com/jpush/jpush-flutter-plugin.git 68 | # ref: master 69 | # bugly 70 | flutter_bugly: 0.2.8 71 | # 获取app信息:版本、包名等 72 | package_info: ^0.4.0+18 73 | 74 | dev_dependencies: 75 | flutter_test: 76 | sdk: flutter 77 | 78 | 79 | # For information on the generic Dart part of this file, see the 80 | # following page: https://www.dartlang.org/tools/pub/pubspec 81 | 82 | # The following section is specific to Flutter. 83 | flutter: 84 | 85 | # The following line ensures that the Material Icons font is 86 | # included with your application, so that you can use the icons in 87 | # the material Icons class. 88 | uses-material-design: true 89 | 90 | # To add assets to your application, add an assets section, like this: 91 | assets: 92 | - assets/app.db 93 | - assets/images/ 94 | - assets/fonts/ 95 | 96 | 97 | # An image asset can refer to one or more resolution-specific "variants", see 98 | # https://flutter.io/assets-and-images/#resolution-aware. 99 | 100 | # For details regarding adding assets from package dependencies, see 101 | # https://flutter.io/assets-and-images/#from-packages 102 | 103 | # To add custom fonts to your application, add a fonts section here, 104 | # in this "flutter" section. Each entry in this list should have a 105 | # "family" key with the font family name, and a "fonts" key with a 106 | # list giving the asset and other descriptors for the font. For 107 | # example: 108 | # fonts: 109 | # - family: Schyler 110 | # fonts: 111 | # - asset: fonts/Schyler-Regular.ttf 112 | # - asset: fonts/Schyler-Italic.ttf 113 | # style: italic 114 | # - family: Trajan Pro 115 | # fonts: 116 | # - asset: fonts/TrajanPro.ttf 117 | # - asset: fonts/TrajanPro_Bold.ttf 118 | # weight: 700 119 | # 120 | # For details regarding fonts from package dependencies, 121 | # see https://flutter.io/custom-fonts/#from-packages 122 | -------------------------------------------------------------------------------- /res/values/strings_en.arb: -------------------------------------------------------------------------------- 1 | { 2 | "app_title": "app_title", 3 | "main_title": "My Main Title", 4 | "message_tip" : "You have pushed the button many times: $count" 5 | } -------------------------------------------------------------------------------- /res/values/strings_zh.arb: -------------------------------------------------------------------------------- 1 | { 2 | "app_title": "App标题", 3 | "main_title": "主标题", 4 | "message_tip" : "点击按钮次数: $count" 5 | } -------------------------------------------------------------------------------- /screenshot/about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/screenshot/about.png -------------------------------------------------------------------------------- /screenshot/cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/screenshot/cat.png -------------------------------------------------------------------------------- /screenshot/change-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/screenshot/change-color.png -------------------------------------------------------------------------------- /screenshot/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/screenshot/home.png -------------------------------------------------------------------------------- /screenshot/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/screenshot/login.png -------------------------------------------------------------------------------- /screenshot/mine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/screenshot/mine.png -------------------------------------------------------------------------------- /screenshot/my-collect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/screenshot/my-collect.png -------------------------------------------------------------------------------- /screenshot/project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/screenshot/project.png -------------------------------------------------------------------------------- /screenshot/rank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/screenshot/rank.png -------------------------------------------------------------------------------- /screenshot/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/screenshot/splash.png -------------------------------------------------------------------------------- /screenshot/theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/screenshot/theme.png -------------------------------------------------------------------------------- /screenshot/web-detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YGragon/Flutter-WanAndroid/ef3ac56a2dc090362f7c1a9c6aa14e9fb6fd6a2d/screenshot/web-detail.png -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | import 'package:flutter_wanandroid/init/app.dart'; 11 | 12 | import 'package:flutter_wanandroid/main.dart'; 13 | 14 | void main() { 15 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 16 | // Build our app and trigger a frame. 17 | await tester.pumpWidget(MyApp()); 18 | 19 | // Verify that our counter starts at 0. 20 | expect(find.text('0'), findsOneWidget); 21 | expect(find.text('1'), findsNothing); 22 | 23 | // Tap the '+' icon and trigger a frame. 24 | await tester.tap(find.byIcon(Icons.add)); 25 | await tester.pump(); 26 | 27 | // Verify that our counter has incremented. 28 | expect(find.text('0'), findsNothing); 29 | expect(find.text('1'), findsOneWidget); 30 | }); 31 | } 32 | --------------------------------------------------------------------------------