├── .all-contributorsrc ├── .gitignore ├── .gitlab-ci.yml ├── LICENSE ├── README.md ├── android ├── .gradle │ ├── 4.10.2 │ │ ├── fileChanges │ │ │ └── last-build.bin │ │ ├── fileContent │ │ │ └── fileContent.lock │ │ └── gc.properties │ └── vcs-1 │ │ └── gc.properties ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── yu │ │ │ │ └── MainActivity.java │ │ └── res │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── logo.png │ │ │ ├── mipmap-mdpi │ │ │ └── logo.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── logo.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── logo.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── logo.png │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── flutter.jks ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── yu_android.iml ├── dique.iml ├── flrs ├── animation_test.flr ├── aura.flr ├── bg_head.flr ├── loading_bird.flr └── main_logo.flr ├── images ├── bird.png ├── explain.png ├── explain_2.png ├── splash_screen.png └── token_introduce.png ├── ios ├── Flutter │ ├── AppFrameworkInfo.plist │ └── Debug.xcconfig ├── Podfile ├── Pods │ ├── Headers │ │ ├── Private │ │ │ ├── FMDB │ │ │ │ ├── FMDB.h │ │ │ │ ├── FMDatabase.h │ │ │ │ ├── FMDatabaseAdditions.h │ │ │ │ ├── FMDatabasePool.h │ │ │ │ ├── FMDatabaseQueue.h │ │ │ │ └── FMResultSet.h │ │ │ ├── JPush │ │ │ │ └── JPUSHService.h │ │ │ ├── path_provider │ │ │ │ └── PathProviderPlugin.h │ │ │ ├── shared_preferences │ │ │ │ └── SharedPreferencesPlugin.h │ │ │ └── sqflite │ │ │ │ ├── SqfliteOperation.h │ │ │ │ └── SqflitePlugin.h │ │ └── Public │ │ │ ├── FMDB │ │ │ ├── FMDB.h │ │ │ ├── FMDatabase.h │ │ │ ├── FMDatabaseAdditions.h │ │ │ ├── FMDatabasePool.h │ │ │ ├── FMDatabaseQueue.h │ │ │ └── FMResultSet.h │ │ │ ├── JPush │ │ │ └── JPUSHService.h │ │ │ ├── path_provider │ │ │ └── PathProviderPlugin.h │ │ │ ├── shared_preferences │ │ │ └── SharedPreferencesPlugin.h │ │ │ └── sqflite │ │ │ ├── SqfliteOperation.h │ │ │ └── SqflitePlugin.h │ ├── Local Podspecs │ │ ├── flutter_webview_plugin.podspec.json │ │ ├── jpush_flutter.podspec.json │ │ ├── path_provider.podspec.json │ │ ├── shared_preferences.podspec.json │ │ └── sqflite.podspec.json │ ├── Pods.xcodeproj │ │ └── xcuserdata │ │ │ └── yuan.xcuserdatad │ │ │ └── xcschemes │ │ │ ├── FMDB.xcscheme │ │ │ ├── flutter_webview_plugin.xcscheme │ │ │ ├── jpush_flutter.xcscheme │ │ │ └── path_provider.xcscheme │ └── Target Support Files │ │ ├── FMDB │ │ ├── FMDB-dummy.m │ │ └── FMDB-prefix.pch │ │ ├── Pods-Runner │ │ └── Pods-Runner-dummy.m │ │ ├── path_provider │ │ ├── path_provider-dummy.m │ │ └── path_provider-prefix.pch │ │ ├── shared_preferences │ │ ├── shared_preferences-dummy.m │ │ └── shared_preferences-prefix.pch │ │ └── sqflite │ │ ├── sqflite-dummy.m │ │ └── sqflite-prefix.pch ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Runner │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── icon_dique_1024x1024.png │ │ │ ├── icon_dique_29x29@1x.png │ │ │ ├── icon_dique_29x29@2x.png │ │ │ ├── icon_dique_40x40@2x-1.png │ │ │ ├── icon_dique_60x60-1.png │ │ │ ├── icon_dique_60x60@2x-1.png │ │ │ ├── icon_dique_60x60@2x.png │ │ │ ├── icon_dique_60x60@3x.png │ │ │ ├── icon_dique_80x80@2x.png │ │ │ └── icon_dique_87x87@3x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── GeneratedPluginRegistrant.h │ ├── GeneratedPluginRegistrant.m │ ├── Info.plist │ ├── Runner.entitlements │ ├── main.m │ └── splash_screen.png └── ServiceDefinitions.json ├── lib ├── json │ ├── article_detail_bean.dart │ ├── article_list_bean.dart │ ├── check_update_bean.dart │ ├── group_repository_bean.dart │ ├── jpush_bean.dart │ ├── local_repolist_bean.dart │ ├── repo_detail_bean.dart │ ├── repo_directory_bean.dart │ ├── user_groups_bean.dart │ └── user_info_bean.dart ├── logic │ ├── all_logic.dart │ ├── article_detail_page_logic.dart │ ├── article_page_logic.dart │ ├── login_page_logic.dart │ ├── main_page_logic.dart │ └── repository_page_logic.dart ├── main.dart ├── model │ ├── all_model.dart │ ├── article_detail_page_event.dart │ ├── article_page_event.dart │ ├── login_page_event.dart │ ├── main_page_event.dart │ └── repository_page_event.dart ├── pages │ ├── about_page.dart │ ├── all_pages.dart │ ├── article_detail_page.dart │ ├── article_page.dart │ ├── explain_page.dart │ ├── feedback_page.dart │ ├── image_page.dart │ ├── login_page.dart │ ├── main_page.dart │ ├── provider_pages.dart │ ├── repository_page.dart │ ├── version_page.dart │ └── webview_page.dart ├── public │ ├── HttpService.dart │ ├── NetManager.dart │ ├── api_service.dart │ ├── public_header.dart │ └── theme.dart ├── utils │ ├── check_update_util.dart │ ├── keys.dart │ ├── shared_util.dart │ └── toast_util.dart └── widget │ ├── custom_book.dart │ ├── explosion_widget.dart │ ├── hide_anim_widget.dart │ ├── loading_dialog.dart │ ├── loading_widget.dart │ ├── nav_head.dart │ ├── rotate_anim_widget.dart │ ├── top_show_widget.dart │ └── update_dialog.dart ├── pubspec.yaml └── test └── widget_test.dart /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "dique", 3 | "projectOwner": "FEMessage", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "imageSize": 100, 10 | "commit": false, 11 | "commitConvention": "angular", 12 | "contributors": [ 13 | { 14 | "login": "asjqkkkk", 15 | "name": "android bro", 16 | "avatar_url": "https://avatars3.githubusercontent.com/u/30992818?v=4", 17 | "profile": "https://github.com/asjqkkkk", 18 | "contributions": [ 19 | "code", 20 | "doc", 21 | "design", 22 | "infra" 23 | ] 24 | }, 25 | { 26 | "login": "kira2015", 27 | "name": "wu_zy", 28 | "avatar_url": "https://avatars2.githubusercontent.com/u/14231117?v=4", 29 | "profile": "https://github.com/kira2015", 30 | "contributions": [ 31 | "code", 32 | "doc", 33 | "content", 34 | "infra" 35 | ] 36 | } 37 | ], 38 | "contributorsPerLine": 7 39 | } 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/encodings.xml 2 | .idea/modules.xml 3 | .idea/vcs.xml 4 | .idea/workspace.xml 5 | .idea/yuque-app.iml 6 | .idea/libraries/Dart_Packages.xml 7 | .idea/libraries/Dart_SDK.xml 8 | .idea/libraries/Flutter_Plugins.xml 9 | android/.gradle/4.10.2/fileHashes/fileHashes.bin 10 | android/.gradle/4.10.2/fileHashes/fileHashes.lock 11 | android/.gradle/4.10.2/fileHashes/resourceHashesCache.bin 12 | android/.gradle/4.10.2/javaCompile/classAnalysis.bin 13 | android/.gradle/4.10.2/javaCompile/jarAnalysis.bin 14 | android/.gradle/4.10.2/javaCompile/javaCompile.lock 15 | android/.gradle/4.10.2/javaCompile/taskHistory.bin 16 | android/.gradle/4.10.2/taskHistory/taskHistory.bin 17 | android/.gradle/4.10.2/taskHistory/taskHistory.lock 18 | android/.gradle/buildOutputCleanup/buildOutputCleanup.lock 19 | android/.gradle/buildOutputCleanup/outputFiles.bin 20 | android/android.iml 21 | android/.idea/encodings.xml 22 | android/.idea/gradle.xml 23 | android/.idea/misc.xml 24 | android/.idea/modules.xml 25 | android/.idea/runConfigurations.xml 26 | android/.idea/vcs.xml 27 | android/.idea/workspace.xml 28 | android/.idea/caches/build_file_checksums.ser 29 | android/.idea/caches/gradle_models.ser 30 | android/.idea/libraries/Gradle____local_aars____Users_lichen_flutter_bin_cache_artifacts_engine_android_arm_flutter_jar_unspecified_jar.xml 31 | android/.idea/libraries/Gradle____local_aars____Users_lichen_flutter_projects_yuque_app_build_app_intermediates_flutter_flutter_x86_jar_unspecified_jar.xml 32 | android/.idea/libraries/Gradle__androidx_annotation_annotation_1_0_0_jar.xml 33 | android/.idea/libraries/Gradle__androidx_appcompat_appcompat_1_0_0_aar.xml 34 | android/.idea/libraries/Gradle__androidx_arch_core_core_common_2_0_0_jar.xml 35 | android/.idea/libraries/Gradle__androidx_arch_core_core_runtime_2_0_0_aar.xml 36 | android/.idea/libraries/Gradle__androidx_asynclayoutinflater_asynclayoutinflater_1_0_0_aar.xml 37 | android/.idea/libraries/Gradle__androidx_collection_collection_1_0_0_jar.xml 38 | android/.idea/libraries/Gradle__androidx_coordinatorlayout_coordinatorlayout_1_0_0_aar.xml 39 | android/.idea/libraries/Gradle__androidx_core_core_1_0_0_aar.xml 40 | android/.idea/libraries/Gradle__androidx_cursoradapter_cursoradapter_1_0_0_aar.xml 41 | android/.idea/libraries/Gradle__androidx_customview_customview_1_0_0_aar.xml 42 | android/.idea/libraries/Gradle__androidx_documentfile_documentfile_1_0_0_aar.xml 43 | android/.idea/libraries/Gradle__androidx_drawerlayout_drawerlayout_1_0_0_aar.xml 44 | android/.idea/libraries/Gradle__androidx_fragment_fragment_1_0_0_aar.xml 45 | android/.idea/libraries/Gradle__androidx_interpolator_interpolator_1_0_0_aar.xml 46 | android/.idea/libraries/Gradle__androidx_legacy_legacy_support_core_ui_1_0_0_aar.xml 47 | android/.idea/libraries/Gradle__androidx_legacy_legacy_support_core_utils_1_0_0_aar.xml 48 | android/.idea/libraries/Gradle__androidx_legacy_legacy_support_v4_1_0_0_aar.xml 49 | android/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_common_2_0_0_jar.xml 50 | android/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_livedata_2_0_0_aar.xml 51 | android/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_livedata_core_2_0_0_aar.xml 52 | android/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_runtime_2_0_0_aar.xml 53 | android/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_viewmodel_2_0_0_aar.xml 54 | android/.idea/libraries/Gradle__androidx_loader_loader_1_0_0_aar.xml 55 | android/.idea/libraries/Gradle__androidx_localbroadcastmanager_localbroadcastmanager_1_0_0_aar.xml 56 | android/.idea/libraries/Gradle__androidx_media_media_1_0_0_aar.xml 57 | android/.idea/libraries/Gradle__androidx_print_print_1_0_0_aar.xml 58 | android/.idea/libraries/Gradle__androidx_slidingpanelayout_slidingpanelayout_1_0_0_aar.xml 59 | android/.idea/libraries/Gradle__androidx_swiperefreshlayout_swiperefreshlayout_1_0_0_aar.xml 60 | android/.idea/libraries/Gradle__androidx_vectordrawable_vectordrawable_1_0_0_aar.xml 61 | android/.idea/libraries/Gradle__androidx_vectordrawable_vectordrawable_animated_1_0_0_aar.xml 62 | android/.idea/libraries/Gradle__androidx_versionedparcelable_versionedparcelable_1_0_0_aar.xml 63 | android/.idea/libraries/Gradle__androidx_viewpager_viewpager_1_0_0_aar.xml 64 | android/.idea/libraries/Gradle__cn_jiguang_sdk_jcore_1_2_5_aar.xml 65 | android/.idea/libraries/Gradle__cn_jiguang_sdk_jpush_3_1_6_aar.xml 66 | android/.idea/libraries/Gradle__com_android_support_support_annotations_27_1_1_jar.xml 67 | android/.idea/libraries/Gradle__com_android_support_test_espresso_espresso_core_3_0_2_aar.xml 68 | android/.idea/libraries/Gradle__com_android_support_test_espresso_espresso_idling_resource_3_0_2_aar.xml 69 | android/.idea/libraries/Gradle__com_android_support_test_monitor_1_0_2_aar.xml 70 | android/.idea/libraries/Gradle__com_android_support_test_runner_1_0_2_aar.xml 71 | android/.idea/libraries/Gradle__com_google_code_findbugs_jsr305_2_0_1_jar.xml 72 | android/.idea/libraries/Gradle__com_squareup_javawriter_2_1_1_jar.xml 73 | android/.idea/libraries/Gradle__com_tencent_bugly_crashreport_upgrade_1_4_0_aar.xml 74 | android/.idea/libraries/Gradle__com_tencent_bugly_nativecrashreport_3_7_1_aar.xml 75 | android/.idea/libraries/Gradle__javax_inject_javax_inject_1_jar.xml 76 | android/.idea/libraries/Gradle__junit_junit_4_12_jar.xml 77 | android/.idea/libraries/Gradle__net_sf_kxml_kxml2_2_3_0_jar.xml 78 | android/.idea/libraries/Gradle__org_hamcrest_hamcrest_core_1_3_jar.xml 79 | android/.idea/libraries/Gradle__org_hamcrest_hamcrest_integration_1_3_jar.xml 80 | android/.idea/libraries/Gradle__org_hamcrest_hamcrest_library_1_3_jar.xml 81 | android/app/app.iml 82 | ios/.symlinks/plugins/flutter_bugly 83 | ios/.symlinks/plugins/path_provider 84 | ios/.symlinks/plugins/shared_preferences 85 | ios/.symlinks/plugins/sqflite 86 | key.properties 87 | GeneratedPluginRegistrant.java 88 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: openjdk:8-jdk 2 | 3 | variables: 4 | ANDROID_COMPILE_SDK: "28" 5 | ANDROID_BUILD_TOOLS: "28.0.3" 6 | ANDROID_SDK_TOOLS: "4333796" 7 | 8 | #cache: 9 | # key: flutter_sdk_cache 10 | # paths: 11 | # - .gradle/wrapper 12 | # - .gradle/caches 13 | 14 | 15 | 16 | before_script: 17 | # android 18 | - apt-get --quiet update --yes 19 | - apt-get --quiet install --yes wget tar unzip lib32stdc++6 lib32z1 20 | - wget --quiet --output-document=android-sdk.zip https://dl.google.com/android/repository/sdk-tools-linux-${ANDROID_SDK_TOOLS}.zip 21 | - unzip -d android-sdk-linux android-sdk.zip 22 | - echo y | android-sdk-linux/tools/bin/sdkmanager "platforms;android-${ANDROID_COMPILE_SDK}" >/dev/null 23 | - echo y | android-sdk-linux/tools/bin/sdkmanager "platform-tools" >/dev/null 24 | - echo y | android-sdk-linux/tools/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS}" >/dev/null 25 | - export ANDROID_HOME=$PWD/android-sdk-linux 26 | - export PATH=$PATH:$PWD/android-sdk-linux/platform-tools/ 27 | 28 | # 暂时禁用检查错误,并使用yes接受所有许可证 29 | - set +o pipefail 30 | - yes | android-sdk-linux/tools/bin/sdkmanager --licenses 31 | - set -o pipefail 32 | 33 | 34 | # flutter 35 | - wget --output-document=flutter-sdk.tar.xz https://storage.googleapis.com/flutter_infra/releases/stable/linux/flutter_linux_v1.7.8+hotfix.4-stable.tar.xz 36 | - tar -xf flutter-sdk.tar.xz 37 | - export PATH=$PATH:$PWD/flutter/bin 38 | - export PUB_HOSTED_URL=https://pub.flutter-io.cn 39 | - export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn 40 | - echo flutter.sdk=$PWD/flutter > android/local.properties 41 | - cd android 42 | - rm -rf android/.gradle 43 | 44 | stages: 45 | - android-build 46 | 47 | 48 | assembleRelease: 49 | stage: android-build 50 | # tags: 51 | # - build 52 | only: 53 | - dev 54 | script: 55 | - cd .. 56 | # - flutter doctor 57 | # - ifconfig 58 | - flutter packages get 59 | - flutter build apk --target-platform android-arm --split-per-abi 60 | - mv build/app/outputs/apk/release/app-armeabi-v7a-release.apk build 61 | - ls 62 | # - curl http://127.0.0.1:8080/file/upload -F "file=@/build/app/outputs/apk/release/app-release.apk" 63 | artifacts: 64 | paths: 65 | - build/app-armeabi-v7a-release.apk -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 deepexi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![dique](https://user-images.githubusercontent.com/30992818/63570745-c2862d00-c5b0-11e9-87c9-5db9af0082da.png) 2 | 3 | 4 | [![support](https://img.shields.io/badge/platform-flutter%7Cdart%20vm-ff69b4.svg?style=flat-square)](https://github.com/FEMessage/dique) 5 | [![apkdownload](https://img.shields.io/badge/download-apk-brightgreen)](http://levy.ren/) 6 | 7 | # 介绍 8 | 9 | 【滴雀】app面向所有语雀用户。 10 | 11 | 它完全使用flutter编写,同时所有内容提供来源接口皆由语雀提供: 12 | [**语雀开发者**](https://www.yuque.com/yuque/developer) 13 | 14 | 使用只需要提供你的语雀账号token即可! 15 | 16 | 17 | 登录页| 主页 | 文章详情 18 | ---|---|--- 19 | | | 20 | 21 | 22 | # 项目结构 23 | 24 | 下面是项目文件结构 25 | 26 | 27 | 28 | 29 | 30 | - flr:存放flare动画文件 31 | - images:存放图片文件 32 | - json:存放网络请求json文件 33 | - logic:逻辑操作 34 | - model:数据存放 35 | - pages:所有页面 36 | - public:一些配置类 37 | - utils:工具类 38 | - widgets:自定义Widget 39 | 40 | 41 | 42 | # 第三方库 43 | 44 | 下面是项目中使用到的第三方库说明 45 | 46 | 控件 | 说明 47 | ---|--- 48 | [dio](https://pub.flutter-io.cn/packages/dio) | 网络请求 49 | [shared_preferences](https://pub.flutter-io.cn/packages/shared_preferences) | 本地存储 50 | [provider](https://pub.flutter-io.cn/packages/provider) | 状态管理 51 | [test](https://pub.flutter-io.cn/packages/test) | 单元测试 52 | [cached_network_image](https://pub.flutter-io.cn/packages/cached_network_image) | 图片缓存 53 | [path_provider](https://pub.flutter-io.cn/packages/path_provider) | 路径获取 54 | [package_info](https://pub.flutter-io.cn/packages/package_info) | 获取package信息 55 | [flutter_webview_plugin](https://pub.flutter-io.cn/packages/flutter_webview_plugin) | 网页 56 | [pull_to_refresh](https://pub.flutter-io.cn/packages/pull_to_refresh) | 上拉加载 57 | [photo_view](https://pub.flutter-io.cn/packages/photo_view) | 图片展示 58 | [font_awesome_flutter](https://pub.flutter-io.cn/packages/font_awesome_flutter) | 各种矢量图标 59 | [open_file](https://pub.flutter-io.cn/packages/open_file) | 打开文件,android更新下载安装包用 60 | [flare_flutter](https://pub.flutter-io.cn/packages/flare_flutter) | flare动画 61 | [flutter_html](https://pub.flutter-io.cn/packages/flutter_html) | 解析html 62 | [jpush_flutter](https://pub.flutter-io.cn/packages/jpush_flutter) | 极光推送 63 | 64 | 65 | # 构建配置 66 | 67 | 为了避免类似android打包key存放在云端,我们使用的是譬如gitlab的variables功能来通过环境变量提供所需参数: 68 | 69 | ![](https://user-images.githubusercontent.com/30992818/63574277-fc0f6600-c5b9-11e9-8d4f-c9ceb98c6685.png) 70 | 71 | 如上面中所配置的这样,项目中有五个参数需要在使用的时候进行配置,可以查看android/app/build.gradle文件: 72 | 73 | 74 | ``` 75 | def appKeyPassword = System.getenv('KEY_PASSWORD') 76 | def appStorePassword = System.getenv('STORE_PASSWORD') 77 | def appKeyAlias = System.getenv('KEY_ALIAS') 78 | def appStoreFile = System.getenv('STORE_FILE') 79 | def jPushKey = System.getenv('JPUSH_APP_KEY') 80 | ``` 81 | 前四个参数用于打包android apk,最后的参数是使用极光推送所需要的appkey 82 | 83 | ## Contributors 84 | 85 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 86 | 87 | 88 | 89 |
android bro
android bro

💻 📖 🎨 🚇
wu_zy
wu_zy

💻 📖 🖋 🚇
90 | 91 | 92 | 93 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! -------------------------------------------------------------------------------- /android/.gradle/4.10.2/fileChanges/last-build.bin: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /android/.gradle/4.10.2/fileContent/fileContent.lock: -------------------------------------------------------------------------------- 1 | |a㽒_ش -------------------------------------------------------------------------------- /android/.gradle/4.10.2/gc.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/android/.gradle/4.10.2/gc.properties -------------------------------------------------------------------------------- /android/.gradle/vcs-1/gc.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/android/.gradle/vcs-1/gc.properties -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | def appKeyPassword = System.getenv('KEY_PASSWORD') 28 | def appStorePassword = System.getenv('STORE_PASSWORD') 29 | def appKeyAlias = System.getenv('KEY_ALIAS') 30 | def appStoreFile = System.getenv('STORE_FILE') 31 | def jPushKey = System.getenv('JPUSH_APP_KEY') 32 | 33 | 34 | android { 35 | compileSdkVersion 28 36 | 37 | lintOptions { 38 | disable 'InvalidPackage' 39 | } 40 | 41 | defaultConfig { 42 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 43 | applicationId "com.example.yu" 44 | minSdkVersion 16 45 | targetSdkVersion 28 46 | versionCode flutterVersionCode.toInteger() 47 | versionName flutterVersionName 48 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 49 | 50 | // ndk { 51 | // //选择要添加的对应 cpu 类型的 .so 库。 52 | // abiFilters 'armeabi', 'armeabi-v7a', 'x86', 'x86_64', 'mips', 'mips64' ,'arm64-v8a' 53 | //// abiFilters 'armeabi-v7a' 54 | // } 55 | 56 | if(System.getenv('STORE_FILE') == null){ 57 | def keystorePropertiesFile = rootProject.file("key.properties") 58 | def keystoreProperties = new Properties() 59 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 60 | manifestPlaceholders = [ 61 | JPUSH_PKGNAME : applicationId, 62 | JPUSH_APPKEY : keystoreProperties['jPushAPPKEY'], // NOTE: JPush 上注册的包名对应的 Appkey. 63 | JPUSH_CHANNEL : "developer-default", //暂时填写默认值即可. 64 | ] 65 | } else { 66 | manifestPlaceholders = [ 67 | JPUSH_PKGNAME : applicationId, 68 | JPUSH_APPKEY : jPushKey, // NOTE: JPush 上注册的包名对应的 Appkey. 69 | JPUSH_CHANNEL : "developer-default", //暂时填写默认值即可. 70 | ] 71 | } 72 | 73 | 74 | 75 | } 76 | 77 | signingConfigs { 78 | if(System.getenv('STORE_FILE') == null){ 79 | def keystorePropertiesFile = rootProject.file("key.properties") 80 | def keystoreProperties = new Properties() 81 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 82 | release { 83 | keyAlias keystoreProperties['keyAlias'] 84 | keyPassword keystoreProperties['keyPassword'] 85 | storeFile file(keystoreProperties['storeFile']) 86 | storePassword keystoreProperties['storePassword'] 87 | } 88 | } else{ 89 | release { 90 | keyAlias appKeyAlias 91 | keyPassword appKeyPassword 92 | storeFile file(appStoreFile) 93 | storePassword appStorePassword 94 | } 95 | } 96 | } 97 | 98 | buildTypes { 99 | release { 100 | // TODO: Add your own signing config for the release build. 101 | // Signing with the debug keys for now, so `flutter run --release` works. 102 | signingConfig signingConfigs.release 103 | } 104 | } 105 | } 106 | 107 | flutter { 108 | source '../..' 109 | } 110 | 111 | dependencies { 112 | testImplementation 'junit:junit:4.12' 113 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 114 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 115 | } 116 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 22 | 29 | 33 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/example/yu/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.yu; 2 | 3 | import android.os.Bundle; 4 | import io.flutter.app.FlutterActivity; 5 | import io.flutter.plugins.GeneratedPluginRegistrant; 6 | 7 | public class MainActivity extends FlutterActivity { 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | GeneratedPluginRegistrant.registerWith(this); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/android/app/src/main/res/mipmap-hdpi/logo.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/android/app/src/main/res/mipmap-mdpi/logo.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/android/app/src/main/res/mipmap-xhdpi/logo.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/android/app/src/main/res/mipmap-xxhdpi/logo.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/android/app/src/main/res/mipmap-xxxhdpi/logo.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.2.1' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /android/flutter.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/android/flutter.jks -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableJetifier=true 3 | android.useAndroidX=true -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /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.10.2-all.zip 7 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /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/yu_android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /dique.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /flrs/animation_test.flr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/flrs/animation_test.flr -------------------------------------------------------------------------------- /flrs/aura.flr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/flrs/aura.flr -------------------------------------------------------------------------------- /flrs/bg_head.flr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/flrs/bg_head.flr -------------------------------------------------------------------------------- /flrs/loading_bird.flr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/flrs/loading_bird.flr -------------------------------------------------------------------------------- /flrs/main_logo.flr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/flrs/main_logo.flr -------------------------------------------------------------------------------- /images/bird.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/images/bird.png -------------------------------------------------------------------------------- /images/explain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/images/explain.png -------------------------------------------------------------------------------- /images/explain_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/images/explain_2.png -------------------------------------------------------------------------------- /images/splash_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/images/splash_screen.png -------------------------------------------------------------------------------- /images/token_introduce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/images/token_introduce.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 "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def parse_KV_file(file, separator='=') 14 | file_abs_path = File.expand_path(file) 15 | if !File.exists? file_abs_path 16 | return []; 17 | end 18 | pods_ary = [] 19 | skip_line_start_symbols = ["#", "/"] 20 | File.foreach(file_abs_path) { |line| 21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 22 | plugin = line.split(pattern=separator) 23 | if plugin.length == 2 24 | podname = plugin[0].strip() 25 | path = plugin[1].strip() 26 | podpath = File.expand_path("#{path}", file_abs_path) 27 | pods_ary.push({:name => podname, :path => podpath}); 28 | else 29 | puts "Invalid plugin specification: #{line}" 30 | end 31 | } 32 | return pods_ary 33 | end 34 | 35 | target 'Runner' do 36 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 37 | # referring to absolute paths on developers' machines. 38 | system('rm -rf .symlinks') 39 | system('mkdir -p .symlinks/plugins') 40 | 41 | # Flutter Pods 42 | generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') 43 | if generated_xcode_build_settings.empty? 44 | puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." 45 | end 46 | generated_xcode_build_settings.map { |p| 47 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR' 48 | symlink = File.join('.symlinks', 'flutter') 49 | File.symlink(File.dirname(p[:path]), symlink) 50 | pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) 51 | end 52 | } 53 | 54 | # Plugin Pods 55 | plugin_pods = parse_KV_file('../.flutter-plugins') 56 | plugin_pods.map { |p| 57 | symlink = File.join('.symlinks', 'plugins', p[:name]) 58 | File.symlink(p[:path], symlink) 59 | pod p[:name], :path => File.join(symlink, 'ios') 60 | } 61 | end 62 | 63 | post_install do |installer| 64 | installer.pods_project.targets.each do |target| 65 | target.build_configurations.each do |config| 66 | config.build_settings['ENABLE_BITCODE'] = 'NO' 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /ios/Pods/Headers/Private/FMDB/FMDB.h: -------------------------------------------------------------------------------- 1 | ../../../FMDB/src/fmdb/FMDB.h -------------------------------------------------------------------------------- /ios/Pods/Headers/Private/FMDB/FMDatabase.h: -------------------------------------------------------------------------------- 1 | ../../../FMDB/src/fmdb/FMDatabase.h -------------------------------------------------------------------------------- /ios/Pods/Headers/Private/FMDB/FMDatabaseAdditions.h: -------------------------------------------------------------------------------- 1 | ../../../FMDB/src/fmdb/FMDatabaseAdditions.h -------------------------------------------------------------------------------- /ios/Pods/Headers/Private/FMDB/FMDatabasePool.h: -------------------------------------------------------------------------------- 1 | ../../../FMDB/src/fmdb/FMDatabasePool.h -------------------------------------------------------------------------------- /ios/Pods/Headers/Private/FMDB/FMDatabaseQueue.h: -------------------------------------------------------------------------------- 1 | ../../../FMDB/src/fmdb/FMDatabaseQueue.h -------------------------------------------------------------------------------- /ios/Pods/Headers/Private/FMDB/FMResultSet.h: -------------------------------------------------------------------------------- 1 | ../../../FMDB/src/fmdb/FMResultSet.h -------------------------------------------------------------------------------- /ios/Pods/Headers/Private/JPush/JPUSHService.h: -------------------------------------------------------------------------------- 1 | ../../../JPush/JPUSHService.h -------------------------------------------------------------------------------- /ios/Pods/Headers/Private/path_provider/PathProviderPlugin.h: -------------------------------------------------------------------------------- 1 | ../../../../.symlinks/plugins/path_provider/ios/Classes/PathProviderPlugin.h -------------------------------------------------------------------------------- /ios/Pods/Headers/Private/shared_preferences/SharedPreferencesPlugin.h: -------------------------------------------------------------------------------- 1 | ../../../../.symlinks/plugins/shared_preferences/ios/Classes/SharedPreferencesPlugin.h -------------------------------------------------------------------------------- /ios/Pods/Headers/Private/sqflite/SqfliteOperation.h: -------------------------------------------------------------------------------- 1 | ../../../../.symlinks/plugins/sqflite/ios/Classes/SqfliteOperation.h -------------------------------------------------------------------------------- /ios/Pods/Headers/Private/sqflite/SqflitePlugin.h: -------------------------------------------------------------------------------- 1 | ../../../../.symlinks/plugins/sqflite/ios/Classes/SqflitePlugin.h -------------------------------------------------------------------------------- /ios/Pods/Headers/Public/FMDB/FMDB.h: -------------------------------------------------------------------------------- 1 | ../../../FMDB/src/fmdb/FMDB.h -------------------------------------------------------------------------------- /ios/Pods/Headers/Public/FMDB/FMDatabase.h: -------------------------------------------------------------------------------- 1 | ../../../FMDB/src/fmdb/FMDatabase.h -------------------------------------------------------------------------------- /ios/Pods/Headers/Public/FMDB/FMDatabaseAdditions.h: -------------------------------------------------------------------------------- 1 | ../../../FMDB/src/fmdb/FMDatabaseAdditions.h -------------------------------------------------------------------------------- /ios/Pods/Headers/Public/FMDB/FMDatabasePool.h: -------------------------------------------------------------------------------- 1 | ../../../FMDB/src/fmdb/FMDatabasePool.h -------------------------------------------------------------------------------- /ios/Pods/Headers/Public/FMDB/FMDatabaseQueue.h: -------------------------------------------------------------------------------- 1 | ../../../FMDB/src/fmdb/FMDatabaseQueue.h -------------------------------------------------------------------------------- /ios/Pods/Headers/Public/FMDB/FMResultSet.h: -------------------------------------------------------------------------------- 1 | ../../../FMDB/src/fmdb/FMResultSet.h -------------------------------------------------------------------------------- /ios/Pods/Headers/Public/JPush/JPUSHService.h: -------------------------------------------------------------------------------- 1 | ../../../JPush/JPUSHService.h -------------------------------------------------------------------------------- /ios/Pods/Headers/Public/path_provider/PathProviderPlugin.h: -------------------------------------------------------------------------------- 1 | ../../../../.symlinks/plugins/path_provider/ios/Classes/PathProviderPlugin.h -------------------------------------------------------------------------------- /ios/Pods/Headers/Public/shared_preferences/SharedPreferencesPlugin.h: -------------------------------------------------------------------------------- 1 | ../../../../.symlinks/plugins/shared_preferences/ios/Classes/SharedPreferencesPlugin.h -------------------------------------------------------------------------------- /ios/Pods/Headers/Public/sqflite/SqfliteOperation.h: -------------------------------------------------------------------------------- 1 | ../../../../.symlinks/plugins/sqflite/ios/Classes/SqfliteOperation.h -------------------------------------------------------------------------------- /ios/Pods/Headers/Public/sqflite/SqflitePlugin.h: -------------------------------------------------------------------------------- 1 | ../../../../.symlinks/plugins/sqflite/ios/Classes/SqflitePlugin.h -------------------------------------------------------------------------------- /ios/Pods/Local Podspecs/flutter_webview_plugin.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flutter_webview_plugin", 3 | "version": "0.0.1", 4 | "summary": "A new flutter plugin project.", 5 | "description": "A new flutter plugin project.", 6 | "homepage": "https://github.com/dart-flitter/flutter_webview_plugin", 7 | "license": { 8 | "file": "../LICENSE" 9 | }, 10 | "authors": { 11 | "Your Company": "email@example.com" 12 | }, 13 | "source": { 14 | "path": "." 15 | }, 16 | "source_files": "Classes/**/*", 17 | "public_header_files": "Classes/**/*.h", 18 | "dependencies": { 19 | "Flutter": [ 20 | 21 | ] 22 | }, 23 | "platforms": { 24 | "ios": "8.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ios/Pods/Local Podspecs/jpush_flutter.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jpush_flutter", 3 | "version": "0.0.2", 4 | "summary": "A new flutter plugin project.", 5 | "description": "A new flutter plugin project.", 6 | "homepage": "http://example.com", 7 | "license": { 8 | "file": "../LICENSE" 9 | }, 10 | "authors": { 11 | "huminios": "380108184@qq.com" 12 | }, 13 | "source": { 14 | "path": "." 15 | }, 16 | "source_files": "Classes/**/*", 17 | "public_header_files": "Classes/**/*.h", 18 | "dependencies": { 19 | "Flutter": [ 20 | 21 | ], 22 | "JPush": [ 23 | 24 | ] 25 | }, 26 | "platforms": { 27 | "ios": "8.0" 28 | }, 29 | "static_framework": true 30 | } 31 | -------------------------------------------------------------------------------- /ios/Pods/Local Podspecs/path_provider.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "path_provider", 3 | "version": "0.0.1", 4 | "summary": "A Flutter plugin for getting commonly used locations on the filesystem.", 5 | "description": "A Flutter plugin for getting commonly used locations on the filesystem.", 6 | "homepage": "https://github.com/flutter/plugins/tree/master/packages/path_provider", 7 | "license": { 8 | "file": "../LICENSE" 9 | }, 10 | "authors": { 11 | "Flutter Team": "flutter-dev@googlegroups.com" 12 | }, 13 | "source": { 14 | "path": "." 15 | }, 16 | "source_files": "Classes/**/*", 17 | "public_header_files": "Classes/**/*.h", 18 | "dependencies": { 19 | "Flutter": [ 20 | 21 | ] 22 | }, 23 | "platforms": { 24 | "ios": "8.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ios/Pods/Local Podspecs/shared_preferences.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shared_preferences", 3 | "version": "0.0.1", 4 | "summary": "A Flutter plugin for reading and writing simple key-value pairs.", 5 | "description": "A Flutter plugin for reading and writing simple key-value pairs.", 6 | "homepage": "https://github.com/flutter/plugins/tree/master/packages/shared_preferences", 7 | "license": { 8 | "file": "../LICENSE" 9 | }, 10 | "authors": { 11 | "Flutter Team": "flutter-dev@googlegroups.com" 12 | }, 13 | "source": { 14 | "path": "." 15 | }, 16 | "source_files": "Classes/**/*", 17 | "public_header_files": "Classes/**/*.h", 18 | "dependencies": { 19 | "Flutter": [ 20 | 21 | ] 22 | }, 23 | "platforms": { 24 | "ios": "8.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ios/Pods/Local Podspecs/sqflite.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sqflite", 3 | "version": "0.0.1", 4 | "summary": "A new flutter plugin project.", 5 | "description": "A new flutter plugin project.", 6 | "homepage": "http://example.com", 7 | "license": { 8 | "file": "../LICENSE" 9 | }, 10 | "authors": { 11 | "Your Company": "email@example.com" 12 | }, 13 | "source": { 14 | "path": "." 15 | }, 16 | "source_files": "Classes/**/*", 17 | "public_header_files": "Classes/**/*.h", 18 | "dependencies": { 19 | "Flutter": [ 20 | 21 | ], 22 | "FMDB": [ 23 | "~> 2.7.2" 24 | ] 25 | }, 26 | "platforms": { 27 | "ios": "8.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ios/Pods/Pods.xcodeproj/xcuserdata/yuan.xcuserdatad/xcschemes/FMDB.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 45 | 46 | 52 | 53 | 55 | 56 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /ios/Pods/Pods.xcodeproj/xcuserdata/yuan.xcuserdatad/xcschemes/flutter_webview_plugin.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 45 | 46 | 52 | 53 | 55 | 56 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /ios/Pods/Pods.xcodeproj/xcuserdata/yuan.xcuserdatad/xcschemes/jpush_flutter.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 45 | 46 | 52 | 53 | 55 | 56 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /ios/Pods/Pods.xcodeproj/xcuserdata/yuan.xcuserdatad/xcschemes/path_provider.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 45 | 46 | 52 | 53 | 55 | 56 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /ios/Pods/Target Support Files/FMDB/FMDB-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_FMDB : NSObject 3 | @end 4 | @implementation PodsDummy_FMDB 5 | @end 6 | -------------------------------------------------------------------------------- /ios/Pods/Target Support Files/FMDB/FMDB-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /ios/Pods/Target Support Files/Pods-Runner/Pods-Runner-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_Runner : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_Runner 5 | @end 6 | -------------------------------------------------------------------------------- /ios/Pods/Target Support Files/path_provider/path_provider-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_path_provider : NSObject 3 | @end 4 | @implementation PodsDummy_path_provider 5 | @end 6 | -------------------------------------------------------------------------------- /ios/Pods/Target Support Files/path_provider/path_provider-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /ios/Pods/Target Support Files/shared_preferences/shared_preferences-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_shared_preferences : NSObject 3 | @end 4 | @implementation PodsDummy_shared_preferences 5 | @end 6 | -------------------------------------------------------------------------------- /ios/Pods/Target Support Files/shared_preferences/shared_preferences-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /ios/Pods/Target Support Files/sqflite/sqflite-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_sqflite : NSObject 3 | @end 4 | @implementation PodsDummy_sqflite 5 | @end 6 | -------------------------------------------------------------------------------- /ios/Pods/Target Support Files/sqflite/sqflite-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Original 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "icon_dique_40x40@2x-1.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "icon_dique_60x60-1.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "icon_dique_29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "icon_dique_29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "icon_dique_87x87@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "icon_dique_80x80@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "icon_dique_60x60@2x-1.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "icon_dique_60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "icon_dique_60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "idiom" : "ipad", 59 | "size" : "20x20", 60 | "scale" : "1x" 61 | }, 62 | { 63 | "idiom" : "ipad", 64 | "size" : "20x20", 65 | "scale" : "2x" 66 | }, 67 | { 68 | "idiom" : "ipad", 69 | "size" : "29x29", 70 | "scale" : "1x" 71 | }, 72 | { 73 | "idiom" : "ipad", 74 | "size" : "29x29", 75 | "scale" : "2x" 76 | }, 77 | { 78 | "idiom" : "ipad", 79 | "size" : "40x40", 80 | "scale" : "1x" 81 | }, 82 | { 83 | "idiom" : "ipad", 84 | "size" : "40x40", 85 | "scale" : "2x" 86 | }, 87 | { 88 | "idiom" : "ipad", 89 | "size" : "76x76", 90 | "scale" : "1x" 91 | }, 92 | { 93 | "idiom" : "ipad", 94 | "size" : "76x76", 95 | "scale" : "2x" 96 | }, 97 | { 98 | "idiom" : "ipad", 99 | "size" : "83.5x83.5", 100 | "scale" : "2x" 101 | }, 102 | { 103 | "size" : "1024x1024", 104 | "idiom" : "ios-marketing", 105 | "filename" : "icon_dique_1024x1024.png", 106 | "scale" : "1x" 107 | } 108 | ], 109 | "info" : { 110 | "version" : 1, 111 | "author" : "xcode" 112 | } 113 | } -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_dique_1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_dique_1024x1024.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_dique_29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_dique_29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_dique_29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_dique_29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_dique_40x40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_dique_40x40@2x-1.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_dique_60x60-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_dique_60x60-1.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_dique_60x60@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_dique_60x60@2x-1.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_dique_60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_dique_60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_dique_60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_dique_60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_dique_80x80@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_dique_80x80@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_dique_87x87@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_dique_87x87@3x.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/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/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 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /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/GeneratedPluginRegistrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | #ifndef GeneratedPluginRegistrant_h 6 | #define GeneratedPluginRegistrant_h 7 | 8 | #import 9 | 10 | @interface GeneratedPluginRegistrant : NSObject 11 | + (void)registerWithRegistry:(NSObject*)registry; 12 | @end 13 | 14 | #endif /* GeneratedPluginRegistrant_h */ 15 | -------------------------------------------------------------------------------- /ios/Runner/GeneratedPluginRegistrant.m: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | #import "GeneratedPluginRegistrant.h" 6 | #import 7 | #import 8 | #import 9 | #import 10 | #import 11 | #import 12 | #import 13 | #import 14 | #import 15 | 16 | @implementation GeneratedPluginRegistrant 17 | 18 | + (void)registerWithRegistry:(NSObject*)registry { 19 | [FlutterWebviewPlugin registerWithRegistrar:[registry registrarForPlugin:@"FlutterWebviewPlugin"]]; 20 | [JPushPlugin registerWithRegistrar:[registry registrarForPlugin:@"JPushPlugin"]]; 21 | [OpenFilePlugin registerWithRegistrar:[registry registrarForPlugin:@"OpenFilePlugin"]]; 22 | [FLTPackageInfoPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTPackageInfoPlugin"]]; 23 | [FLTPathProviderPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTPathProviderPlugin"]]; 24 | [FLTSharedPreferencesPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTSharedPreferencesPlugin"]]; 25 | [SqflitePlugin registerWithRegistrar:[registry registrarForPlugin:@"SqflitePlugin"]]; 26 | [FLTUrlLauncherPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTUrlLauncherPlugin"]]; 27 | [FLTWebViewFlutterPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTWebViewFlutterPlugin"]]; 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleAllowMixedLocalizations 6 | 7 | ITSAppUsesNonExemptEncryption 8 | 9 | CFBundleLocalizations 10 | 11 | zh_CN 12 | 13 | CFBundleDevelopmentRegion 14 | en 15 | CFBundleExecutable 16 | $(EXECUTABLE_NAME) 17 | CFBundleIdentifier 18 | $(PRODUCT_BUNDLE_IDENTIFIER) 19 | CFBundleInfoDictionaryVersion 20 | 6.0 21 | CFBundleName 22 | 滴雀 23 | CFBundlePackageType 24 | APPL 25 | CFBundleShortVersionString 26 | 1.0.1 27 | CFBundleSignature 28 | ???? 29 | CFBundleVersion 30 | 1 31 | LSRequiresIPhoneOS 32 | 33 | NSAppTransportSecurity 34 | 35 | NSAllowsArbitraryLoads 36 | 37 | 38 | UILaunchStoryboardName 39 | LaunchScreen 40 | UIMainStoryboardFile 41 | Main 42 | UISupportedInterfaceOrientations 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | UISupportedInterfaceOrientations~ipad 49 | 50 | UIInterfaceOrientationPortrait 51 | UIInterfaceOrientationPortraitUpsideDown 52 | UIInterfaceOrientationLandscapeLeft 53 | UIInterfaceOrientationLandscapeRight 54 | 55 | UIViewControllerBasedStatusBarAppearance 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /ios/Runner/Runner.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ios/Runner/splash_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEMessage/dique/8c810e922efc9aa3c139e95bfc589282b4e8e9b4/ios/Runner/splash_screen.png -------------------------------------------------------------------------------- /ios/ServiceDefinitions.json: -------------------------------------------------------------------------------- 1 | {"services":[]} -------------------------------------------------------------------------------- /lib/json/check_update_bean.dart: -------------------------------------------------------------------------------- 1 | class CheckUpdateBean { 2 | 3 | /** 4 | * android : {"version":"1.0.0","content":"1.新增xxx\n2.优化xxx","isForceUpdate":0,"updateTime":"2019-04-28","downloadLink":"https://work-1256696029.cos.ap-guangzhou.myqcloud.com/apk_download/dique.apk"} 5 | * ios : {"version":"1.0.0","content":"1.新增xxx\n2.优化xxx","updateTime":"2019-06-12","isForceUpdate":0,"downloadLink":"https://apps.apple.com/cn/app/%E6%BB%B4%E9%9B%80/id1466759938"} 6 | */ 7 | 8 | AndroidBean android; 9 | IosBean ios; 10 | 11 | static CheckUpdateBean fromMap(Map map) { 12 | CheckUpdateBean check_update_bean = new CheckUpdateBean(); 13 | check_update_bean.android = AndroidBean.fromMap(map['android']); 14 | check_update_bean.ios = IosBean.fromMap(map['ios']); 15 | return check_update_bean; 16 | } 17 | 18 | static List fromMapList(dynamic mapList) { 19 | List list = new List(mapList.length); 20 | for (int i = 0; i < mapList.length; i++) { 21 | list[i] = fromMap(mapList[i]); 22 | } 23 | return list; 24 | } 25 | 26 | } 27 | 28 | class AndroidBean { 29 | 30 | /** 31 | * version : "1.0.0" 32 | * content : "1.新增xxx\n2.优化xxx" 33 | * updateTime : "2019-04-28" 34 | * downloadLink : "https://work-1256696029.cos.ap-guangzhou.myqcloud.com/apk_download/dique.apk" 35 | * isForceUpdate : 0 36 | */ 37 | 38 | String version; 39 | String content; 40 | String updateTime; 41 | String downloadLink; 42 | int isForceUpdate; 43 | 44 | static AndroidBean fromMap(Map map) { 45 | AndroidBean androidBean = new AndroidBean(); 46 | androidBean.version = map['version']; 47 | androidBean.content = map['content']; 48 | androidBean.updateTime = map['updateTime']; 49 | androidBean.downloadLink = map['downloadLink']; 50 | androidBean.isForceUpdate = map['isForceUpdate']; 51 | return androidBean; 52 | } 53 | 54 | static List fromMapList(dynamic mapList) { 55 | List list = new List(mapList.length); 56 | for (int i = 0; i < mapList.length; i++) { 57 | list[i] = fromMap(mapList[i]); 58 | } 59 | return list; 60 | } 61 | } 62 | 63 | class IosBean { 64 | 65 | /** 66 | * version : "1.0.0" 67 | * content : "1.新增xxx\n2.优化xxx" 68 | * updateTime : "2019-06-12" 69 | * downloadLink : "https://apps.apple.com/cn/app/%E6%BB%B4%E9%9B%80/id1466759938" 70 | * isForceUpdate : 0 71 | */ 72 | 73 | String version; 74 | String content; 75 | String updateTime; 76 | String downloadLink; 77 | int isForceUpdate; 78 | 79 | static IosBean fromMap(Map map) { 80 | IosBean iosBean = new IosBean(); 81 | iosBean.version = map['version']; 82 | iosBean.content = map['content']; 83 | iosBean.updateTime = map['updateTime']; 84 | iosBean.downloadLink = map['downloadLink']; 85 | iosBean.isForceUpdate = map['isForceUpdate']; 86 | return iosBean; 87 | } 88 | 89 | static List fromMapList(dynamic mapList) { 90 | List list = new List(mapList.length); 91 | for (int i = 0; i < mapList.length; i++) { 92 | list[i] = fromMap(mapList[i]); 93 | } 94 | return list; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /lib/json/jpush_bean.dart: -------------------------------------------------------------------------------- 1 | class JPushBean { 2 | 3 | /** 4 | * alert : "推送内容" 5 | * title : "文章标题" 6 | * extras : {"cn.jpush.android.ALERT_TYPE":-1,"cn.jpush.android.NOTIFICATION_ID":536281999,"cn.jpush.android.MSG_ID":58546804879130989,"cn.jpush.android.ALERT":"推送内容","cn.jpush.android.EXTRA":{"nameSpace":"deepexi-serverless/dev-doc","slug":"engxg1"}} 7 | */ 8 | 9 | String alert; 10 | String title; 11 | var extras; 12 | EXTRABean extraBean; 13 | 14 | static JPushBean fromMap(Map map) { 15 | JPushBean jpush_bean = new JPushBean(); 16 | jpush_bean.alert = map['alert']; 17 | jpush_bean.title = map['title']; 18 | jpush_bean.extras = map['extras']; 19 | var data = jpush_bean.extras['cn.jpush.android.EXTRA']; 20 | jpush_bean.extraBean = EXTRABean.fromMap(data); 21 | return jpush_bean; 22 | } 23 | 24 | static List fromMapList(dynamic mapList) { 25 | List list = new List(mapList.length); 26 | for (int i = 0; i < mapList.length; i++) { 27 | list[i] = fromMap(mapList[i]); 28 | } 29 | return list; 30 | } 31 | 32 | } 33 | 34 | class ExtrasBean { 35 | 36 | /** 37 | * cn.jpush.android.ALERT : "推送内容" 38 | * cn.jpush.android.ALERT_TYPE : -1 39 | * cn.jpush.android.NOTIFICATION_ID : 536281999 40 | * cn.jpush.android.MSG_ID : 58546804879130989 41 | * cn.jpush.android.EXTRA : {"nameSpace":"deepexi-serverless/dev-doc","slug":"engxg1"} 42 | */ 43 | 44 | var extra; 45 | 46 | static ExtrasBean fromMap(Map map) { 47 | ExtrasBean extrasBean = new ExtrasBean(); 48 | 49 | extrasBean.extra = EXTRABean.fromMap(map['cn.jpush.android.EXTRA']); 50 | return extrasBean; 51 | } 52 | 53 | static List fromMapList(dynamic mapList) { 54 | List list = new List(mapList.length); 55 | for (int i = 0; i < mapList.length; i++) { 56 | list[i] = fromMap(mapList[i]); 57 | } 58 | return list; 59 | } 60 | } 61 | 62 | class EXTRABean { 63 | 64 | /** 65 | * nameSpace : "deepexi-serverless/dev-doc" 66 | * slug : "engxg1" 67 | */ 68 | 69 | String nameSpace; 70 | String slug; 71 | 72 | static EXTRABean fromMap(Map map) { 73 | EXTRABean bean = new EXTRABean(); 74 | bean.nameSpace = map['nameSpace']; 75 | bean.slug = map['slug']; 76 | return bean; 77 | } 78 | 79 | } 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /lib/json/local_repolist_bean.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | class LocalRepoListBean { 4 | 5 | /** 6 | * teamName : "teamName" 7 | * repoName : "repoName" 8 | * groupLoginId : "groupLoginId" 9 | * groupAvatarUrl : "groupAvatarUrl" 10 | */ 11 | 12 | String teamName; 13 | String repoName; 14 | String groupLoginId; 15 | String groupAvatarUrl; 16 | String nameSpace; 17 | List articleBeans; 18 | 19 | static LocalRepoListBean fromMap(Map map) { 20 | LocalRepoListBean local_repolist_bean = new LocalRepoListBean(); 21 | local_repolist_bean.teamName = map['teamName']; 22 | local_repolist_bean.repoName = map['repoName']; 23 | local_repolist_bean.groupLoginId = map['groupLoginId']; 24 | local_repolist_bean.groupAvatarUrl = map['groupAvatarUrl']; 25 | local_repolist_bean.nameSpace = map['nameSpace']; 26 | local_repolist_bean.articleBeans = ArticleBean.fromMapList(map['articles']); 27 | return local_repolist_bean; 28 | } 29 | 30 | static List fromMapList(dynamic mapList) { 31 | List list = new List(mapList.length); 32 | for (int i = 0; i < mapList.length; i++) { 33 | list[i] = fromMap(mapList[i]); 34 | } 35 | return list; 36 | } 37 | 38 | LocalRepoListBean({this.teamName, this.repoName, this.groupLoginId, 39 | this.groupAvatarUrl, this.nameSpace, this.articleBeans}); 40 | 41 | static List fromStringList(List repoList){ 42 | List list = new List(repoList.length); 43 | List newList = List.from(list); 44 | for (int i = 0; i < repoList.length; i++) { 45 | var data = jsonDecode(repoList[i]); 46 | newList[i] = fromMap(data); 47 | } 48 | return newList; 49 | } 50 | 51 | static List fromBeanList(List beans){ 52 | List list = new List(beans.length); 53 | List newList = List.from(list); 54 | for (int i = 0; i < beans.length; i++) { 55 | var data = toJson(beans[i]); 56 | newList[i] = jsonEncode(data); 57 | } 58 | return newList; 59 | } 60 | 61 | static bool containsRepoByNameSpace(LocalRepoListBean bean, List beans){ 62 | bool isContain = false; 63 | for(LocalRepoListBean repo in beans){ 64 | if(bean.nameSpace == repo.nameSpace){ 65 | isContain = true; 66 | } 67 | } 68 | return isContain; 69 | } 70 | 71 | // static bool containsRepoByName(LocalRepoListBean bean, List beans){ 72 | // bool isContain = false; 73 | // for(LocalRepoListBean repo in beans){ 74 | // print("仓库名字:${bean.nameSpace} ____ 列表${repo.nameSpace}"); 75 | // if(bean.repoName == repo.repoName){ 76 | // isContain = true; 77 | // } 78 | // } 79 | // return isContain; 80 | // } 81 | 82 | static bool containArticle(ArticleBean bean, List beans){ 83 | bool isContain = false; 84 | for(ArticleBean article in beans){ 85 | if(bean.articleSlug == article.articleSlug){ 86 | isContain = true; 87 | } 88 | } 89 | return isContain; 90 | } 91 | 92 | 93 | static Map toJson(LocalRepoListBean bean) =>{ 94 | 'teamName': bean.teamName, 95 | 'repoName': bean.repoName, 96 | 'groupLoginId': bean.groupLoginId, 97 | 'groupAvatarUrl': bean.groupAvatarUrl, 98 | 'nameSpace': bean.nameSpace, 99 | 'articles': ArticleBean.toJsonList(bean.articleBeans), 100 | }; 101 | 102 | 103 | } 104 | 105 | 106 | class ArticleBean{ 107 | String articleTitle; 108 | String articleSlug; 109 | String nameSpace; 110 | 111 | 112 | ArticleBean({this.articleTitle, this.articleSlug, this.nameSpace}); 113 | 114 | static ArticleBean fromMap(Map map) { 115 | ArticleBean articleBean = new ArticleBean(); 116 | articleBean.articleTitle = map['articleTitle']; 117 | articleBean.articleSlug = map['articleSlug']; 118 | articleBean.nameSpace = map['nameSpace']; 119 | return articleBean; 120 | } 121 | 122 | 123 | static List fromMapList(dynamic mapList) { 124 | List list = new List(mapList.length); 125 | for (int i = 0; i < mapList.length; i++) { 126 | list[i] = fromMap(mapList[i]); 127 | } 128 | return list; 129 | } 130 | 131 | static Map toJson(ArticleBean bean) =>{ 132 | 'articleTitle': bean.articleTitle, 133 | 'articleSlug': bean.articleSlug, 134 | 'nameSpace': bean.nameSpace, 135 | }; 136 | 137 | static List toJsonList(List beans){ 138 | List list = new List(beans.length); 139 | for (int i = 0; i < list.length; i++) { 140 | list[i] = toJson(beans[i]); 141 | } 142 | return list; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /lib/json/repo_directory_bean.dart: -------------------------------------------------------------------------------- 1 | class RepoDirectoryBean { 2 | 3 | /** 4 | * data : [{"title":"目标方向","slug":"cs0drm","depth":1},{"title":"分工协作","slug":"kqdgoa","depth":1},{"title":"小程序","slug":"#","depth":1},{"title":"握手计划一期开发总结","slug":"gmtoym","depth":2},{"title":"踩坑记录","slug":"aqr6fb","depth":2},{"title":"app","slug":"#","depth":1},{"title":"移动端入职培训","slug":"zwnmw8","depth":2},{"title":"新手入门(flutter)","slug":"xbuua2","depth":2},{"title":"状态管理之Stream","slug":"uqtp1e","depth":2},{"title":"使用Flare创建2D矢量动画","slug":"engxg1","depth":2},{"title":"核心技术点","slug":"sygy2g","depth":2},{"title":"Flutter开发记录","slug":"lw5wkh","depth":2},{"title":"移动端-推送","slug":"dc74gz","depth":2},{"title":"测试排版","slug":"nsnun2","depth":2},{"title":"Web","slug":"#","depth":1},{"title":"Mobile Safari 下 input 引起的问题总结","slug":"fe4iqc","depth":2},{"title":"我们的Github Workflow","slug":"qcg9x0","depth":1}] 5 | */ 6 | 7 | List data; 8 | 9 | static RepoDirectoryBean fromMap(Map map) { 10 | RepoDirectoryBean repo_directory_bean = new RepoDirectoryBean(); 11 | repo_directory_bean.data = RepoDirListBean.fromMapList(map['data']); 12 | return repo_directory_bean; 13 | } 14 | 15 | static List fromMapList(dynamic mapList) { 16 | List list = new List(mapList.length); 17 | for (int i = 0; i < mapList.length; i++) { 18 | list[i] = fromMap(mapList[i]); 19 | } 20 | return list; 21 | } 22 | 23 | } 24 | 25 | class RepoDirListBean { 26 | 27 | /** 28 | * title : "目标方向" 29 | * slug : "cs0drm" 30 | * depth : 1 31 | */ 32 | 33 | String title; 34 | String slug; 35 | int depth; 36 | 37 | static RepoDirListBean fromMap(Map map) { 38 | RepoDirListBean dataListBean = new RepoDirListBean(); 39 | dataListBean.title = map['title']; 40 | dataListBean.slug = map['slug']; 41 | dataListBean.depth = map['depth']; 42 | return dataListBean; 43 | } 44 | 45 | static List fromMapList(dynamic mapList) { 46 | List list = new List(mapList.length); 47 | for (int i = 0; i < mapList.length; i++) { 48 | list[i] = fromMap(mapList[i]); 49 | } 50 | return list; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/json/user_groups_bean.dart: -------------------------------------------------------------------------------- 1 | class UserGroupsBean { 2 | 3 | /** 4 | * data : [{"id":210644,"login":"deepexi-company","name":"滴普科技","avatar_url":"https://cdn.nlark.com/yuque/0/2018/png/210635/1542683667591-aae0cf37-d163-439c-b1f4-289231a4df7c.png","large_avatar_url":"https://cdn.nlark.com/yuque/0/2018/png/210635/1542683667591-aae0cf37-d163-439c-b1f4-289231a4df7c.png?x-oss-process=image/resize,m_fill,w_320,h_320","medium_avatar_url":"https://cdn.nlark.com/yuque/0/2018/png/210635/1542683667591-aae0cf37-d163-439c-b1f4-289231a4df7c.png?x-oss-process=image/resize,m_fill,w_160,h_160","small_avatar_url":"https://cdn.nlark.com/yuque/0/2018/png/210635/1542683667591-aae0cf37-d163-439c-b1f4-289231a4df7c.png?x-oss-process=image/resize,m_fill,w_80,h_80","books_count":26,"public_books_count":0,"topics_count":0,"public_topics_count":0,"members_count":315,"public":0,"description":"公司全员","created_at":"2018-11-20T03:14:33.000Z","updated_at":"2019-05-14T06:39:45.000Z","_serializer":"v2.group"},{"id":313927,"login":"deepexi-serverless","name":"DEEPEXI Serverless","avatar_url":"https://cdn.nlark.com/yuque/0/2019/png/160590/1556430655844-avatar/51841d71-b69f-46fd-9a42-3a71d717922f.png","large_avatar_url":"https://cdn.nlark.com/yuque/0/2019/png/160590/1556430655844-avatar/51841d71-b69f-46fd-9a42-3a71d717922f.png?x-oss-process=image/resize,m_fill,w_320,h_320","medium_avatar_url":"https://cdn.nlark.com/yuque/0/2019/png/160590/1556430655844-avatar/51841d71-b69f-46fd-9a42-3a71d717922f.png?x-oss-process=image/resize,m_fill,w_160,h_160","small_avatar_url":"https://cdn.nlark.com/yuque/0/2019/png/160590/1556430655844-avatar/51841d71-b69f-46fd-9a42-3a71d717922f.png?x-oss-process=image/resize,m_fill,w_80,h_80","books_count":4,"public_books_count":0,"topics_count":0,"public_topics_count":0,"members_count":10,"public":0,"description":null,"created_at":"2019-04-10T12:11:58.000Z","updated_at":"2019-05-09T02:10:57.000Z","_serializer":"v2.group"}] 5 | */ 6 | 7 | List data; 8 | 9 | static UserGroupsBean fromMap(Map map) { 10 | UserGroupsBean user_groups_bean = new UserGroupsBean(); 11 | user_groups_bean.data = UserGroupListBean.fromMapList(map['data']); 12 | return user_groups_bean; 13 | } 14 | 15 | static List fromMapList(dynamic mapList) { 16 | List list = new List(mapList.length); 17 | for (int i = 0; i < mapList.length; i++) { 18 | list[i] = fromMap(mapList[i]); 19 | } 20 | return list; 21 | } 22 | 23 | } 24 | 25 | class UserGroupListBean { 26 | 27 | /** 28 | * login : "deepexi-company" 29 | * name : "滴普科技" 30 | * avatar_url : "https://cdn.nlark.com/yuque/0/2018/png/210635/1542683667591-aae0cf37-d163-439c-b1f4-289231a4df7c.png" 31 | * large_avatar_url : "https://cdn.nlark.com/yuque/0/2018/png/210635/1542683667591-aae0cf37-d163-439c-b1f4-289231a4df7c.png?x-oss-process=image/resize,m_fill,w_320,h_320" 32 | * medium_avatar_url : "https://cdn.nlark.com/yuque/0/2018/png/210635/1542683667591-aae0cf37-d163-439c-b1f4-289231a4df7c.png?x-oss-process=image/resize,m_fill,w_160,h_160" 33 | * small_avatar_url : "https://cdn.nlark.com/yuque/0/2018/png/210635/1542683667591-aae0cf37-d163-439c-b1f4-289231a4df7c.png?x-oss-process=image/resize,m_fill,w_80,h_80" 34 | * description : "公司全员" 35 | * created_at : "2018-11-20T03:14:33.000Z" 36 | * updated_at : "2019-05-14T06:39:45.000Z" 37 | * _serializer : "v2.group" 38 | * id : 210644 39 | * books_count : 26 40 | * public_books_count : 0 41 | * topics_count : 0 42 | * public_topics_count : 0 43 | * members_count : 315 44 | * public : 0 45 | */ 46 | 47 | String login; 48 | String name; 49 | String avatar_url; 50 | String large_avatar_url; 51 | String medium_avatar_url; 52 | String small_avatar_url; 53 | String description; 54 | String created_at; 55 | String updated_at; 56 | String _serializer; 57 | int id; 58 | int books_count; 59 | int public_books_count; 60 | int topics_count; 61 | int public_topics_count; 62 | int members_count; 63 | int public; 64 | 65 | static UserGroupListBean fromMap(Map map) { 66 | UserGroupListBean dataListBean = new UserGroupListBean(); 67 | dataListBean.login = map['login']; 68 | dataListBean.name = map['name']; 69 | dataListBean.avatar_url = map['avatar_url']; 70 | dataListBean.large_avatar_url = map['large_avatar_url']; 71 | dataListBean.medium_avatar_url = map['medium_avatar_url']; 72 | dataListBean.small_avatar_url = map['small_avatar_url']; 73 | dataListBean.description = map['description']; 74 | dataListBean.created_at = map['created_at']; 75 | dataListBean.updated_at = map['updated_at']; 76 | dataListBean._serializer = map['_serializer']; 77 | dataListBean.id = map['id']; 78 | dataListBean.books_count = map['books_count']; 79 | dataListBean.public_books_count = map['public_books_count']; 80 | dataListBean.topics_count = map['topics_count']; 81 | dataListBean.public_topics_count = map['public_topics_count']; 82 | dataListBean.members_count = map['members_count']; 83 | dataListBean.public = map['public']; 84 | return dataListBean; 85 | } 86 | 87 | static List fromMapList(dynamic mapList) { 88 | List list = new List(mapList.length); 89 | for (int i = 0; i < mapList.length; i++) { 90 | list[i] = fromMap(mapList[i]); 91 | } 92 | return list; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lib/json/user_info_bean.dart: -------------------------------------------------------------------------------- 1 | class UserInfoBean { 2 | 3 | /** 4 | * data : {"id":274963,"type":"User","space_id":0,"account_id":136961,"login":"newfish","name":"李子晨(安卓)","avatar_url":"https://cdn.nlark.com/yuque/0/2019/png/274963/1557302684519-avatar/d4247287-5306-455d-8162-9b9411416870.png","large_avatar_url":"https://cdn.nlark.com/yuque/0/2019/png/274963/1557302684519-avatar/d4247287-5306-455d-8162-9b9411416870.png?x-oss-process=image/resize,m_fill,w_320,h_320","medium_avatar_url":"https://cdn.nlark.com/yuque/0/2019/png/274963/1557302684519-avatar/d4247287-5306-455d-8162-9b9411416870.png?x-oss-process=image/resize,m_fill,w_160,h_160","small_avatar_url":"https://cdn.nlark.com/yuque/0/2019/png/274963/1557302684519-avatar/d4247287-5306-455d-8162-9b9411416870.png?x-oss-process=image/resize,m_fill,w_80,h_80","books_count":0,"public_books_count":0,"followers_count":0,"following_count":0,"public":1,"description":null,"created_at":"2019-02-25T14:48:11.000Z","updated_at":"2019-05-08T08:04:47.000Z","_serializer":"v2.user_detail"} 5 | */ 6 | 7 | DataBean data; 8 | 9 | static UserInfoBean fromMap(Map map) { 10 | UserInfoBean user_info_bean = new UserInfoBean(); 11 | user_info_bean.data = DataBean.fromMap(map['data']); 12 | return user_info_bean; 13 | } 14 | 15 | static List fromMapList(dynamic mapList) { 16 | List list = new List(mapList.length); 17 | for (int i = 0; i < mapList.length; i++) { 18 | list[i] = fromMap(mapList[i]); 19 | } 20 | return list; 21 | } 22 | 23 | } 24 | 25 | class DataBean { 26 | 27 | /** 28 | * type : "User" 29 | * login : "newfish" 30 | * name : "李子晨(安卓)" 31 | * avatar_url : "https://cdn.nlark.com/yuque/0/2019/png/274963/1557302684519-avatar/d4247287-5306-455d-8162-9b9411416870.png" 32 | * large_avatar_url : "https://cdn.nlark.com/yuque/0/2019/png/274963/1557302684519-avatar/d4247287-5306-455d-8162-9b9411416870.png?x-oss-process=image/resize,m_fill,w_320,h_320" 33 | * medium_avatar_url : "https://cdn.nlark.com/yuque/0/2019/png/274963/1557302684519-avatar/d4247287-5306-455d-8162-9b9411416870.png?x-oss-process=image/resize,m_fill,w_160,h_160" 34 | * small_avatar_url : "https://cdn.nlark.com/yuque/0/2019/png/274963/1557302684519-avatar/d4247287-5306-455d-8162-9b9411416870.png?x-oss-process=image/resize,m_fill,w_80,h_80" 35 | * created_at : "2019-02-25T14:48:11.000Z" 36 | * updated_at : "2019-05-08T08:04:47.000Z" 37 | * _serializer : "v2.user_detail" 38 | * id : 274963 39 | * space_id : 0 40 | * account_id : 136961 41 | * books_count : 0 42 | * public_books_count : 0 43 | * followers_count : 0 44 | * following_count : 0 45 | * public : 1 46 | */ 47 | 48 | String type; 49 | String login; 50 | String name; 51 | String avatar_url; 52 | String large_avatar_url; 53 | String medium_avatar_url; 54 | String small_avatar_url; 55 | String created_at; 56 | String updated_at; 57 | String _serializer; 58 | int id; 59 | int space_id; 60 | int account_id; 61 | int books_count; 62 | int public_books_count; 63 | int followers_count; 64 | int following_count; 65 | int public; 66 | 67 | static DataBean fromMap(Map map) { 68 | DataBean dataBean = new DataBean(); 69 | dataBean.type = map['type']; 70 | dataBean.login = map['login']; 71 | dataBean.name = map['name']; 72 | dataBean.avatar_url = map['avatar_url']; 73 | dataBean.large_avatar_url = map['large_avatar_url']; 74 | dataBean.medium_avatar_url = map['medium_avatar_url']; 75 | dataBean.small_avatar_url = map['small_avatar_url']; 76 | dataBean.created_at = map['created_at']; 77 | dataBean.updated_at = map['updated_at']; 78 | dataBean._serializer = map['_serializer']; 79 | dataBean.id = map['id']; 80 | dataBean.space_id = map['space_id']; 81 | dataBean.account_id = map['account_id']; 82 | dataBean.books_count = map['books_count']; 83 | dataBean.public_books_count = map['public_books_count']; 84 | dataBean.followers_count = map['followers_count']; 85 | dataBean.following_count = map['following_count']; 86 | dataBean.public = map['public']; 87 | return dataBean; 88 | } 89 | 90 | static List fromMapList(dynamic mapList) { 91 | List list = new List(mapList.length); 92 | for (int i = 0; i < mapList.length; i++) { 93 | list[i] = fromMap(mapList[i]); 94 | } 95 | return list; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lib/logic/all_logic.dart: -------------------------------------------------------------------------------- 1 | export 'login_page_logic.dart'; 2 | export 'main_page_logic.dart'; 3 | export 'repository_page_logic.dart'; 4 | export 'article_page_logic.dart'; 5 | export 'article_detail_page_logic.dart'; 6 | -------------------------------------------------------------------------------- /lib/logic/article_page_logic.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | import 'package:yuque/model/article_page_event.dart'; 4 | import 'package:yuque/json/article_list_bean.dart'; 5 | import 'package:yuque/json/local_repolist_bean.dart'; 6 | import 'package:yuque/json/repo_directory_bean.dart'; 7 | import 'package:yuque/public/api_service.dart'; 8 | import 'package:yuque/utils/shared_util.dart'; 9 | import 'package:yuque/widget/loading_widget.dart'; 10 | 11 | class ArticlePageLogic{ 12 | 13 | final ArticlePageModel _model; 14 | 15 | ArticlePageLogic(this._model); 16 | 17 | void getArticleList() async{ 18 | _model.setLoadingFlag(LoadingFlag.loading); 19 | String token = await SharedUtil.instance.getString(Keys.xToken); 20 | ApiService.getInstance().getRepoArticles(token, _model.nameSpace, (data){ 21 | ArticleListBean bean = ArticleListBean.fromMap(data); 22 | _model.articleList.clear(); 23 | _model.articleList.addAll(bean.data); 24 | _model.loadingFlag = LoadingFlag.success; 25 | _model.refresh(); 26 | }, (msg){ 27 | _model?.setLoadingFlag(LoadingFlag.error); 28 | _model?.scaffoldKey?.currentState?.showSnackBar(SnackBar( 29 | backgroundColor: Colors.redAccent, 30 | content: Text( 31 | "出错了 -.-", 32 | style: TextStyle(color: Colors.white), 33 | textAlign: TextAlign.center, 34 | ))); 35 | }); 36 | } 37 | 38 | void getRepoDirList() async{ 39 | _model.setLoadingFlag(LoadingFlag.loading); 40 | String token = await SharedUtil.instance.getString(Keys.xToken); 41 | ApiService.getInstance().getRepoDirectory(token, _model.nameSpace, (data){ 42 | RepoDirectoryBean bean = RepoDirectoryBean.fromMap(data); 43 | _model.repoDirList.clear(); 44 | _model.repoDirList.addAll(bean.data); 45 | _model.loadingFlag = LoadingFlag.success; 46 | _model.refresh(); 47 | }, (msg){ 48 | _model?.setLoadingFlag(LoadingFlag.error); 49 | _model?.scaffoldKey?.currentState?.showSnackBar(SnackBar( 50 | backgroundColor: Colors.redAccent, 51 | content: Text( 52 | "出错了 -.-", 53 | style: TextStyle(color: Colors.white), 54 | textAlign: TextAlign.center, 55 | ))); 56 | }); 57 | } 58 | 59 | 60 | void articleListPushAndSave(String title,String slug,String nameSpace,{int number}) async{ 61 | List stringList = await SharedUtil().getListWithToken(Keys.recentRepo)??[]; 62 | List repoLists = LocalRepoListBean.fromStringList(stringList); 63 | for (var i = 0; i < repoLists.length; i++) { 64 | if(repoLists[i].nameSpace == nameSpace){ 65 | List articleBeans = List.from(repoLists[i].articleBeans); 66 | var articleBean = ArticleBean(articleTitle: title,articleSlug: slug,nameSpace: nameSpace); 67 | if(!LocalRepoListBean.containArticle(articleBean, articleBeans)){ 68 | articleBeans.insert(0, articleBean); 69 | } 70 | if(articleBeans.length > number) articleBeans.removeRange(number - 1, articleBeans.length - 1); 71 | repoLists[i].articleBeans = articleBeans; 72 | } 73 | } 74 | 75 | List newStringList = LocalRepoListBean.fromBeanList(repoLists); 76 | SharedUtil().saveListWithToken(Keys.recentRepo, newStringList); 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /lib/logic/login_page_logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:yuque/model/login_page_event.dart'; 4 | import 'package:yuque/pages/provider_pages.dart'; 5 | import 'package:yuque/public/api_service.dart'; 6 | import 'package:yuque/json/user_info_bean.dart'; 7 | import 'package:yuque/utils/shared_util.dart'; 8 | 9 | 10 | class LoginPageLogic { 11 | final LoginPageModel _model; 12 | 13 | LoginPageLogic(this._model); 14 | 15 | void onAnimationButtonTap() { 16 | debugPrint("点击登陆按钮"); 17 | if (_model.currentAnimation == "normal") { 18 | _model.setCurrentAnimation("loading"); 19 | doLogin(_model.context); 20 | } 21 | } 22 | 23 | void animationCallBack(String currentAnimation, BuildContext context) { 24 | switch (currentAnimation) { 25 | case "success": 26 | _model.setCurrentAnimation("normal"); 27 | SharedUtil().saveBoolean(Keys.hasLogged, true); 28 | Navigator.of(context).pushAndRemoveUntil(new MaterialPageRoute(builder: (context){ 29 | return ProviderPages.getInstance().getMainPage(); 30 | }), (router) => router == null); 31 | break; 32 | case "fail": 33 | _model.setCurrentAnimation("normal"); 34 | break; 35 | case "loading": 36 | // _event.setCurrentAnimation("loading"); 37 | // doLogin(context); 38 | break; 39 | } 40 | } 41 | 42 | void doLogin(BuildContext context) { 43 | ApiService.getInstance().getUserInfo(_model.token, (data) { 44 | UserInfoBean userInfoBean = UserInfoBean.fromMap(data); 45 | SharedUtil.instance.saveString(Keys.xToken, _model.token); 46 | SharedUtil.instance.saveString(Keys.username, userInfoBean.data.name); 47 | SharedUtil.instance.saveString(Keys.userId, userInfoBean.data.login); 48 | SharedUtil.instance.saveString( 49 | Keys.userAvatarUrl, userInfoBean.data.medium_avatar_url); 50 | _model.setCurrentAnimation("success"); 51 | }, (msg) { 52 | print("网络请求错误:${msg}"); 53 | _model?.setCurrentAnimation("fail"); 54 | _model?.scaffoldKey?.currentState?.showSnackBar(SnackBar( 55 | backgroundColor: Theme 56 | .of(context) 57 | .primaryColor, 58 | content: Text( 59 | "出错了 -.-", 60 | style: TextStyle(color: Colors.white), 61 | textAlign: TextAlign.center, 62 | ))); 63 | }); 64 | } 65 | 66 | double isTextOpacity() { 67 | return _model.currentAnimation == "normal" ? 1 : 0; 68 | } 69 | 70 | String invalidToken(String token) { 71 | if (token.isEmpty) { 72 | return 'token不能为空'; 73 | } else if (token.length > 50) { 74 | return 'token应该没有这么长吧'; 75 | } else { 76 | _model.token = token; 77 | return null; 78 | } 79 | } 80 | 81 | getClipboard() async{ 82 | ClipboardData tt = await Clipboard.getData(Clipboard.kTextPlain); 83 | debugPrint("粘贴的内容:${tt?.text}"); 84 | _model.textControll.text = tt?.text??""; 85 | _model.token = tt?.text??""; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /lib/logic/repository_page_logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yuque/model/repository_page_event.dart'; 3 | import 'package:yuque/json/group_repository_bean.dart'; 4 | import 'package:yuque/json/local_repolist_bean.dart'; 5 | import 'package:yuque/public/api_service.dart'; 6 | import 'package:yuque/utils/shared_util.dart'; 7 | import 'package:yuque/widget/loading_widget.dart'; 8 | 9 | class RepositoryPageLogic{ 10 | 11 | final RepositoryPageModel _model; 12 | 13 | RepositoryPageLogic(this._model); 14 | 15 | void getRepoList() async{ 16 | _model.setLoadingFlag(LoadingFlag.loading); 17 | String token = await SharedUtil.instance.getString(Keys.xToken); 18 | ApiService.getInstance().getGroupRepo(token, _model.groupId, (data){ 19 | GroupRepositoryBean bean = GroupRepositoryBean.fromMap(data); 20 | _model.dataList.clear(); 21 | _model.dataList.addAll(bean.data); 22 | _model.loadingFlag = LoadingFlag.success; 23 | _model.refresh(); 24 | }, (msg){ 25 | _model?.setLoadingFlag(LoadingFlag.error); 26 | _model?.scaffoldKey?.currentState?.showSnackBar(SnackBar( 27 | backgroundColor: Colors.redAccent, 28 | content: Text( 29 | "出错了 -.-", 30 | style: TextStyle(color: Colors.white), 31 | textAlign: TextAlign.center, 32 | ))); 33 | }); 34 | } 35 | 36 | 37 | void repoListReadAndSave(String teamName, String repositoryName, String nameSpace, String groupLoginId, String groupAvatar) async{ 38 | LocalRepoListBean repoListBean = LocalRepoListBean( 39 | teamName: teamName, 40 | repoName: repositoryName, 41 | nameSpace: nameSpace, 42 | groupLoginId: groupLoginId, 43 | groupAvatarUrl: groupAvatar, 44 | articleBeans: [], 45 | ); 46 | List stringList = await SharedUtil().getListWithToken(Keys.recentRepo)??[]; 47 | List repoLists = LocalRepoListBean.fromStringList(stringList); 48 | 49 | if(!LocalRepoListBean.containsRepoByNameSpace(repoListBean, repoLists)){ 50 | repoLists.add(repoListBean); 51 | } 52 | List newStringList = LocalRepoListBean.fromBeanList(repoLists); 53 | SharedUtil().saveListWithToken(Keys.recentRepo, newStringList); 54 | } 55 | 56 | 57 | 58 | } -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/services.dart'; 5 | import 'package:jpush_flutter/jpush_flutter.dart'; 6 | import 'package:yuque/utils/shared_util.dart'; 7 | import 'pages/provider_pages.dart'; 8 | //import 'package:flutter_bugly/flutter_bugly.dart'; 9 | 10 | //void main()=>FlutterBugly.postCatchedException((){ 11 | // // 强制竖屏 12 | // SystemChrome.setPreferredOrientations([ 13 | // DeviceOrientation.portraitUp, 14 | // DeviceOrientation.portraitDown 15 | // ]); 16 | // 17 | // runApp(MyApp()); 18 | //}, useLog: false); 19 | 20 | void main(){ 21 | SystemChrome.setPreferredOrientations([ 22 | DeviceOrientation.portraitUp, 23 | DeviceOrientation.portraitDown 24 | ]); 25 | runApp(MyApp()); 26 | } 27 | 28 | class MyApp extends StatefulWidget { 29 | // This widget is the root of your application. 30 | @override 31 | _MyAppState createState() => _MyAppState(); 32 | } 33 | 34 | class _MyAppState extends State { 35 | @override 36 | void initState() { 37 | super.initState(); 38 | print('极光开启'); 39 | JPush().applyPushAuthority( 40 | new NotificationSettingsIOS(sound: true, alert: true, badge: true), 41 | ); 42 | 43 | 44 | } 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | return MaterialApp( 49 | title: '滴雀', 50 | theme: ThemeData( 51 | primaryColor: Color.fromRGBO(106 , 180, 255, 1), 52 | primaryTextTheme: TextTheme( 53 | title: TextStyle(color: Colors.white), 54 | 55 | ), 56 | appBarTheme: AppBarTheme(iconTheme: IconThemeData( 57 | color: Colors.white, 58 | )), 59 | iconTheme: IconThemeData( 60 | color: Colors.lightBlue, 61 | ) 62 | ), 63 | home: FutureBuilder( 64 | future: SharedUtil.instance.getBoolean(Keys.hasLogged), 65 | builder: (context, snapshot) { 66 | bool hasLogged = snapshot?.data ?? false; 67 | return hasLogged 68 | ? ProviderPages.getInstance().getMainPage() 69 | : ProviderPages.getInstance().getLoginPage(); 70 | }), 71 | ); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /lib/model/all_model.dart: -------------------------------------------------------------------------------- 1 | export 'login_page_event.dart'; 2 | export 'main_page_event.dart'; 3 | export 'repository_page_event.dart'; 4 | export 'article_page_event.dart'; 5 | export 'article_detail_page_event.dart'; 6 | -------------------------------------------------------------------------------- /lib/model/article_detail_page_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yuque/json/article_detail_bean.dart'; 3 | import 'package:yuque/logic/article_detail_page_logic.dart'; 4 | import 'package:yuque/widget/loading_widget.dart'; 5 | class ArticleDetailPageModel extends ChangeNotifier{ 6 | 7 | ArticleDetailPageLogic logic; 8 | BuildContext context; 9 | LoadingFlag loadingFlag = LoadingFlag.loading; 10 | final scaffoldKey = GlobalKey(); 11 | String nameSpace; 12 | String articleSlug; 13 | ArticleDetailBean bean; 14 | 15 | 16 | ArticleDetailPageModel(){ 17 | logic = ArticleDetailPageLogic(this); 18 | } 19 | 20 | void setContext(BuildContext context){ 21 | if(this.context == null){ 22 | this.context = context; 23 | logic.getArticleDetail(); 24 | } 25 | } 26 | 27 | @override 28 | void dispose(){ 29 | super.dispose(); 30 | scaffoldKey?.currentState?.dispose(); 31 | debugPrint("ArticleDetailPageEvent销毁了"); 32 | } 33 | 34 | void refresh(){ 35 | notifyListeners(); 36 | } 37 | 38 | void setInitialData(String nameSpace, String articleSlug) { 39 | this.nameSpace = nameSpace; 40 | this.articleSlug = articleSlug; 41 | } 42 | 43 | void setArticleDetailBean(ArticleDetailBean bean){ 44 | if(this.bean == null){ 45 | this.bean = bean; 46 | refresh(); 47 | } 48 | } 49 | 50 | void setLoadingFlag(LoadingFlag flag) { 51 | if (this.loadingFlag != flag) { 52 | this.loadingFlag = flag; 53 | notifyListeners(); 54 | } 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /lib/model/article_page_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yuque/json/article_list_bean.dart'; 3 | import 'package:yuque/json/repo_directory_bean.dart'; 4 | import 'package:yuque/logic/article_page_logic.dart'; 5 | import 'package:yuque/public/NetManager.dart'; 6 | import 'package:yuque/widget/loading_widget.dart'; 7 | 8 | 9 | class ArticlePageModel extends ChangeNotifier{ 10 | 11 | ArticlePageLogic logic; 12 | BuildContext context; 13 | String nameSpace; 14 | LoadingFlag loadingFlag = LoadingFlag.loading; 15 | final scaffoldKey = GlobalKey(); 16 | List articleList = []; 17 | List repoDirList = []; 18 | 19 | ArticlePageModel(){ 20 | logic = ArticlePageLogic(this); 21 | } 22 | 23 | void setContext(BuildContext context){ 24 | if(this.context == null){ 25 | this.context = context; 26 | logic.getRepoDirList(); 27 | } 28 | } 29 | 30 | @override 31 | void dispose(){ 32 | super.dispose(); 33 | scaffoldKey?.currentState?.dispose(); 34 | debugPrint("ArticlePageEvent销毁了"); 35 | } 36 | 37 | void refresh(){ 38 | notifyListeners(); 39 | } 40 | 41 | void setNameSpace(String nameSpace) {this.nameSpace = nameSpace;} 42 | 43 | void setLoadingFlag(LoadingFlag flag) { 44 | if (this.loadingFlag != flag) { 45 | this.loadingFlag = flag; 46 | notifyListeners(); 47 | } 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /lib/model/login_page_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yuque/logic/login_page_logic.dart'; 3 | import 'package:yuque/utils/shared_util.dart'; 4 | 5 | class LoginPageModel extends ChangeNotifier{ 6 | 7 | String currentAnimation = "normal"; 8 | String token = ""; 9 | final scaffoldKey = GlobalKey(); 10 | final textControll = TextEditingController(); 11 | LoginPageLogic logic; 12 | BuildContext context; 13 | 14 | 15 | LoginPageModel(){ 16 | logic = LoginPageLogic(this); 17 | debugPrint("进入到结构类"); 18 | } 19 | 20 | @override 21 | void dispose(){ 22 | super.dispose(); 23 | scaffoldKey?.currentState?.dispose(); 24 | textControll?.dispose(); 25 | debugPrint("LoginPageEvent销毁了"); 26 | } 27 | 28 | void refresh(){ 29 | notifyListeners(); 30 | } 31 | 32 | void setCurrentAnimation(String animation){ 33 | if(currentAnimation != animation){ 34 | currentAnimation = animation; 35 | notifyListeners(); 36 | } 37 | } 38 | 39 | void setContext(BuildContext context){ 40 | if(this.context == null) 41 | this.context = context; 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /lib/model/main_page_event.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:flutter/cupertino.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter/rendering.dart'; 7 | import 'package:jpush_flutter/jpush_flutter.dart'; 8 | import 'package:pull_to_refresh/pull_to_refresh.dart'; 9 | import 'package:yuque/json/group_repository_bean.dart'; 10 | import 'package:yuque/json/local_repolist_bean.dart'; 11 | import 'package:yuque/json/user_groups_bean.dart'; 12 | import 'package:yuque/logic/main_page_logic.dart'; 13 | import 'package:yuque/pages/provider_pages.dart'; 14 | import 'package:yuque/utils/check_update_util.dart'; 15 | import 'package:yuque/widget/loading_widget.dart'; 16 | //import 'package:flutter_bugly/flutter_bugly.dart'; 17 | 18 | class MainPageModel extends ChangeNotifier { 19 | MainPageLogic logic; 20 | BuildContext context; 21 | LoadingFlag groupLoadingFlag = LoadingFlag.loading; 22 | LoadingFlag personRepoLoadingFlag = LoadingFlag.loading; 23 | bool isEditing = false; 24 | int currentExplosionWidget = -99; 25 | List groupsBeans = []; 26 | List personRepoBeans = []; 27 | List deleteChooseList = []; 28 | List deleteCheckList = []; 29 | final scaffoldKey = GlobalKey(); 30 | final refreshController = RefreshController(); 31 | 32 | MainPageModel() { 33 | logic = MainPageLogic(this); 34 | } 35 | 36 | @override 37 | void dispose() { 38 | super.dispose(); 39 | // refreshController.dispose(); 40 | scaffoldKey?.currentState?.dispose(); 41 | debugPrint("MainPageEvent销毁了"); 42 | } 43 | 44 | void notifyMessagePush(notify) { 45 | var extras; 46 | if (Platform.isAndroid) { 47 | var extra = notify['extras']; 48 | var result = extra['cn.jpush.android.EXTRA']; 49 | extras = jsonDecode(result); 50 | } else { 51 | extras = notify['extras']; 52 | } 53 | String title = extras['title']; 54 | String slug = extras['slug']; 55 | String nameSpace = extras['nameSpace']; 56 | print("分别是:$title\n$slug\n$nameSpace"); 57 | Navigator.of(context).push( 58 | new CupertinoPageRoute( 59 | builder: (context) { 60 | return ProviderPages.getInstance() 61 | .getArticleDetailPage(title, slug, nameSpace); 62 | }, 63 | ), 64 | ); 65 | } 66 | 67 | void refresh() { 68 | notifyListeners(); 69 | } 70 | 71 | void setContext(BuildContext context) { 72 | if (this.context == null) { 73 | //未启动App,点击推送 74 | JPush().getLaunchAppNotification().then((notify) { 75 | if (notify['extras'] != null) { 76 | print('首次打开==$notify'); 77 | notifyMessagePush(notify); 78 | } 79 | }); 80 | 81 | logic.getGroups(); 82 | logic.getPersonRepos(); 83 | CheckUpdateUtil().checkUpdate(context); 84 | JPush().addEventHandler(onReceiveMessage: (notify) { 85 | debugPrint("收到信息:$notify"); 86 | // notifyMessagePush(notify); 87 | }, onReceiveNotification: (notify) { 88 | debugPrint("收到通知内容:${notify}"); 89 | if (Platform.isIOS) { 90 | var content = notify['aps']['alert']; 91 | var extras = notify['extras']; 92 | String title = extras['title']; 93 | String slug = extras['slug']; 94 | String nameSpace = extras['nameSpace']; 95 | 96 | scaffoldKey.currentState.showSnackBar(SnackBar( 97 | backgroundColor: Theme.of(context).primaryColor, 98 | // duration: Duration(seconds: 2), 99 | content: CupertinoButton( 100 | child: Text( 101 | content, 102 | style: TextStyle(color: Colors.white), 103 | ), 104 | onPressed: () { 105 | Navigator.of(context).push( 106 | new CupertinoPageRoute( 107 | builder: (context) { 108 | return ProviderPages.getInstance() 109 | .getArticleDetailPage(title, slug, nameSpace); 110 | }, 111 | ), 112 | ); 113 | }, 114 | ), 115 | )); 116 | } 117 | 118 | // notifyMessagePush(notify); 119 | }, onOpenNotification: (notify) { 120 | print('点击推送--$notify'); 121 | notifyMessagePush(notify); 122 | }); 123 | 124 | this.context = context; 125 | } 126 | } 127 | 128 | void setGroupLoadingFlag(LoadingFlag flag) { 129 | if (this.groupLoadingFlag != flag) { 130 | this.groupLoadingFlag = flag; 131 | notifyListeners(); 132 | } 133 | } 134 | 135 | void setPersonRepoLoadingFlag(LoadingFlag flag) { 136 | if (this.personRepoLoadingFlag != flag) { 137 | this.personRepoLoadingFlag = flag; 138 | notifyListeners(); 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /lib/model/repository_page_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yuque/json/group_repository_bean.dart'; 3 | import 'package:yuque/logic/repository_page_logic.dart'; 4 | import 'package:yuque/widget/loading_widget.dart'; 5 | 6 | class RepositoryPageModel extends ChangeNotifier { 7 | 8 | RepositoryPageLogic logic; 9 | BuildContext context; 10 | String groupId; 11 | final scaffoldKey = GlobalKey(); 12 | List dataList = []; 13 | LoadingFlag loadingFlag = LoadingFlag.loading; 14 | 15 | 16 | RepositoryPageModel() { 17 | logic = RepositoryPageLogic(this); 18 | } 19 | 20 | void setContext(BuildContext context) { 21 | if (this.context == null) { 22 | this.context = context; 23 | logic.getRepoList(); 24 | } 25 | } 26 | 27 | @override 28 | void dispose() { 29 | super.dispose(); 30 | scaffoldKey?.currentState?.dispose(); 31 | debugPrint("RepositoryEvent销毁了"); 32 | } 33 | 34 | void refresh() { 35 | notifyListeners(); 36 | } 37 | 38 | void setGroupId(String groupId) { 39 | this.groupId = groupId; 40 | } 41 | 42 | void setLoadingFlag(LoadingFlag flag) { 43 | if (this.loadingFlag != flag) { 44 | this.loadingFlag = flag; 45 | notifyListeners(); 46 | } 47 | } 48 | 49 | 50 | } -------------------------------------------------------------------------------- /lib/pages/about_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:yuque/pages/all_pages.dart'; 4 | import 'dart:io'; 5 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 6 | 7 | class AboutPage extends StatelessWidget { 8 | @override 9 | Widget build(BuildContext context) { 10 | return Scaffold( 11 | appBar: AppBar(title: Text("关于"),), 12 | body: Container( 13 | child: ListView( 14 | scrollDirection: Axis.vertical, 15 | children: [ 16 | ListTile( 17 | leading: Icon(Icons.supervised_user_circle, color: Theme.of(context).primaryColor,), 18 | title: Text("关于我们"), 19 | trailing: Icon(Icons.keyboard_arrow_right,), 20 | onTap: (){ 21 | Navigator.of(context).push(new CupertinoPageRoute(builder: (ctx){ 22 | return WebViewPage("https://femessage.github.io/blog/", title: "关于我们",); 23 | })); 24 | }, 25 | ), 26 | ListTile( 27 | leading: Icon(Platform.isAndroid?Icons.android:FontAwesomeIcons.apple, color: Theme.of(context).primaryColor,), 28 | title: Text("版本信息"), 29 | trailing: Icon(Icons.keyboard_arrow_right,), 30 | onTap: (){ 31 | Navigator.of(context).push(new CupertinoPageRoute(builder: (ctx){ 32 | return VersionPage(); 33 | })); 34 | }, 35 | ), 36 | ], 37 | ), 38 | ), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/pages/all_pages.dart: -------------------------------------------------------------------------------- 1 | export 'login_page.dart'; 2 | export 'main_page.dart'; 3 | export 'repository_page.dart'; 4 | export 'article_detail_page.dart'; 5 | export 'article_page.dart'; 6 | export 'feedback_page.dart'; 7 | export 'image_page.dart'; 8 | export 'webview_page.dart'; 9 | export 'version_page.dart'; 10 | -------------------------------------------------------------------------------- /lib/pages/article_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:provider/provider.dart'; 5 | import 'package:yuque/model/article_page_event.dart'; 6 | import 'package:yuque/pages/provider_pages.dart'; 7 | import 'package:yuque/pages/webview_page.dart'; 8 | import 'package:yuque/widget/loading_widget.dart'; 9 | 10 | class ArticlePage extends StatelessWidget { 11 | final String repositoryName; 12 | final String nameSpace; 13 | 14 | ArticlePage(this.repositoryName, this.nameSpace); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | final model = Provider.of(context); 19 | model.setNameSpace(nameSpace); 20 | model.setContext(context); 21 | return Scaffold( 22 | key: model.scaffoldKey, 23 | appBar: AppBar( 24 | title: Text(repositoryName), 25 | ), 26 | body: Container( 27 | child: model.loadingFlag != LoadingFlag.success 28 | ? LoadingWidget( 29 | text: "文章列表加载中...", 30 | errorCallBack: model.logic.getRepoDirList, 31 | flag: model.loadingFlag, 32 | ) 33 | : ListView.builder( 34 | padding: EdgeInsets.all(10), 35 | itemCount: model.repoDirList.length, 36 | itemBuilder: (context, index) { 37 | final data = model.repoDirList[index]; 38 | final depth = data.depth.toInt(); 39 | // debugPrint("深度:$depth"); 40 | 41 | return Container( 42 | child: Row( 43 | children: [ 44 | Expanded( 45 | child: InkWell( 46 | onTap: data.slug == "#" 47 | ? null 48 | : () { 49 | if(data.slug.contains("http")){ 50 | Navigator.of(context).push(new CupertinoPageRoute(builder: (context) { 51 | return WebViewPage(data.slug); 52 | })); 53 | return; 54 | } 55 | double screenWidth = 56 | MediaQuery.of(context).size.width; 57 | int num = (screenWidth / 2 / 40).toInt(); 58 | Navigator.of(context).push( 59 | new CupertinoPageRoute( 60 | builder: (context) { 61 | model.logic.articleListPushAndSave( 62 | model.repoDirList[index].title, 63 | model.repoDirList[index].slug, 64 | model.nameSpace, 65 | number: num); 66 | return ProviderPages.getInstance() 67 | .getArticleDetailPage( 68 | model.repoDirList[index].title, 69 | model.repoDirList[index].slug, 70 | model.nameSpace); 71 | })); 72 | }, 73 | child: Container( 74 | margin: EdgeInsets.only( 75 | left: (20 * (depth - 1).toDouble() + 10), right: 10, top: 10,bottom: 10), 76 | child: Text( 77 | data.title, 78 | textAlign: TextAlign.left, 79 | style: data.slug == "#" 80 | ? TextStyle( 81 | fontSize: 16, color: Colors.black54) 82 | : TextStyle( 83 | fontSize: 16, 84 | ), 85 | overflow: TextOverflow.ellipsis, 86 | ), 87 | ), 88 | ), 89 | flex: 9, 90 | ), 91 | Expanded( 92 | flex: 1, 93 | child: data.slug == "#" 94 | ? SizedBox() 95 | : Icon(Icons.keyboard_arrow_right)) 96 | ], 97 | ), 98 | ); 99 | }, 100 | ), 101 | ), 102 | ); 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /lib/pages/explain_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yuque/pages/image_page.dart'; 3 | 4 | class ExplainHome extends StatelessWidget { 5 | @override 6 | Widget build(BuildContext context) { 7 | Widget _titleWidget(text) { 8 | return Container( 9 | padding: EdgeInsets.only(bottom: 5.0), 10 | child: Text( 11 | text, 12 | style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500), 13 | ), 14 | ); 15 | } 16 | 17 | Widget _describeWidget(text) { 18 | return Container( 19 | padding: EdgeInsets.only(bottom: 5.0), 20 | child: Text( 21 | text, 22 | style: TextStyle(fontSize: 13, fontWeight: FontWeight.normal), 23 | ), 24 | ); 25 | } 26 | 27 | return Scaffold( 28 | appBar: AppBar( 29 | title: Text('滴雀'), 30 | ), 31 | body: SingleChildScrollView( 32 | padding: EdgeInsets.fromLTRB(15, 30, 15, 0), 33 | child: Column( 34 | crossAxisAlignment: CrossAxisAlignment.start, 35 | children: [ 36 | // _titleWidget('什么是Token?'), 37 | // _describeWidget('Token是语雀平台用于客户端/ API访问用户自己的私密数据!'), 38 | // SizedBox( 39 | // height: 25, 40 | // ), 41 | // _titleWidget('Token的作用?'), 42 | // _describeWidget( 43 | // 'Token是语雀平台用于客户端/ API访问用户自己的私密数据,不需要账户密码即可授权访问,信息更安全!'), 44 | // SizedBox( 45 | // height: 25, 46 | // ), 47 | _titleWidget('如何获取Token?'), 48 | _describeWidget('按照以下流程即可。(仅限拥有 语雀平台使用账号情况下)'), 49 | _describeWidget('1)打开浏览器登录语雀账号'), 50 | _describeWidget('2)点击右上角头像,进入“设置”页面'), 51 | _describeWidget('3)选择“Token”并点击“新建”'), 52 | _describeWidget('4)填写好“用途”(推荐写法:App使用-Token)'), 53 | _describeWidget('5)选择授权范围(推荐全部勾选以免使用时出错,App不会记录任何用户信息,也不侵犯用户隐私)'), 54 | _describeWidget('6)完成操作,即可复制Token用于登录App啦'), 55 | GestureDetector(onTap:(){ 56 | showDialog( 57 | context: context, 58 | builder: (context) { 59 | return ImagePage( 60 | 'images/explain_2.png', 61 | isNetwork: false, 62 | ); 63 | }); 64 | },child: Container(height: 200,child: Image.asset('images/explain_2.png'))), 65 | GestureDetector(onTap:(){ 66 | showDialog( 67 | context: context, 68 | builder: (context) { 69 | return ImagePage( 70 | 'images/explain.png', 71 | isNetwork: false, 72 | ); 73 | }); 74 | },child: Image.asset('images/explain.png')), 75 | SizedBox(height: 20,), 76 | Text("注意:请不要忘了给Token添加权限哦!", style: TextStyle(fontWeight: FontWeight.bold),), 77 | GestureDetector(onTap:(){ 78 | showDialog( 79 | context: context, 80 | builder: (context) { 81 | return ImagePage( 82 | 'images/token_introduce.png', 83 | isNetwork: false, 84 | ); 85 | }); 86 | },child: Image.asset('images/token_introduce.png')), 87 | Container( 88 | alignment: Alignment(0, 0), 89 | child: Text( 90 | '- 到底了 -', 91 | style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 13.0), 92 | ), 93 | ) 94 | ], 95 | ), 96 | ), 97 | ); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lib/pages/image_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:photo_view/photo_view.dart'; 3 | import 'package:cached_network_image/cached_network_image.dart'; 4 | import 'dart:math'; 5 | 6 | class ImagePage extends StatelessWidget { 7 | 8 | final String imageUrl; 9 | final bool isNetwork; 10 | 11 | 12 | ImagePage(this.imageUrl, {this.isNetwork = true}); 13 | 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Scaffold( 18 | backgroundColor: Colors.transparent, 19 | body: Stack( 20 | children: [ 21 | PhotoView( 22 | onTapUp: (ctx,details,value){ 23 | debugPrint("up:${details}"); 24 | Navigator.of(context).pop(); 25 | }, 26 | imageProvider: isNetwork? CachedNetworkImageProvider(imageUrl) : AssetImage(imageUrl), 27 | ), 28 | Align( 29 | alignment: Alignment(0,0.8), 30 | child: IconButton(icon: Icon(Icons.cancel, color: Colors.white70,size: 35,), onPressed: (){ 31 | Navigator.of(context).pop(); 32 | }), 33 | ) 34 | ], 35 | ) 36 | ); 37 | } 38 | } 39 | 40 | 41 | class StfImagePage extends StatefulWidget { 42 | 43 | final String imageUrl; 44 | 45 | 46 | StfImagePage(this.imageUrl); 47 | 48 | @override 49 | _StfImagePageState createState() => _StfImagePageState(); 50 | } 51 | 52 | class _StfImagePageState extends State { 53 | 54 | PhotoViewScaleState _state = PhotoViewScaleState.initial; 55 | bool isDragging = false; 56 | PhotoViewController controller; 57 | 58 | 59 | @override 60 | void initState() { 61 | super.initState(); 62 | controller = PhotoViewController(); 63 | controller.addIgnorableListener((){ 64 | debugPrint("ignore"); 65 | }); 66 | controller.outputStateStream.listen((value){ 67 | debugPrint("value:${value}"); 68 | }); 69 | } 70 | 71 | @override 72 | Widget build(BuildContext context) { 73 | return Scaffold( 74 | backgroundColor: Colors.transparent, 75 | body: Stack( 76 | children: [ 77 | Container( 78 | width: MediaQuery.of(context).size.width, 79 | height: MediaQuery.of(context).size.height, 80 | child: PhotoView( 81 | controller: controller, 82 | scaleStateChangedCallback: (state){ 83 | debugPrint("当前state:${state.index}"); 84 | setState(() { 85 | _state = state; 86 | }); 87 | }, 88 | imageProvider: CachedNetworkImageProvider(widget.imageUrl), 89 | ), 90 | ), 91 | Align( 92 | alignment: Alignment(0,0.8), 93 | child: IconButton(icon: Icon(Icons.cancel, color: Colors.white70,size: 35,), onPressed: (){ 94 | Navigator.of(context).pop(); 95 | }), 96 | ) 97 | ], 98 | ) 99 | // Draggable( 100 | // onDragEnd: (detail){ 101 | // if(detail.offset.dx.abs() > 100 || detail.offset.dy.abs() > 100){ 102 | // Navigator.of(context).pop(); 103 | // isDragging = false; 104 | // } else{ 105 | // isDragging = false; 106 | // setState(() { 107 | // 108 | // }); 109 | // } 110 | // debugPrint("结束x:${detail.offset.dx} y:${detail.offset.dy}"); 111 | // }, 112 | // onDragStarted: (){ 113 | // isDragging = true; 114 | // setState(() { 115 | // 116 | // }); 117 | // debugPrint("开始"); 118 | // }, 119 | // maxSimultaneousDrags: getDragFlag(), 120 | // feedback: Container( 121 | // width: MediaQuery.of(context).size.width, 122 | // height: MediaQuery.of(context).size.height, 123 | // alignment: Alignment.center, 124 | // child: CachedNetworkImage(imageUrl: widget.imageUrl,), 125 | // ), 126 | // childWhenDragging: Container(), 127 | // child: !isDragging? Stack( 128 | // children: [ 129 | // Container( 130 | // width: MediaQuery.of(context).size.width, 131 | // height: MediaQuery.of(context).size.height, 132 | // child: PhotoView( 133 | // scaleStateChangedCallback: (state){ 134 | // setState(() { 135 | // _state = state; 136 | // }); 137 | // }, 138 | // imageProvider: CachedNetworkImageProvider(widget.imageUrl), 139 | // ), 140 | // ), 141 | // Align( 142 | // alignment: Alignment(0,0.8), 143 | // child: IconButton(icon: Icon(Icons.cancel, color: Colors.white70,size: 35,), onPressed: (){ 144 | // Navigator.of(context).pop(); 145 | // }), 146 | // ) 147 | // ], 148 | // ):Container() 149 | // ), 150 | ); 151 | } 152 | 153 | int getDragFlag(){ 154 | int flag = _state == PhotoViewScaleState.initial?1:0; 155 | debugPrint("flag是${flag}"); 156 | return flag; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /lib/pages/login_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flare_flutter/flare_actor.dart'; 4 | import 'package:provider/provider.dart'; 5 | import 'package:yuque/model/login_page_event.dart'; 6 | import 'package:yuque/pages/explain_page.dart'; 7 | 8 | class LoginPage extends StatelessWidget { 9 | @override 10 | Widget build(BuildContext context) { 11 | final model = Provider.of(context); 12 | model.setContext(context); 13 | return Scaffold( 14 | key: model.scaffoldKey, 15 | body: Container( 16 | alignment: Alignment.center, 17 | child: SingleChildScrollView( 18 | scrollDirection: Axis.vertical, 19 | child: Column( 20 | mainAxisAlignment: MainAxisAlignment.center, 21 | crossAxisAlignment: CrossAxisAlignment.center, 22 | children: [ 23 | Container( 24 | width: 300, 25 | height: 320, 26 | child: FlareActor( 27 | "flrs/main_logo.flr", 28 | animation: "rain", 29 | fit: BoxFit.contain, 30 | ), 31 | ), 32 | Container( 33 | margin: EdgeInsets.all(5), 34 | child: Text( 35 | "滴•雀", 36 | style: TextStyle(fontSize: 15, color: Colors.black38), 37 | ) 38 | // Text.rich(TextSpan( 39 | // style: TextStyle(color: Colors.blueAccent, fontSize: 15), text: "滴", children: [ 40 | // TextSpan(style: TextStyle(color: Colors.black), text: "•"), 41 | // TextSpan(style: TextStyle(color: Colors.green), text: "雀"), 42 | // ])) 43 | ), 44 | Container( 45 | margin: EdgeInsets.only(left: 40, right: 40), 46 | child: Form( 47 | autovalidate: true, 48 | child: TextFormField( 49 | controller: model.textControll, 50 | keyboardType: TextInputType.text, 51 | textDirection: TextDirection.ltr, 52 | validator: (token) => model.logic.invalidToken(token), 53 | decoration: InputDecoration( 54 | suffixIcon: FlatButton( 55 | onPressed: model.logic.getClipboard, 56 | child: Text( 57 | "粘贴", 58 | style: TextStyle( 59 | color: Theme.of(context).primaryColor), 60 | )), 61 | hintText: "输入你的token", 62 | labelText: "Token", 63 | ), 64 | ), 65 | ), 66 | ), 67 | SizedBox( 68 | height: 10, 69 | ), 70 | Stack( 71 | alignment: Alignment.center, 72 | children: [ 73 | InkWell( 74 | onTap: model.logic.onAnimationButtonTap, 75 | child: Container( 76 | height: 50, 77 | margin: EdgeInsets.only(left: 100, right: 100), 78 | decoration: BoxDecoration( 79 | shape: BoxShape.rectangle, 80 | borderRadius: BorderRadius.circular(20), 81 | gradient: RadialGradient(//背景径向渐变 82 | colors: [ 83 | Colors.white, 84 | Theme.of(context).primaryColor.withOpacity(0.8) 85 | ], center: Alignment.topLeft, radius: .99), 86 | ), 87 | child: Center( 88 | child: model.logic.isTextOpacity() == 1? Text( 89 | "登 录", 90 | style: TextStyle( 91 | fontSize: 20, 92 | color: Colors.white, 93 | fontWeight: FontWeight.bold), 94 | ):SizedBox(), 95 | ), 96 | ), 97 | ), 98 | model.logic.isTextOpacity() == 1 ? SizedBox() : Container( 99 | height: 60, 100 | width: 130, 101 | child: FlareActor( 102 | "flrs/animation_test.flr", 103 | animation: model.currentAnimation, 104 | fit: BoxFit.contain, 105 | callback: (animationName) => model.logic 106 | .animationCallBack(animationName, context), 107 | ), 108 | ), 109 | 110 | ], 111 | ), 112 | FlatButton( 113 | onPressed: () { 114 | Navigator.push(context, 115 | CupertinoPageRoute(builder: (context) { 116 | return ExplainHome(); 117 | })); 118 | }, 119 | child: Text( 120 | "如何获取Token?", 121 | style: TextStyle(color: Theme.of(context).primaryColor), 122 | )) 123 | ], 124 | ), 125 | ), 126 | ), 127 | ); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /lib/pages/provider_pages.dart: -------------------------------------------------------------------------------- 1 | import 'package:provider/provider.dart'; 2 | import 'package:yuque/model/all_model.dart'; 3 | import 'all_pages.dart'; 4 | 5 | class ProviderPages{ 6 | static ProviderPages _instance; 7 | 8 | static ProviderPages getInstance(){ 9 | if(_instance == null){ 10 | _instance = ProviderPages._internal(); 11 | } 12 | return _instance; 13 | } 14 | 15 | ProviderPages._internal(); 16 | 17 | ChangeNotifierProvider getLoginPage(){ 18 | return ChangeNotifierProvider( 19 | builder:(context) => LoginPageModel(), 20 | child: LoginPage(), 21 | ); 22 | } 23 | 24 | ChangeNotifierProvider getMainPage(){ 25 | return ChangeNotifierProvider( 26 | builder:(context) => MainPageModel(), 27 | child: MainPage(), 28 | ); 29 | } 30 | 31 | ChangeNotifierProvider getRepositoryPage(String name, String id,String avatarUrl){ 32 | return ChangeNotifierProvider( 33 | builder:(context) => RepositoryPageModel(), 34 | child: RepositoryPage(name, id,avatarUrl), 35 | ); 36 | } 37 | 38 | ChangeNotifierProvider getArticlePage(String repoName, String nameSpace){ 39 | return ChangeNotifierProvider( 40 | builder:(context) => ArticlePageModel(), 41 | child: ArticlePage(repoName, nameSpace), 42 | ); 43 | } 44 | 45 | ChangeNotifierProvider getArticleDetailPage(String articleName, String articleSlug, String nameSpace,{bool isFromMain}){ 46 | return ChangeNotifierProvider( 47 | builder:(context) => ArticleDetailPageModel(), 48 | child: ArticleDetailPage(articleName, articleSlug, nameSpace,isFromMain: isFromMain??false,), 49 | ); 50 | } 51 | } -------------------------------------------------------------------------------- /lib/pages/repository_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:provider/provider.dart'; 5 | import 'package:yuque/model/repository_page_event.dart'; 6 | import 'package:yuque/pages/provider_pages.dart'; 7 | import 'package:yuque/widget/loading_widget.dart'; 8 | import 'package:yuque/widget/custom_book.dart'; 9 | 10 | class RepositoryPage extends StatelessWidget { 11 | final String teamName; 12 | final String groupLoginId; 13 | final String groupAvatar; 14 | 15 | RepositoryPage(this.teamName, this.groupLoginId, this.groupAvatar); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | final model = Provider.of(context); 20 | model.setGroupId(groupLoginId); 21 | model.setContext(context); 22 | return Scaffold( 23 | key: model.scaffoldKey, 24 | appBar: AppBar( 25 | elevation: 0, 26 | title: SingleChildScrollView( 27 | child: Text( 28 | teamName, 29 | textAlign: TextAlign.end, 30 | ), 31 | ), 32 | ), 33 | body: Container( 34 | child: model.loadingFlag != LoadingFlag.success 35 | ? LoadingWidget( 36 | text: "仓库列表加载中...", 37 | errorCallBack: model.logic.getRepoList, 38 | flag: model.loadingFlag, 39 | ) 40 | : ListView.builder( 41 | padding: EdgeInsets.all(10), 42 | itemCount: model.dataList.length, 43 | itemBuilder: (context, index) { 44 | return GestureDetector( 45 | onTap: () { 46 | Navigator.of(context) 47 | .push(new CupertinoPageRoute(builder: (context) { 48 | model.logic.repoListReadAndSave( 49 | teamName, 50 | model.dataList[index].name, 51 | model.dataList[index].namespace, 52 | groupLoginId, 53 | groupAvatar); 54 | return ProviderPages.getInstance().getArticlePage( 55 | model.dataList[index].name, 56 | model.dataList[index].namespace); 57 | })); 58 | }, 59 | child: Card( 60 | elevation: 5, 61 | child: Container( 62 | height: 120, 63 | child: Column( 64 | mainAxisAlignment: MainAxisAlignment.start, 65 | crossAxisAlignment: CrossAxisAlignment.start, 66 | children: [ 67 | SizedBox( 68 | height: 10, 69 | ), 70 | Row( 71 | children: [ 72 | Expanded( 73 | flex: 1, 74 | child: Icon( 75 | Icons.home, 76 | color: 77 | Theme.of(context).primaryColorLight, 78 | )), 79 | Expanded( 80 | flex: 5, 81 | child: Text( 82 | "${model.dataList[index].name}", 83 | overflow: TextOverflow.ellipsis, 84 | style: TextStyle( 85 | color: Colors.black, 86 | fontWeight: FontWeight.bold, 87 | fontSize: 18, 88 | ), 89 | ), 90 | ) 91 | ], 92 | ), 93 | SizedBox( 94 | height: 10, 95 | ), 96 | Container( 97 | margin: EdgeInsets.only(left: 18,right: 18), 98 | child: Text( 99 | model.dataList[index].description , 100 | maxLines: 2, 101 | style: TextStyle(fontSize: 15), 102 | overflow: TextOverflow.ellipsis, 103 | )), 104 | 105 | ], 106 | ), 107 | ), 108 | ), 109 | ); 110 | }, 111 | ), 112 | ), 113 | ); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /lib/pages/version_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:package_info/package_info.dart'; 5 | import 'package:yuque/utils/check_update_util.dart'; 6 | 7 | class VersionPage extends StatelessWidget { 8 | @override 9 | Widget build(BuildContext context) { 10 | return Scaffold( 11 | 12 | appBar: AppBar( 13 | title: Text("版本信息"), 14 | ), 15 | body: Container( 16 | child: ListView( 17 | children: [ 18 | Container( 19 | height: 100, 20 | width: 100, 21 | margin: EdgeInsets.only(top: 100), 22 | child: ClipRRect( 23 | borderRadius: BorderRadius.all(Radius.circular(50)), 24 | child: Image.asset( 25 | "images/bird.png", 26 | )), 27 | ), 28 | SizedBox(height: 20,), 29 | FutureBuilder( 30 | future: PackageInfo.fromPlatform(), 31 | builder: (context, snapshot) { 32 | PackageInfo info = snapshot.data; 33 | return Column( 34 | children: [ 35 | Text( 36 | "${info?.appName ?? "滴雀"}", 37 | style: TextStyle( 38 | fontSize: 20, 39 | fontWeight: FontWeight.bold,), 40 | ), 41 | SizedBox(height: 5,), 42 | Text( 43 | "${info?.version ?? "1.0.0"}", 44 | style: TextStyle(fontSize: 12,), 45 | ), 46 | ], 47 | ); 48 | }), 49 | Container( 50 | margin: EdgeInsets.only(left: 30,right: 30,top: 5), 51 | height: 1, 52 | child: Divider(), 53 | ), 54 | Platform.isAndroid ? Container( 55 | margin: EdgeInsets.only(left: 30,right: 30), 56 | child: ListTile( 57 | leading: Icon(Icons.cloud_upload,color: Theme.of(context).primaryColor,), 58 | title: Text("检查更新"), 59 | trailing: Icon(Icons.keyboard_arrow_right), 60 | onTap: (){ 61 | CheckUpdateUtil().checkUpdate(context, isManual: true); 62 | }, 63 | ), 64 | ):SizedBox(), 65 | Container( 66 | margin: EdgeInsets.only(left: 30,right: 30,), 67 | height: 1, 68 | child: Divider(), 69 | ), 70 | ], 71 | ), 72 | ), 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/pages/webview_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flare_flutter/flare_actor.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; 4 | import 'package:yuque/widget/loading_widget.dart'; 5 | import 'dart:io'; 6 | 7 | class WebViewPage extends StatefulWidget { 8 | final String url; 9 | final String title; 10 | 11 | WebViewPage(this.url, {this.title}); 12 | 13 | @override 14 | _WebViewPageState createState() => _WebViewPageState(); 15 | } 16 | 17 | class _WebViewPageState extends State { 18 | FlutterWebviewPlugin flutterWebviewPlugin; 19 | 20 | 21 | @override 22 | void initState() { 23 | super.initState(); 24 | flutterWebviewPlugin = new FlutterWebviewPlugin(); 25 | 26 | } 27 | 28 | 29 | @override 30 | void dispose() { 31 | super.dispose(); 32 | flutterWebviewPlugin.dispose(); 33 | 34 | } 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | flutterWebviewPlugin.onProgressChanged.listen((value){ 39 | if (value==1) { 40 | flutterWebviewPlugin.show(); 41 | } 42 | }); 43 | return WillPopScope( 44 | onWillPop: (){ 45 | flutterWebviewPlugin.close().then((a){ 46 | Navigator.of(context).pop(); 47 | }); 48 | }, 49 | child: Scaffold( 50 | appBar: AppBar(title: Text(widget.title??widget.url, overflow: TextOverflow.ellipsis,), 51 | leading: IconButton(icon: Platform.isIOS?Icon(Icons.arrow_back_ios):Icon(Icons.arrow_back), onPressed: (){ 52 | flutterWebviewPlugin.close().then((a){ 53 | Navigator.of(context).pop(); 54 | }); 55 | }), 56 | ), 57 | body: WebviewScaffold( 58 | url: widget.url, 59 | hidden: true, 60 | initialChild: Center( 61 | child: LoadingWidget( 62 | text: widget.title == null?"网页加载中...":"请稍后...", 63 | errorCallBack: (){ 64 | flutterWebviewPlugin.reload(); 65 | }, 66 | ), 67 | ), 68 | ), 69 | ), 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/public/HttpService.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:dio/dio.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'NetManager.dart'; 6 | 7 | const String CONTENT_TYPE_JSON = "application"; 8 | const String CONTENT_TYPE_FORM = "x-www-form-urlencoded"; 9 | const String CONTENT_CHART_SET = 'utf-8'; 10 | 11 | enum Method { 12 | GET, 13 | POST, 14 | UPLOAD, 15 | DOWNLOAD, 16 | } 17 | 18 | class ResultData { 19 | var data; 20 | 21 | /// true 请求成功 false 请求失败,show data 22 | bool result; 23 | 24 | ResultData(this.data, this.result) { 25 | print('最后数据:$result--$data'); 26 | } 27 | } 28 | 29 | class HttpService { 30 | final Map headers; 31 | final String basicUrl; 32 | final BaseOptions dioOptions; 33 | 34 | HttpService({this.headers, this.basicUrl, this.dioOptions}); 35 | 36 | /// get请求 37 | get(String url, 38 | {Map params, 39 | @required Function onSuccess, 40 | @required Function onError}) async { 41 | return await request(url, 42 | method: Method.GET, 43 | params: params, 44 | onSuccess: onSuccess, 45 | onError: onError); 46 | } 47 | 48 | /// post请求 49 | post(String url, 50 | {Map params, 51 | @required Function onSuccess, 52 | @required Function onError}) async { 53 | return await request(url, 54 | method: Method.POST, 55 | params: params, 56 | onSuccess: onSuccess, 57 | onError: onError); 58 | } 59 | 60 | /// 附件上传 61 | upLoad(var file, String fileName, String url, 62 | {Map params, 63 | @required Function onSuccess, 64 | @required Function onError}) async { 65 | return await request(url, 66 | method: Method.UPLOAD, 67 | params: params, 68 | file: file, 69 | fileName: fileName, 70 | onSuccess: onSuccess, 71 | onError: onError); 72 | } 73 | 74 | /// 附件下载 75 | download(String url, String savePath, @required Function onSuccess, 76 | @required Function onError) async { 77 | return await request(url, 78 | method: Method.DOWNLOAD, 79 | fileSavePath: savePath, 80 | onSuccess: onSuccess, 81 | onError: onError); 82 | } 83 | 84 | /// 请求部分 85 | request(String url, 86 | {Method method, 87 | Map params, 88 | var file, 89 | String fileName, 90 | String fileSavePath, 91 | Function onSuccess, 92 | Function onError}) async { 93 | try { 94 | Response response; 95 | 96 | NetManager manager = NetManager(); 97 | manager.options = BaseOptions( 98 | // 15s 超时时间 99 | connectTimeout:15000, 100 | receiveTimeout:15000, 101 | responseType: ResponseType.json, 102 | contentType: ContentType(CONTENT_TYPE_JSON, CONTENT_TYPE_FORM,charset: CONTENT_CHART_SET), 103 | baseUrl: 'https://www.yuque.com/api/v2/', 104 | 105 | ); 106 | 107 | if (this.headers != null) { 108 | manager.options.headers = headers; 109 | } 110 | if (this.basicUrl != null) { 111 | manager.options.baseUrl = basicUrl; 112 | } 113 | 114 | if (this.dioOptions != null) { 115 | manager.options = dioOptions; 116 | } 117 | 118 | print('请求' + 119 | '$method : ' + 120 | manager.options.baseUrl + 121 | url + 122 | '\nHead:${this.headers}' + 123 | '\nParams:$params'); 124 | switch (method) { 125 | case Method.GET: 126 | response = await manager.get(url, queryParameters: params); 127 | break; 128 | case Method.POST: 129 | response = await manager.post(url, data: params); 130 | break; 131 | case Method.UPLOAD: 132 | { 133 | FormData formData = new FormData(); 134 | if (params != null) { 135 | formData = FormData.from(params); 136 | } 137 | formData.add( 138 | "files", UploadFileInfo.fromBytes(file, fileName + '.png')); 139 | response = await manager.post(url, data: formData); 140 | break; 141 | } 142 | case Method.DOWNLOAD: 143 | response = await manager.download(url, fileSavePath); 144 | break; 145 | } 146 | return await handleDataSource(response, method, onSuccess, onError); 147 | // await handleDataSource(response, method,callback); 148 | } catch (exception) { 149 | print('请求异常了'); 150 | // return ResultData(exception.toString(), false); 151 | return onError(exception.toString()); 152 | } 153 | } 154 | 155 | /// 数据处理 156 | static handleDataSource( 157 | Response response, Method method, Function onSuccess, Function onError) { 158 | print('数据在处理中....'); 159 | 160 | String errorMsg = ""; 161 | int statusCode; 162 | statusCode = response.statusCode; 163 | if (method == Method.DOWNLOAD) { 164 | if (statusCode == 200) { 165 | /// 下载成功 166 | // return ResultData('下载成功', true); 167 | 168 | } else { 169 | /// 下载失败 170 | // return ResultData('下载失败', false); 171 | } 172 | } 173 | //处理错误部分 174 | if (statusCode < 0) { 175 | errorMsg = "网络请求错误,状态码:" + statusCode.toString(); 176 | // return ResultData(errorMsg, false); 177 | return onError(errorMsg); 178 | } 179 | try { 180 | return onSuccess(response.data); 181 | // return ResultData(response.data, true); 182 | 183 | /* Map data = json.decode(response.data); 184 | if (data['code'] == 0 ) { 185 | try { 186 | return ResultData(data['data'], true); 187 | }catch (exception){ 188 | return ResultData('暂无数据', false); 189 | } 190 | }else{ 191 | return ResultData(data['msg'], false); 192 | } */ 193 | } catch (exception) { 194 | /* List data = json.decode(response.data); 195 | return ResultData(data, true); */ 196 | 197 | // return ResultData('数据解析异常', false); 198 | return onError(exception.toString()); 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /lib/public/NetManager.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | 3 | 4 | class NetManager extends Dio { 5 | 6 | // static const String CONTENT_TYPE_JSON = "application"; 7 | // static const String CONTENT_TYPE_FORM = "x-www-form-urlencoded"; 8 | // static const String CONTENT_CHART_SET = 'utf-8'; 9 | 10 | // 工厂模式 11 | factory NetManager() =>_getInstance(); 12 | static NetManager get instance => _getInstance(); 13 | static NetManager _instance; 14 | NetManager._internal() { 15 | // 初始化 16 | } 17 | 18 | static NetManager _getInstance() { 19 | if (_instance == null) { 20 | _instance = NetManager._internal(); 21 | } 22 | return _instance; 23 | } 24 | } -------------------------------------------------------------------------------- /lib/public/api_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:crypto/crypto.dart'; 2 | import 'dart:convert'; 3 | 4 | import 'public_header.dart'; 5 | 6 | class ApiService { 7 | static ApiService _instance; 8 | 9 | static ApiService getInstance() { 10 | if (_instance == null) { 11 | _instance = ApiService._internal(); 12 | } 13 | return _instance; 14 | } 15 | 16 | ApiService._internal(); 17 | 18 | //获取用户信息 19 | void getUserInfo(String token, Function success, Function error) { 20 | HttpService(headers: {"X-Auth-Token": token}) 21 | .get("user", onSuccess: success, onError: error); 22 | } 23 | 24 | //获取用户所在的组列表 25 | void getUserGroups( 26 | String token, String userId, Function success, Function error) { 27 | HttpService(headers: {"X-Auth-Token": token}) 28 | .get("users/${userId}/groups", onSuccess: success, onError: error); 29 | } 30 | 31 | //获取某个组的仓库列表 32 | void getGroupRepo( 33 | String token, String groupId, Function success, Function error) { 34 | HttpService(headers: {"X-Auth-Token": token}) 35 | .get("groups/${groupId}/repos", onSuccess: success, onError: error); 36 | } 37 | 38 | //获取某个用户的仓库列表 39 | void getPersonRepo( 40 | String token, String loginId, Function success, Function error) { 41 | HttpService(headers: {"X-Auth-Token": token}) 42 | .get("users/${loginId}/repos", onSuccess: success, onError: error); 43 | } 44 | 45 | //获取某个仓库中的文章列表 46 | void getRepoArticles( 47 | String token, String nameSpace, Function success, Function error) { 48 | HttpService(headers: {"X-Auth-Token": token}) 49 | .get("repos/${nameSpace}/docs", onSuccess: success, onError: error); 50 | } 51 | 52 | //获取某个仓库的目录结构 53 | void getRepoDirectory( 54 | String token, String nameSpace, Function success, Function error) { 55 | HttpService(headers: {"X-Auth-Token": token}) 56 | .get("repos/${nameSpace}/toc", onSuccess: success, onError: error); 57 | } 58 | 59 | //获取某篇文章的详细信息 60 | void getArticleDetail(String token, String nameSpace, String articleSlug, 61 | Function success, Function error) { 62 | HttpService(headers: {"X-Auth-Token": token}).get( 63 | "repos/${nameSpace}/docs/${articleSlug}", 64 | onSuccess: success, 65 | onError: error); 66 | } 67 | 68 | //意见反馈 69 | void postFeedback( 70 | String message, 71 | String connectWay, 72 | Function success, 73 | Function error, 74 | ) { 75 | HttpService(basicUrl: "https://fd39c609.ap.ngrok.io/feedback").post("", 76 | onSuccess: success, 77 | onError: error, 78 | params: {"message": message, "email": connectWay}); 79 | } 80 | 81 | //检测更新接口 82 | void getCheckUpdate(String token, Function success, Function error){ 83 | HttpService(basicUrl: "https://www.easy-mock.com/mock/5ce268ea90fc7e1f09bce004/serverless/dique-update").get( 84 | "", 85 | params: {"_":md5.convert(Utf8Encoder().convert(token))}, 86 | onSuccess: success, 87 | onError: error); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib/public/public_header.dart: -------------------------------------------------------------------------------- 1 | export 'package:yuque/public/HttpService.dart'; 2 | export 'package:yuque/public/theme.dart'; 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /lib/public/theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | 4 | class AppTheme { 5 | static Color greyColor = Color(0xFF666666); 6 | static Color blackColor = Color(0xFF333333); 7 | static Color lineColor = Color(0xFFEEEEEE); 8 | static Color redColor = Color(0xFFC53439); 9 | static Color whiteColor = Color(0xFFFFFFFF); 10 | static Color backgroundColor = Color(0xFFF6F8FA); 11 | 12 | static ThemeData themData = ThemeData( 13 | 14 | textTheme: TextTheme( 15 | body1: TextStyle( 16 | fontSize: 15.0 17 | ), 18 | display1: TextStyle( 19 | color: blackColor, 20 | fontSize: 13.0 21 | ), 22 | display2: TextStyle( 23 | color: blackColor, 24 | // fontWeight: FontWeight.bold, 25 | fontSize: 15.0 26 | ), 27 | display3: TextStyle( 28 | color: blackColor, 29 | // fontWeight: FontWeight.bold, 30 | fontSize: 17.0 31 | ), 32 | display4: TextStyle( 33 | color: blackColor, 34 | // fontWeight: FontWeight.bold, 35 | fontSize: 20.0 36 | ), 37 | subtitle: TextStyle( 38 | color: greyColor, 39 | fontWeight: FontWeight.normal, 40 | fontSize: 14.0 41 | ), 42 | // title: TextStyle( 43 | // color: Colors.yellow 44 | // ) 45 | 46 | ), 47 | //platform: TargetPlatform.iOS, 48 | iconTheme: IconThemeData( 49 | size: 30, 50 | color: blackColor, 51 | opacity: 0.85, 52 | ), 53 | 54 | // primaryIconTheme 导航栏按钮颜色 55 | primaryIconTheme: IconThemeData( 56 | color: blackColor, 57 | ), 58 | accentColor: Colors.green, //强调颜色 59 | primarySwatch: Colors.red, //调色板,主题色但会被覆盖 60 | // primaryColorBrightness: Brightness.light, 61 | // primaryColor: Color(redColor), // appbar和tabbar:Tint的颜色 62 | appBarTheme: AppBarTheme( 63 | brightness: Brightness.light, 64 | color: whiteColor, 65 | textTheme: TextTheme( 66 | 67 | title: TextStyle( 68 | color: Colors.black, 69 | fontSize: 17, 70 | fontWeight: FontWeight.normal 71 | 72 | ), 73 | 74 | ) 75 | ), 76 | 77 | scaffoldBackgroundColor: backgroundColor, // 整体的scaffold背景颜色 78 | ); 79 | } 80 | -------------------------------------------------------------------------------- /lib/utils/check_update_util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:package_info/package_info.dart'; 5 | import 'package:yuque/json/check_update_bean.dart'; 6 | import 'package:yuque/public/api_service.dart'; 7 | import 'package:yuque/utils/shared_util.dart'; 8 | import 'package:yuque/widget/loading_dialog.dart'; 9 | import 'package:yuque/widget/update_dialog.dart'; 10 | 11 | class CheckUpdateUtil { 12 | 13 | 14 | Future checkUpdate(BuildContext context,{bool isManual = false}) async { 15 | 16 | 17 | if(isManual){ 18 | showDialog(context: context, builder: (ctx){ 19 | return LoadingDialog(); 20 | }); 21 | } else{ 22 | String token = await SharedUtil.instance.getString(Keys.xToken); 23 | ApiService.getInstance().getCheckUpdate(token,(data) async { 24 | CheckUpdateBean bean = CheckUpdateBean.fromMap(data); 25 | print('看看更新的数据-->$data'); 26 | 27 | int cloudAndroidVersion = int.parse((bean?.android?.version??"0").replaceAll(".", "")); 28 | PackageInfo packageInfo = await PackageInfo.fromPlatform(); 29 | int localVersion = int.parse(packageInfo.version.replaceAll(".", "")); 30 | if(cloudAndroidVersion > localVersion){ 31 | if(Platform.isAndroid){ 32 | _showUpdateDialog(bean?.android?.version??"", bean.android.content, bean.android.downloadLink, bean.android.isForceUpdate == 1, context); 33 | } else if(Platform.isIOS){ 34 | _showUpdateDialog(bean?.ios?.version??"", bean.ios.content, bean.ios.downloadLink, bean.ios.isForceUpdate == 1, context); 35 | } 36 | } 37 | }, (msg){ 38 | debugPrint("${msg}"); 39 | }); 40 | } 41 | 42 | } 43 | 44 | 45 | _showUpdateDialog(String version, String updateInfo, String url, 46 | bool isForceUpgrade, BuildContext context) { 47 | showDialog( 48 | context: context, 49 | builder: (context) { 50 | return UpdateDialog( 51 | version: version, 52 | updateInfo: updateInfo, 53 | updateUrl: url, 54 | isForce: isForceUpgrade, 55 | ); 56 | }); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/utils/keys.dart: -------------------------------------------------------------------------------- 1 | class Keys{ 2 | static final String xToken = "x_token"; 3 | static final String username = "username"; 4 | static final String userId = "user_id"; 5 | static final String userAvatarUrl = "user_avatar_url"; 6 | static final String hasLogged = "has_logged"; 7 | static final String hideRepository = "hide_repository"; 8 | static final String hideGroups = "hide_groups"; 9 | static final String recentRepo = "recent_repo"; //最近浏览的仓库 10 | } -------------------------------------------------------------------------------- /lib/utils/shared_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:shared_preferences/shared_preferences.dart'; 2 | export 'keys.dart'; 3 | import 'keys.dart'; 4 | 5 | class SharedUtil{ 6 | 7 | factory SharedUtil() => _getInstance(); 8 | 9 | static SharedUtil get instance => _getInstance(); 10 | static SharedUtil _instance; 11 | 12 | 13 | SharedUtil._internal() { 14 | //初始化 15 | } 16 | 17 | static SharedUtil _getInstance() { 18 | if (_instance == null) { 19 | _instance = new SharedUtil._internal(); 20 | } 21 | return _instance; 22 | } 23 | 24 | 25 | Future saveString (String key, String value) async{ 26 | SharedPreferences prefs = await SharedPreferences.getInstance(); 27 | await prefs.setString(key, value); 28 | } 29 | 30 | Future saveInt (String key, int value) async{ 31 | SharedPreferences prefs = await SharedPreferences.getInstance(); 32 | await prefs.setInt(key, value); 33 | } 34 | 35 | Future saveDouble (String key, double value) async{ 36 | SharedPreferences prefs = await SharedPreferences.getInstance(); 37 | await prefs.setDouble(key, value); 38 | } 39 | 40 | Future saveBoolean (String key, bool value) async{ 41 | SharedPreferences prefs = await SharedPreferences.getInstance(); 42 | await prefs.setBool(key, value); 43 | } 44 | 45 | Future saveStringList (String key, List list) async{ 46 | SharedPreferences prefs = await SharedPreferences.getInstance(); 47 | await prefs.setStringList(key, list); 48 | } 49 | 50 | Future saveListWithToken (String key, List list) async{ 51 | SharedPreferences prefs = await SharedPreferences.getInstance(); 52 | String token = await prefs.getString(Keys.xToken); 53 | await prefs.setStringList(key+token, list); 54 | } 55 | 56 | 57 | 58 | 59 | //-----------------------------------------------------get---------------------------------------------------- 60 | 61 | 62 | Future getString (String key) async{ 63 | SharedPreferences prefs = await SharedPreferences.getInstance(); 64 | return prefs.getString(key); 65 | } 66 | 67 | Future getInt (String key) async{ 68 | SharedPreferences prefs = await SharedPreferences.getInstance(); 69 | return prefs.getInt(key); 70 | } 71 | 72 | Future getDouble (String key) async{ 73 | SharedPreferences prefs = await SharedPreferences.getInstance(); 74 | return prefs.getDouble(key); 75 | } 76 | 77 | Future getBoolean (String key) async{ 78 | SharedPreferences prefs = await SharedPreferences.getInstance(); 79 | return prefs.getBool(key)??false; 80 | } 81 | 82 | Future> getStringList(String key) async{ 83 | SharedPreferences prefs = await SharedPreferences.getInstance(); 84 | return prefs.getStringList(key); 85 | } 86 | 87 | Future> getListWithToken(String key) async{ 88 | SharedPreferences prefs = await SharedPreferences.getInstance(); 89 | String token = await prefs.getString(Keys.xToken); 90 | return prefs.getStringList(key+token); 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /lib/utils/toast_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'dart:async'; 3 | 4 | class ToastUtil{ 5 | static ToastUtil _instance; 6 | 7 | static ToastUtil getInstance(){ 8 | if(_instance == null){ 9 | _instance = ToastUtil._internal(); 10 | } 11 | return _instance; 12 | } 13 | 14 | ToastUtil._internal(); 15 | 16 | OverlayEntry _overlayEntry; 17 | Timer timeTask; 18 | 19 | 20 | 21 | void show(BuildContext context, {Widget showWidget, String text = "默认显示内容",Duration duration }){ 22 | if(_overlayEntry == null){ 23 | _showEntry(showWidget, context,text,duration); 24 | } else{ 25 | _overlayEntry.remove(); 26 | _overlayEntry = null; 27 | _showEntry(showWidget, context,text,duration); 28 | } 29 | } 30 | 31 | void _showEntry(Widget showWidget, BuildContext context, String text, Duration duration) { 32 | _overlayEntry = OverlayEntry(builder: (ctx){ 33 | return showWidget??_defaultShow(text); 34 | }); 35 | Overlay.of(context).insert(_overlayEntry); 36 | timeTask?.cancel(); 37 | timeTask = Timer(duration??Duration(seconds: 3), (){ 38 | if(_overlayEntry != null){ 39 | _overlayEntry.remove(); 40 | _overlayEntry = null; 41 | } 42 | }); 43 | } 44 | 45 | Widget _defaultShow(String text){ 46 | return Container( 47 | alignment: Alignment.bottomCenter, 48 | margin: EdgeInsets.only(bottom: 50), 49 | 50 | child: Material( 51 | borderRadius: BorderRadius.all(Radius.circular(20)), 52 | color: Colors.grey.withOpacity(0.5), 53 | child: Container( 54 | margin: EdgeInsets.fromLTRB(10, 5, 10, 5), 55 | child: Text( 56 | text, 57 | style: TextStyle(fontSize: 16, color: Colors.white), 58 | ), 59 | ), 60 | ), 61 | ); 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /lib/widget/custom_book.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | 4 | class CustomBook extends StatelessWidget { 5 | 6 | final Widget child; 7 | final Color bookLeftColor; 8 | final Color bookDownColor; 9 | 10 | 11 | CustomBook({this.child, this.bookLeftColor = Colors.green, this.bookDownColor = Colors.lightGreen}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Center( 16 | child: CustomPaint( 17 | child: child, 18 | painter: MyPainter(bookLeftColor, bookDownColor), 19 | ), 20 | ); 21 | } 22 | } 23 | 24 | 25 | class MyPainter extends CustomPainter { 26 | 27 | final Color bookLeftColor; 28 | final Color bookDownColor; 29 | 30 | MyPainter(this.bookLeftColor, this.bookDownColor); 31 | 32 | 33 | @override 34 | void paint(Canvas canvas, Size size) { 35 | 36 | double distanceX = size.width / 15; 37 | double distanceY = size.height / 15; 38 | 39 | //书本最左侧 40 | var paint = Paint() 41 | ..color = bookLeftColor 42 | ..strokeWidth = 3 43 | ..style = PaintingStyle.fill; 44 | 45 | Path pathLeft = Path() 46 | ..moveTo(0, 0) 47 | ..lineTo(-distanceX, distanceY) 48 | ..lineTo(-distanceX, size.height + distanceY) 49 | ..lineTo(0, size.height); 50 | canvas.drawPath(pathLeft, paint); 51 | 52 | 53 | //书本下侧 54 | paint.color = bookDownColor; 55 | 56 | Path pathDown = Path() 57 | ..moveTo(0, size.height) 58 | ..lineTo(size.width, size.height) 59 | ..lineTo(size.width - distanceX, size.height + distanceY) 60 | ..lineTo(-distanceX, size.height + distanceY); 61 | 62 | 63 | 64 | canvas.drawPath(pathDown, paint); 65 | 66 | 67 | // canvas.drawLine(Offset(0, 0), Offset(100, 100), paint); 68 | } 69 | 70 | @override 71 | bool shouldRepaint(CustomPainter oldDelegate) { 72 | return false; 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /lib/widget/hide_anim_widget.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | class HideAnimWidget extends StatefulWidget { 6 | 7 | final Widget child; 8 | final bool start; 9 | final VoidCallback onComplete; 10 | final String tag; 11 | 12 | HideAnimWidget({this.child, this.start, this.onComplete, this.tag}); 13 | 14 | @override 15 | _HideAnimWidgetState createState() => _HideAnimWidgetState(); 16 | } 17 | 18 | class _HideAnimWidgetState extends State with SingleTickerProviderStateMixin { 19 | AnimationController controller; 20 | Animation animation; 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | controller = AnimationController(vsync: this, duration: Duration(milliseconds: 500)); 26 | animation = new Tween(begin: 0.0, end: 1.0).animate(controller); 27 | controller.addStatusListener((status){ 28 | debugPrint("当前动画状态:${status}"); 29 | if (status == AnimationStatus.completed) { 30 | //动画执行结束时反向执行动画 31 | widget.onComplete(); 32 | } 33 | }); 34 | } 35 | 36 | @override 37 | void dispose() { 38 | controller.dispose(); 39 | debugPrint("动画销毁 ${widget.tag}"); 40 | super.dispose(); 41 | } 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | 46 | return AnimatedBuilder( 47 | animation: animation, 48 | child: widget.child, 49 | builder: (BuildContext ctx, Widget child) { 50 | if(widget.start){ 51 | controller.forward(); 52 | } else { 53 | controller.reverse(); 54 | } 55 | // debugPrint("value${animation.value} + tag:${widget.tag} start:${widget.start}"); 56 | return Transform.scale( 57 | scale: (1 - (animation.value)), 58 | child: new Transform.rotate( 59 | angle: (animation.value) * pi * 4, 60 | child: child, 61 | ), 62 | ); 63 | }, 64 | ); 65 | } 66 | } 67 | 68 | -------------------------------------------------------------------------------- /lib/widget/loading_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:package_info/package_info.dart'; 5 | import 'package:yuque/json/check_update_bean.dart'; 6 | import 'package:yuque/public/api_service.dart'; 7 | import 'package:yuque/utils/shared_util.dart'; 8 | import 'package:yuque/widget/update_dialog.dart'; 9 | import 'loading_widget.dart'; 10 | 11 | class LoadingDialog extends StatefulWidget { 12 | @override 13 | _LoadingDialogState createState() => _LoadingDialogState(); 14 | } 15 | 16 | class _LoadingDialogState extends State { 17 | 18 | LoadingFlag checkUpdateFlag = LoadingFlag.loading; 19 | bool isLatest = false; 20 | 21 | 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | _checkUpdate(); 27 | } 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | final width = MediaQuery.of(context).size.width; 32 | final height = MediaQuery.of(context).size.height; 33 | return Container( 34 | margin: EdgeInsets.fromLTRB(width/5, height/4, width/5, height/4), 35 | child: Card( 36 | child: !isLatest? LoadingWidget( 37 | text: "更新检测中...", 38 | errorText: "重新检测", 39 | flag: checkUpdateFlag, 40 | errorCallBack: (){ 41 | _checkUpdate(); 42 | }, 43 | ) : Center( 44 | child: Text("已是最新版本"), 45 | ) 46 | ) 47 | ); 48 | } 49 | 50 | Future _checkUpdate() async { 51 | setState(() { 52 | checkUpdateFlag = LoadingFlag.loading; 53 | }); 54 | PackageInfo packageInfo = await PackageInfo.fromPlatform(); 55 | debugPrint("当前版本:${packageInfo.version}"); 56 | String token = await SharedUtil.instance.getString(Keys.xToken); 57 | ApiService.getInstance().getCheckUpdate(token, (data) async { 58 | CheckUpdateBean bean = CheckUpdateBean.fromMap(data); 59 | int cloudAndroidVersion = int.parse((bean?.android?.version??"0").replaceAll(".", "")); 60 | int localVersion = int.parse(packageInfo.version.replaceAll(".", "")); 61 | if(cloudAndroidVersion > localVersion){ 62 | Navigator.of(context).pop(); 63 | if(Platform.isAndroid){ 64 | _showUpdateDialog(bean?.android?.version??"", bean.android.content, bean.android.downloadLink, bean.android.isForceUpdate == 0, context); 65 | } else if(Platform.isIOS){ 66 | _showUpdateDialog(bean?.ios?.version??"", bean.ios.content, bean.ios.downloadLink, bean.ios.isForceUpdate == 0, context); 67 | } 68 | } else { 69 | setState(() { 70 | isLatest = true; 71 | }); 72 | Future.delayed(Duration(milliseconds: 500), (){ 73 | if(context != null){ 74 | Navigator.of(context).pop(); 75 | } 76 | }); 77 | } 78 | }, (msg){ 79 | debugPrint("${msg}"); 80 | setState(() { 81 | checkUpdateFlag = LoadingFlag.error; 82 | }); 83 | }); 84 | } 85 | 86 | _showUpdateDialog(String version, String updateInfo, String url, 87 | bool isForceUpgrade, BuildContext context) { 88 | showDialog( 89 | context: context, 90 | barrierDismissible: false, 91 | builder: (context) { 92 | return UpdateDialog( 93 | version: version, 94 | updateInfo: updateInfo, 95 | updateUrl: url, 96 | isForce: isForceUpgrade, 97 | ); 98 | }); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /lib/widget/loading_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flare_flutter/flare_actor.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class LoadingWidget extends StatelessWidget { 5 | final Color progressColor; 6 | final Color textColor; 7 | final double textSize; 8 | final String text; 9 | final String emptyText; 10 | final String errorText; 11 | final LoadingFlag flag; 12 | final VoidCallback errorCallBack; 13 | 14 | LoadingWidget( 15 | {this.progressColor, 16 | this.textColor, 17 | this.textSize = 16, 18 | this.text = "加载中...", 19 | this.flag = LoadingFlag.loading, 20 | this.errorCallBack, this.emptyText, this.errorText}) 21 | : assert(errorCallBack != null), 22 | assert(flag != null); 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | switch (flag) { 27 | case LoadingFlag.loading: 28 | return Center( 29 | child: Column( 30 | mainAxisAlignment: MainAxisAlignment.center, 31 | children: [ 32 | Container( 33 | height: 70, 34 | width: 70, 35 | child: FlareActor( 36 | "flrs/aura.flr", 37 | animation: "Aura", 38 | fit: BoxFit.cover, 39 | )), 40 | SizedBox( 41 | height: 5, 42 | ), 43 | Text( 44 | text, 45 | style: TextStyle( 46 | fontSize: textSize, color: progressColor ?? Colors.black), 47 | ) 48 | ], 49 | ), 50 | ); 51 | break; 52 | case LoadingFlag.error: 53 | return Center( 54 | child: Column( 55 | mainAxisAlignment: MainAxisAlignment.center, 56 | children: [ 57 | Icon( 58 | Icons.error, 59 | color: Colors.redAccent, 60 | size: 30, 61 | ), 62 | SizedBox( 63 | height: 5, 64 | ), 65 | FlatButton( 66 | onPressed: errorCallBack, 67 | child: Text( 68 | errorText??"重新加载", 69 | style: TextStyle(fontSize: 20, color: Colors.redAccent), 70 | )), 71 | ], 72 | ), 73 | ); 74 | break; 75 | case LoadingFlag.success: 76 | return SizedBox(); 77 | break; 78 | case LoadingFlag.empty: 79 | return Center( 80 | child: Text( 81 | emptyText??"空空如也", 82 | style: TextStyle(fontSize: 20, color: Theme.of(context).primaryColor), 83 | ), 84 | ); 85 | break; 86 | } 87 | } 88 | } 89 | 90 | enum LoadingFlag { loading, error, success, empty } 91 | -------------------------------------------------------------------------------- /lib/widget/nav_head.dart: -------------------------------------------------------------------------------- 1 | //navigation的头 2 | import 'dart:io'; 3 | 4 | import 'package:cached_network_image/cached_network_image.dart'; 5 | import 'package:flare_flutter/flare_actor.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter/cupertino.dart'; 8 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 9 | import 'package:yuque/pages/image_page.dart'; 10 | import 'package:yuque/pages/version_page.dart'; 11 | import 'package:yuque/pages/webview_page.dart'; 12 | import 'package:yuque/utils/check_update_util.dart'; 13 | import 'package:yuque/utils/shared_util.dart'; 14 | //import 'package:flutter_bugly/flutter_bugly.dart'; 15 | 16 | class NavHeader extends StatelessWidget { 17 | final Widget exitWidget; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | 22 | return Stack(children: [ 23 | new Container( 24 | width: 500, 25 | height: MediaQuery.of(context).size.height, 26 | child: new FlareActor( 27 | "flrs/bg_head.flr", 28 | animation: "move", 29 | fit: BoxFit.cover, 30 | ), 31 | ), 32 | ListView( 33 | children: [ 34 | UserAccountsDrawerHeader( 35 | decoration: BoxDecoration(color: Colors.transparent), 36 | accountName: FutureBuilder( 37 | future: SharedUtil.instance.getString(Keys.username), 38 | builder: (context, snapshot) { 39 | return Text( 40 | snapshot.data ?? "...", 41 | style: TextStyle(fontSize: 10, color: Colors.black54), 42 | ); 43 | }), 44 | accountEmail: FutureBuilder( 45 | future: SharedUtil.instance.getString(Keys.userId), 46 | builder: (context, snapshot) { 47 | return Text( 48 | snapshot.data ?? "...", 49 | style: TextStyle(fontSize: 10, color: Colors.black54), 50 | ); 51 | }), 52 | currentAccountPicture: ClipRRect( 53 | borderRadius: BorderRadius.circular(40.0), 54 | child: FutureBuilder( 55 | future: SharedUtil.instance.getString(Keys.userAvatarUrl), 56 | builder: (context, snapshot) { 57 | return snapshot.hasData 58 | ? GestureDetector( 59 | onTap: (){ 60 | Navigator.of(context).push(new MaterialPageRoute(builder: (ctx){ 61 | return ImagePage(snapshot.data); 62 | })); 63 | }, 64 | child: CachedNetworkImage( 65 | imageUrl: snapshot.data, 66 | placeholder: (context, url) => 67 | new CircularProgressIndicator( 68 | valueColor: new AlwaysStoppedAnimation( 69 | Theme.of(context).primaryColor), 70 | ), 71 | errorWidget: (context, url, error) => new Icon( 72 | Icons.error, 73 | color: Colors.redAccent, 74 | )), 75 | ) 76 | : CircularProgressIndicator( 77 | valueColor: new AlwaysStoppedAnimation( 78 | Theme.of(context).primaryColor), 79 | ); 80 | }), 81 | ), 82 | 83 | ), 84 | // ListTile( 85 | // leading: Icon(Icons.feedback, color: Theme.of(context).primaryColorLight,), 86 | // title: Text("意见反馈",), 87 | // trailing: Icon(Icons.keyboard_arrow_right), 88 | // onTap: (){ 89 | // Navigator.of(context).push( new CupertinoPageRoute(builder: (context){ 90 | // return FeedbackPage(); 91 | // })); 92 | // }, 93 | // ), 94 | ListTile( 95 | leading: Icon(Icons.supervised_user_circle, color: Theme.of(context).primaryColor,), 96 | title: Text("关于我们"), 97 | trailing: Icon(Icons.keyboard_arrow_right,), 98 | onTap: (){ 99 | Navigator.of(context).push(new CupertinoPageRoute(builder: (ctx){ 100 | return WebViewPage("https://femessage.github.io/blog/", title: "关于我们",); 101 | })); 102 | }, 103 | ), 104 | ListTile( 105 | leading: Icon(Platform.isAndroid?Icons.android:FontAwesomeIcons.apple, color: Theme.of(context).primaryColor,), 106 | title: Text("版本信息"), 107 | trailing: Icon(Icons.keyboard_arrow_right,), 108 | onTap: (){ 109 | Navigator.of(context).push(new CupertinoPageRoute(builder: (ctx){ 110 | return VersionPage(); 111 | })); 112 | }, 113 | ), 114 | 115 | ], 116 | ), 117 | Positioned( 118 | right: 16, 119 | bottom: 16, 120 | child: exitWidget, 121 | ), 122 | ]); 123 | } 124 | 125 | NavHeader(this.exitWidget); 126 | 127 | } 128 | -------------------------------------------------------------------------------- /lib/widget/rotate_anim_widget.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | class RotateAnimWidget extends StatefulWidget { 6 | 7 | final Widget child; 8 | final bool stop; 9 | 10 | RotateAnimWidget({this.child, this.stop}); 11 | 12 | @override 13 | _RotateAnimWidgetState createState() => _RotateAnimWidgetState(); 14 | } 15 | 16 | class _RotateAnimWidgetState extends State with SingleTickerProviderStateMixin { 17 | AnimationController controller; 18 | Animation animation; 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | controller = AnimationController(vsync: this, duration: Duration(milliseconds: 300)); 24 | animation = new Tween(begin: -1.0, end: 1.0).animate(controller); 25 | animation.addStatusListener((status){ 26 | if (status == AnimationStatus.completed) { 27 | //动画执行结束时反向执行动画 28 | controller.reverse(); 29 | } else if (status == AnimationStatus.dismissed) { 30 | //动画恢复到初始状态时执行动画(正向) 31 | controller.forward(); 32 | } 33 | }); 34 | controller.forward(); 35 | } 36 | 37 | @override 38 | void dispose() { 39 | controller.dispose(); 40 | debugPrint("动画销毁"); 41 | super.dispose(); 42 | } 43 | 44 | @override 45 | Widget build(BuildContext context) { 46 | return AnimatedBuilder( 47 | animation: animation, 48 | child: widget.child, 49 | builder: (BuildContext ctx, Widget child) { 50 | if(widget.stop){ 51 | controller.reset(); 52 | }; 53 | return new Transform.rotate( 54 | angle: widget.stop?0:animation.value * pi / 30, 55 | child: child, 56 | ); 57 | }, 58 | ); 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /lib/widget/top_show_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class TopAnimationShowWidget extends StatefulWidget { 4 | final Widget child; 5 | final Duration duration; 6 | final double distanceY; 7 | 8 | TopAnimationShowWidget({this.child, this.duration, this.distanceY = 0}) 9 | : assert(child != null); 10 | 11 | @override 12 | _TopAnimationShowWidgetState createState() => _TopAnimationShowWidgetState(); 13 | } 14 | 15 | class _TopAnimationShowWidgetState extends State 16 | with SingleTickerProviderStateMixin { 17 | AnimationController _controller; 18 | Animation _animation; 19 | 20 | @override 21 | void initState() { 22 | _controller = AnimationController( 23 | vsync: this, duration: widget.duration ?? Duration(seconds: 1)); 24 | _animation = new Tween(begin: 0.0, end: 1.0) 25 | .animate(CurvedAnimation(parent: _controller, curve: Curves.easeOutBack)); 26 | _controller.forward(); 27 | super.initState(); 28 | } 29 | 30 | @override 31 | void dispose() { 32 | _controller.dispose(); 33 | debugPrint("top_show销毁"); 34 | super.dispose(); 35 | } 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | 40 | return AnimatedBuilder( 41 | animation: _animation, 42 | child: Container(child: widget.child), 43 | builder: (ctx, child) { 44 | return Transform.translate( 45 | offset: Offset(0, (_animation.value - 1) * (widget.distanceY)), 46 | child: child, 47 | ); 48 | }, 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/widget/update_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:dio/dio.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/services.dart'; 6 | import 'package:path_provider/path_provider.dart'; 7 | import 'package:url_launcher/url_launcher.dart'; 8 | import 'package:yuque/public/NetManager.dart'; 9 | import 'package:open_file/open_file.dart'; 10 | 11 | class UpdateDialog extends StatefulWidget { 12 | final String version; 13 | final String updateInfo; 14 | final String updateUrl; 15 | final bool isForce; 16 | 17 | UpdateDialog({ 18 | this.version, 19 | this.updateInfo, 20 | this.updateUrl, 21 | this.isForce = false, 22 | }); 23 | 24 | @override 25 | State createState() => new UpdateDialogState(); 26 | } 27 | 28 | class UpdateDialogState extends State { 29 | int _downloadProgress = 0; 30 | NetManager manager; 31 | CancelToken token; 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return WillPopScope( 36 | onWillPop: () async => false, 37 | child: Container( 38 | margin: EdgeInsets.only(left: 50, right: 50, top: 200, bottom: 200), 39 | decoration: BoxDecoration( 40 | shape: BoxShape.rectangle, 41 | borderRadius: BorderRadius.circular(10), 42 | color: Theme.of(context).primaryColor, 43 | ), 44 | child: Column( 45 | children: [ 46 | Expanded( 47 | flex: 2, 48 | child: Container( 49 | margin: EdgeInsets.fromLTRB(5, 30, 5, 5), 50 | child: Material( 51 | child: Text( 52 | "新版本来啦!", 53 | style: TextStyle(color: Colors.white, fontSize: 20), 54 | ), 55 | color: Colors.transparent, 56 | )), 57 | ), 58 | Expanded( 59 | flex: 5, 60 | child: Container( 61 | margin: EdgeInsets.all(5), 62 | alignment: Alignment.center, 63 | child: Material( 64 | color: Colors.transparent, 65 | child: SingleChildScrollView( 66 | scrollDirection: Axis.vertical, 67 | child: Text( 68 | widget.updateInfo, 69 | style: TextStyle(color: Colors.white), 70 | ), 71 | ), 72 | ))), 73 | _downloadProgress != 0 74 | ? Expanded( 75 | child: Container( 76 | child: LinearProgressIndicator( 77 | valueColor: 78 | new AlwaysStoppedAnimation(Colors.orange), 79 | backgroundColor: Colors.grey[300], 80 | value: _downloadProgress / 100, //精确模式,进度20% 81 | ), 82 | ), 83 | flex: 1, 84 | ) 85 | : SizedBox(), 86 | Expanded( 87 | flex: 2, 88 | child: Container( 89 | decoration: BoxDecoration( 90 | shape: BoxShape.rectangle, 91 | borderRadius: BorderRadius.only( 92 | bottomLeft: Radius.circular(10), 93 | bottomRight: Radius.circular(10)), 94 | color: Colors.white, 95 | ), 96 | child: Row( 97 | children: [ 98 | !widget.isForce 99 | ? Expanded( 100 | flex: 1, 101 | child: FlatButton( 102 | onPressed: () { 103 | Navigator.of(context).pop(); 104 | }, 105 | child: Text( 106 | "取消", 107 | style: TextStyle(color: Colors.grey,fontSize: 16), 108 | )), 109 | ) 110 | : SizedBox(), 111 | !widget.isForce 112 | ? Container( 113 | width: 1, 114 | color: Colors.grey[100], 115 | ) 116 | : SizedBox(), 117 | Expanded( 118 | flex: 1, 119 | child: FlatButton( 120 | onPressed: () async { 121 | if (Platform.isAndroid) { 122 | _androidUpdate(); 123 | } else if (Platform.isIOS) { 124 | _iosUpdate(); 125 | } 126 | }, 127 | child: Text( 128 | "升级", 129 | style: TextStyle(color: Colors.black,fontSize: 16), 130 | )), 131 | ), 132 | ], 133 | ), 134 | ), 135 | ) 136 | ], 137 | ), 138 | ), 139 | ); 140 | } 141 | 142 | void _androidUpdate() async { 143 | String path = (await getExternalStorageDirectory()).path; 144 | debugPrint("获取的目录:${path}"); 145 | manager.download(widget.updateUrl, path + "/Download/" + "release.apk", 146 | cancelToken: token, onReceiveProgress: (int count, int total) { 147 | setState(() { 148 | _downloadProgress = ((count / total) * 100).toInt(); 149 | if (_downloadProgress == 100) { 150 | debugPrint("读取的目录:${path}"); 151 | try { 152 | OpenFile.open(path + "/Download/" + "release.apk"); 153 | } catch (e) {} 154 | Navigator.of(context).pop(); 155 | } 156 | }); 157 | }); 158 | } 159 | 160 | void _iosUpdate(){ 161 | launch(widget.updateUrl); 162 | } 163 | 164 | 165 | 166 | @override 167 | void initState() { 168 | super.initState(); 169 | manager = NetManager(); 170 | token = new CancelToken(); 171 | } 172 | 173 | @override 174 | void dispose() { 175 | super.dispose(); 176 | token?.cancel(); 177 | manager?.clear(); 178 | debugPrint("升级销毁"); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: yuque 2 | description: A new Flutter project. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # In Android, build-name is used as versionName while build-number used as versionCode. 10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 12 | # Read more about iOS versioning at 13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 14 | version: 1.1.0+1 15 | 16 | environment: 17 | sdk: ">=2.1.0 <3.0.0" 18 | 19 | dependencies: 20 | flutter: 21 | sdk: flutter 22 | 23 | #网络请求 24 | dio: ^2.1.2 25 | #本地存储 26 | shared_preferences: ^0.5.2 27 | #Flare插件 28 | flare_flutter: ^1.5.5 29 | #状态管理 30 | provider: ^2.0.1 31 | #html展示 32 | flutter_html: ^0.10.1+hotfix.1 33 | #图片缓存 34 | cached_network_image: ^1.1.1 35 | #拖动图片 36 | photo_view: ^0.4.2 37 | #webview 38 | flutter_webview_plugin: ^0.3.5 39 | #极光推送 40 | jpush_flutter: 41 | git: 42 | url: https://github.com/CaiJingLong/jpush-flutter-plugin.git 43 | ref: 4d847f9b2150dc4fd1f8a7620d414d60f464fab7 44 | #刷新 45 | pull_to_refresh: ^1.4.5 46 | #获取app相关信息 47 | package_info: ^0.4.0+4 48 | #app目录 49 | path_provider: ^0.5.0+1 50 | #打开文件 51 | open_file: ^2.0.3 52 | #各种图标 53 | font_awesome_flutter: ^8.4.0 54 | #单元测试 55 | test: ^1.6.0 56 | 57 | 58 | #markdown 59 | # flutter_markdown: ^0.2.0 60 | #html样式 61 | flutter_widget_from_html_core: ^0.2.2 62 | flutter_widget_from_html: ^0.2.2+1 63 | # html2md: ^0.3.1 64 | 65 | 66 | # The following adds the Cupertino Icons font to your application. 67 | # Use with the CupertinoIcons class for iOS style icons. 68 | cupertino_icons: ^0.1.2 69 | 70 | dev_dependencies: 71 | flutter_test: 72 | sdk: flutter 73 | 74 | device_info: ^0.4.0+2 75 | 76 | 77 | # For information on the generic Dart part of this file, see the 78 | # following page: https://www.dartlang.org/tools/pub/pubspec 79 | 80 | # The following section is specific to Flutter. 81 | flutter: 82 | #导入下面目录下的各种资源 83 | assets: 84 | - flrs/ 85 | - images/ 86 | 87 | # The following line ensures that the Material Icons font is 88 | # included with your application, so that you can use the icons in 89 | # the material Icons class. 90 | uses-material-design: true 91 | 92 | # To add assets to your application, add an assets section, like this: 93 | # assets: 94 | # - images/a_dot_burr.jpeg 95 | # - images/a_dot_ham.jpeg 96 | 97 | # An image asset can refer to one or more resolution-specific "variants", see 98 | # https://flutter.dev/assets-and-images/#resolution-aware. 99 | 100 | # For details regarding adding assets from package dependencies, see 101 | # https://flutter.dev/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.dev/custom-fonts/#from-packages 122 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:yuque/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | --------------------------------------------------------------------------------