├── ios
├── Runner
│ ├── zh-Hans.lproj
│ │ ├── Main.strings
│ │ └── LaunchScreen.strings
│ ├── Runner-Bridging-Header.h
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ ├── icon-1024.png
│ │ │ ├── icon-57.png
│ │ │ ├── icon-20@3x.png
│ │ │ ├── icon-29@3x.png
│ │ │ ├── icon-40@2x.png
│ │ │ ├── icon-40@3x.png
│ │ │ ├── icon-57@2x.png
│ │ │ ├── icon-60@2x.png
│ │ │ ├── icon-60@3x.png
│ │ │ ├── icon-29-ipad.png
│ │ │ ├── icon-20@2x-ipad.png
│ │ │ ├── icon-29@2x-ipad.png
│ │ │ └── Contents.json
│ │ └── LaunchImage.imageset
│ │ │ ├── launcher_bg.png
│ │ │ ├── README.md
│ │ │ └── Contents.json
│ ├── AppDelegate.swift
│ ├── Base.lproj
│ │ ├── Main.storyboard
│ │ └── LaunchScreen.storyboard
│ └── Info.plist
├── Flutter
│ ├── Debug.xcconfig
│ ├── Release.xcconfig
│ └── AppFrameworkInfo.plist
├── Runner.xcodeproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── WorkspaceSettings.xcsettings
│ │ └── IDEWorkspaceChecks.plist
├── .gitignore
├── Podfile
└── Podfile.lock
├── android
├── settings_aar.gradle
├── keystore
│ ├── key.jks
│ └── key.properties
├── gradle.properties
├── app
│ ├── src
│ │ ├── main
│ │ │ ├── res
│ │ │ │ ├── drawable
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── launcher_bg.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ └── values
│ │ │ │ │ └── styles.xml
│ │ │ ├── kotlin
│ │ │ │ └── com
│ │ │ │ │ └── siyee
│ │ │ │ │ └── flutterpicgo
│ │ │ │ │ └── MainActivity.kt
│ │ │ └── AndroidManifest.xml
│ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ └── profile
│ │ │ └── AndroidManifest.xml
│ ├── .classpath
│ ├── .settings
│ │ └── org.eclipse.buildship.core.prefs
│ ├── .project
│ └── build.gradle
├── .gitignore
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
├── .settings
│ └── org.eclipse.buildship.core.prefs
├── .project
├── build.gradle
└── settings.gradle
├── docs
├── hostconfig.jpeg
├── design
│ ├── launcher.psd
│ ├── squareLogo.png
│ ├── squareLogo.psd
│ ├── launcher_bg.png
│ ├── squareLogo144.png
│ ├── squareLogo192.png
│ ├── squareLogo256.png
│ ├── squareLogo384.png
│ ├── squareLogo1024.png
│ └── db.json
├── dist
│ ├── ios_development.cer
│ ├── ios_distribution.cer
│ ├── pico_dev_profile.mobileprovision
│ ├── pico_appstore_profile.mobileprovision
│ └── CertificateSigningRequest.certSigningRequest
├── version.json
├── static
│ └── location.html
└── 设计文档.md
├── assets
├── images
│ ├── logo.png
│ ├── 2.0x
│ │ └── logo.png
│ ├── 3.0x
│ │ └── logo.png
│ ├── 4.0x
│ │ └── logo.png
│ └── icon_empty_album.png
└── fonts
│ └── iconfont.ttf
├── lib
├── resources
│ ├── table_name_keys.dart
│ ├── pb_type_keys.dart
│ ├── shared_preferences_keys.dart
│ └── theme_colors.dart
├── utils
│ ├── strings.dart
│ ├── strategy
│ │ ├── image_upload_strategy.dart
│ │ ├── impl
│ │ │ ├── niupic_image_upload.dart
│ │ │ ├── lsky_image_upload.dart
│ │ │ ├── smms_image_upload.dart
│ │ │ ├── upyun_image_upload.dart
│ │ │ ├── tcyun_image_upload.dart
│ │ │ ├── github_image_upload.dart
│ │ │ ├── aliyun_image_upload.dart
│ │ │ └── gitee_image_upload.dart
│ │ └── upload_strategy_factory.dart
│ ├── permission.dart
│ ├── encrypt.dart
│ ├── extended.dart
│ ├── sql.dart
│ ├── net.dart
│ ├── shared_preferences.dart
│ ├── image_upload.dart
│ └── local_notification.dart
├── model
│ ├── base.dart
│ ├── received_notification.dart
│ ├── smms_config.dart
│ ├── uploaded.dart
│ ├── lsky_config.dart
│ ├── github_config.dart
│ ├── config.dart
│ ├── pb_setting.dart
│ ├── gitee_config.dart
│ ├── upyun_config.dart
│ ├── tcyun_config.dart
│ ├── qiniu_config.dart
│ ├── aliyun_config.dart
│ ├── qiniu_content.dart
│ ├── gitee_content.dart
│ ├── theme_state.dart
│ ├── smms_content.dart
│ ├── github_content.dart
│ └── lsky_content.dart
├── api
│ ├── picgo_api.dart
│ ├── niupic_api.dart
│ ├── lsky_api.dart
│ ├── github_api.dart
│ ├── smms_api.dart
│ ├── gitee_api.dart
│ ├── upyun_api.dart
│ └── aliyun_api.dart
├── routers
│ ├── application.dart
│ └── routers.dart
├── views
│ ├── pb_setting_page
│ │ ├── niupic_page
│ │ │ └── niupic_page.dart
│ │ ├── smms_page
│ │ │ └── smms_page.dart
│ │ ├── pb_setting_presenter.dart
│ │ ├── upyun_page
│ │ │ └── upyun_page.dart
│ │ ├── tcyun_page
│ │ │ └── tcyun_page.dart
│ │ ├── aliyun_page
│ │ │ └── aliyun_page.dart
│ │ ├── github_page
│ │ │ └── github_page.dart
│ │ ├── gitee_page
│ │ │ └── gitee_page.dart
│ │ ├── qiniu_page
│ │ │ └── qiniu_page.dart
│ │ └── lsky_page
│ │ │ └── lsky_page.dart
│ ├── 404.dart
│ ├── manage_page
│ │ ├── base_loading_page_state.dart
│ │ ├── smms_page
│ │ │ └── smms_repo_page_presenter.dart
│ │ ├── lsky_page
│ │ │ └── lsky_repo_page_presenter.dart
│ │ ├── github_page
│ │ │ └── github_repo_page_presenter.dart
│ │ ├── gitee_page
│ │ │ └── gitee_repo_page_presenter.dart
│ │ └── qiniu_page
│ │ │ └── qiniu_repo_page_presenter.dart
│ ├── home.dart
│ ├── album_page
│ │ └── album_page_presenter.dart
│ ├── picgo_setting_page
│ │ └── theme_setting_page.dart
│ └── setting_page
│ │ └── setting_page.dart
├── components
│ ├── upload_item
│ │ └── upload_item_presenter.dart
│ ├── loading.dart
│ └── manage_item.dart
└── main.dart
├── .github
├── ISSUE_TEMPLATE
│ ├── feature-request.md
│ └── bug-report.md
└── workflows
│ └── build.yml
├── .metadata
├── test
└── api
│ ├── qiniu_api_test.dart
│ ├── tcyun_api_test.dart
│ └── aliyun_api_test.dart
├── .gitignore
├── LICENSE
└── pubspec.yaml
/ios/Runner/zh-Hans.lproj/Main.strings:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/android/settings_aar.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/ios/Runner/zh-Hans.lproj/LaunchScreen.strings:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/docs/hostconfig.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/docs/hostconfig.jpeg
--------------------------------------------------------------------------------
/android/keystore/key.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/android/keystore/key.jks
--------------------------------------------------------------------------------
/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/assets/images/logo.png
--------------------------------------------------------------------------------
/docs/design/launcher.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/docs/design/launcher.psd
--------------------------------------------------------------------------------
/assets/fonts/iconfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/assets/fonts/iconfont.ttf
--------------------------------------------------------------------------------
/docs/design/squareLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/docs/design/squareLogo.png
--------------------------------------------------------------------------------
/docs/design/squareLogo.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/docs/design/squareLogo.psd
--------------------------------------------------------------------------------
/assets/images/2.0x/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/assets/images/2.0x/logo.png
--------------------------------------------------------------------------------
/assets/images/3.0x/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/assets/images/3.0x/logo.png
--------------------------------------------------------------------------------
/assets/images/4.0x/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/assets/images/4.0x/logo.png
--------------------------------------------------------------------------------
/docs/design/launcher_bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/docs/design/launcher_bg.png
--------------------------------------------------------------------------------
/docs/design/squareLogo144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/docs/design/squareLogo144.png
--------------------------------------------------------------------------------
/docs/design/squareLogo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/docs/design/squareLogo192.png
--------------------------------------------------------------------------------
/docs/design/squareLogo256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/docs/design/squareLogo256.png
--------------------------------------------------------------------------------
/docs/design/squareLogo384.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/docs/design/squareLogo384.png
--------------------------------------------------------------------------------
/docs/dist/ios_development.cer:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/docs/dist/ios_development.cer
--------------------------------------------------------------------------------
/docs/design/squareLogo1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/docs/design/squareLogo1024.png
--------------------------------------------------------------------------------
/docs/dist/ios_distribution.cer:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/docs/dist/ios_distribution.cer
--------------------------------------------------------------------------------
/assets/images/icon_empty_album.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/assets/images/icon_empty_album.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/docs/dist/pico_dev_profile.mobileprovision:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/docs/dist/pico_dev_profile.mobileprovision
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.enableR8=true
3 | android.useAndroidX=true
4 | android.enableJetifier=true
5 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/android/app/src/main/res/drawable/ic_launcher.png
--------------------------------------------------------------------------------
/docs/dist/pico_appstore_profile.mobileprovision:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/docs/dist/pico_appstore_profile.mobileprovision
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/launcher_bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/android/app/src/main/res/mipmap-xxhdpi/launcher_bg.png
--------------------------------------------------------------------------------
/lib/resources/table_name_keys.dart:
--------------------------------------------------------------------------------
1 | /// table name keys
2 |
3 | const String TABLE_NAME_PBSETTING = 'pb_setting';
4 | const String TABLE_NAME_UPLOADED = 'uploaded';
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-57.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-57@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-57@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29-ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29-ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/launcher_bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/launcher_bg.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x-ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x-ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x-ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PicGo/flutter-picgo/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x-ipad.png
--------------------------------------------------------------------------------
/lib/utils/strings.dart:
--------------------------------------------------------------------------------
1 | /// Returns true if s is either null, empty or is solely made of whitespace characters (as defined by String.trim).
2 |
3 | bool isBlank(String s) => s == null || s.trim().isEmpty;
--------------------------------------------------------------------------------
/docs/version.json:
--------------------------------------------------------------------------------
1 | {
2 | "iOS": {
3 | "versionName": "1.9.2",
4 | "versionCode": "24"
5 | },
6 | "Android": {
7 | "versionName": "1.9.2",
8 | "versionCode": "24"
9 | }
10 | }
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/siyee/flutterpicgo/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.siyee.flutterpicgo
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/lib/model/base.dart:
--------------------------------------------------------------------------------
1 | import 'package:sqflite/sqflite.dart';
2 |
3 | class BaseModel {
4 | Database db;
5 | final String table = '';
6 | var query;
7 | BaseModel(this.db) {
8 | query = db.query;
9 | }
10 | }
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F680 Feature Request"
3 | about: I have a suggestion
4 | ---
5 |
6 | ## Feature Request
7 |
8 | **仅限中文与英文**, 其他语言的提交将直接被关闭
9 |
10 | ### 功能需求
11 |
12 | ### 描述您想要的功能.
13 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 23 08:50:38 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
7 |
--------------------------------------------------------------------------------
/android/keystore/key.properties:
--------------------------------------------------------------------------------
1 | # keytool -genkey -v -keystore C:\Users\Administrator\Desktop\flutter-picgo\flutter-picgo\android\key.jks -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 -alias picgo
2 | keyAlias=picgo
3 | keyPassword=ZJyzy520
4 | storePassword=ZJyzy520
5 | storeFile=../keystore/key.jks
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/lib/utils/strategy/image_upload_strategy.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 | import 'package:flutter_picgo/model/uploaded.dart';
3 |
4 | abstract class ImageUploadStrategy {
5 |
6 | /// 上传照片,根据策略来实现上传的图片
7 | Future upload(File file, String renameImage);
8 |
9 | Future delete(Uploaded uploaded);
10 |
11 | }
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: b041144f833e05cf463b8887fa12efdec9493488
8 | channel: stable
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/lib/api/picgo_api.dart:
--------------------------------------------------------------------------------
1 | import 'package:dio/dio.dart';
2 | import 'package:flutter_picgo/utils/net.dart';
3 |
4 | class PicgoApi {
5 | /// 获取App最新版本
6 | static Future getLatestVersion() async {
7 | Response res = await NetUtils.getInstance().get(
8 | 'https://cdn.jsdelivr.net/gh/PicGo/flutter-picgo/docs/version.json');
9 | return res;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/android/app/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/lib/model/received_notification.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class ReceivedNotification {
4 | final int id;
5 | final String title;
6 | final String body;
7 | final String payload;
8 |
9 | ReceivedNotification({
10 | @required this.id,
11 | @required this.title,
12 | @required this.body,
13 | @required this.payload,
14 | });
15 | }
16 |
--------------------------------------------------------------------------------
/lib/model/smms_config.dart:
--------------------------------------------------------------------------------
1 | class SMMSConfig {
2 | String token;
3 |
4 | SMMSConfig({this.token});
5 |
6 | SMMSConfig.fromJson(Map json) {
7 | token = json['token'];
8 | }
9 |
10 | Map toJson() {
11 | final Map data = new Map();
12 | data['token'] = this.token;
13 | return data;
14 | }
15 | }
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/lib/api/niupic_api.dart:
--------------------------------------------------------------------------------
1 | import 'package:dio/dio.dart';
2 | import 'package:flutter_picgo/utils/net.dart';
3 |
4 | class NiupicApi {
5 | static const BASE_URL = 'https://www.niupic.com/';
6 |
7 | static Future upload(FormData data) async {
8 | Response res = await NetUtils.getInstance()
9 | .post(BASE_URL + 'index/upload/process', data: data);
10 | return res.data;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F41B Bug Report"
3 | about: Something isn't working as expected
4 | ---
5 |
6 | ## Bug Report
7 |
8 | **仅限中文与英文**, 其他语言的提交将直接被关闭
9 |
10 | ### 您当前的flutter doctor信息或设备信息
11 |
12 | ### 预期的表现
13 |
14 | ### 实际的表现
15 |
16 | ### 预期的分析 (给出您能想到, 任何您能想到的)
17 |
18 | ### 重现的方式, 例如从 A界面 点击 b, 跳转到B页面, 界面出现溢出乱码等.
19 |
20 | ### 用于重现此问题或者可能解决以上问题的示例代码(例如github 链接代码)
21 |
--------------------------------------------------------------------------------
/lib/resources/pb_type_keys.dart:
--------------------------------------------------------------------------------
1 | class PBTypeKeys {
2 | static const github = 'github';
3 |
4 | static const smms = 'smms';
5 |
6 | static const gitee = 'gitee';
7 |
8 | static const qiniu = 'qiniu';
9 |
10 | static const aliyun = 'aliyun';
11 |
12 | static const tcyun = 'tcyun';
13 |
14 | static const niupic = 'niupic';
15 |
16 | static const lsky = 'lsky';
17 |
18 | static const upyun = 'upyun';
19 | }
20 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "2x"
10 | },
11 | {
12 | "filename" : "launcher_bg.png",
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/android/.settings/org.eclipse.buildship.core.prefs:
--------------------------------------------------------------------------------
1 | arguments=
2 | auto.sync=false
3 | build.scans.enabled=false
4 | connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
5 | connection.project.dir=app
6 | eclipse.preferences.version=1
7 | gradle.user.home=
8 | java.home=/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home
9 | jvm.arguments=
10 | offline.mode=false
11 | override.workspace.settings=true
12 | show.console.view=true
13 | show.executions.view=true
14 |
--------------------------------------------------------------------------------
/android/app/.settings/org.eclipse.buildship.core.prefs:
--------------------------------------------------------------------------------
1 | arguments=
2 | auto.sync=false
3 | build.scans.enabled=false
4 | connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(6.3))
5 | connection.project.dir=
6 | eclipse.preferences.version=1
7 | gradle.user.home=
8 | java.home=/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home
9 | jvm.arguments=
10 | offline.mode=false
11 | override.workspace.settings=true
12 | show.console.view=true
13 | show.executions.view=true
14 |
--------------------------------------------------------------------------------
/lib/routers/application.dart:
--------------------------------------------------------------------------------
1 | import 'package:fluro/fluro.dart';
2 |
3 | enum ENV {
4 | PRODUCTION,
5 | DEV,
6 | }
7 |
8 | class Application {
9 | /// 通过Application设计环境变量
10 | static ENV env = ENV.DEV;
11 |
12 | static FluroRouter router;
13 |
14 | /// 所有获取配置的唯一入口
15 | Map get config {
16 | if (Application.env == ENV.PRODUCTION) {
17 | return {};
18 | }
19 | if (Application.env == ENV.DEV) {
20 | return {};
21 | }
22 | return {};
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lib/resources/shared_preferences_keys.dart:
--------------------------------------------------------------------------------
1 | class SharedPreferencesKeys {
2 | static final String settingIsUploadedRename = 'settingIsUploadedRename';
3 | static final String settingIsTimestampRename = 'settingIsTimestampRename';
4 | static final String settingIsUploadedTip = 'settingIsUploadedTip';
5 | static final String settingIsForceDelete = 'settingIsForceDelete';
6 | static final String settingDefaultPB = 'settingDefaultPB';
7 |
8 | static final String localThemeState = 'localThemeState';
9 | }
10 |
--------------------------------------------------------------------------------
/android/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | android
4 | Project android created by Buildship.
5 |
6 |
7 |
8 |
9 | org.eclipse.buildship.core.gradleprojectbuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.buildship.core.gradleprojectnature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/lib/model/uploaded.dart:
--------------------------------------------------------------------------------
1 | class Uploaded {
2 | int id;
3 | String path;
4 | String type;
5 | String info;
6 |
7 | Uploaded(this.id, this.path, this.type, {this.info});
8 |
9 | Uploaded.fromMap(Map map) {
10 | this.id = map['id'];
11 | this.type = map['type'];
12 | this.path = map['path'];
13 | this.info = map['info'];
14 | }
15 |
16 | Map toMap() {
17 | var map = {'id': id, 'type': type, 'path': path, 'info': info};
18 | return map;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | -
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lib/views/pb_setting_page/niupic_page/niupic_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_picgo/resources/pb_type_keys.dart';
3 | import 'package:flutter_picgo/views/pb_setting_page/base_pb_page_state.dart';
4 |
5 | class NiupicPage extends StatefulWidget {
6 | _NiupicPageState createState() => _NiupicPageState();
7 | }
8 |
9 | class _NiupicPageState extends BasePBSettingPageState {
10 | @override
11 | onLoadConfig(String config) {}
12 |
13 | @override
14 | String get pbType => PBTypeKeys.niupic;
15 |
16 | @override
17 | String get title => '牛图网图床';
18 | }
19 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | if #available(iOS 10.0, *) {
12 | UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
13 | }
14 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | *.mode1v3
2 | *.mode2v3
3 | *.moved-aside
4 | *.pbxuser
5 | *.perspectivev3
6 | **/*sync/
7 | .sconsign.dblite
8 | .tags*
9 | **/.vagrant/
10 | **/DerivedData/
11 | Icon?
12 | **/Pods/
13 | **/.symlinks/
14 | profile
15 | xcuserdata
16 | **/.generated/
17 | Flutter/App.framework
18 | Flutter/Flutter.framework
19 | Flutter/Flutter.podspec
20 | Flutter/Generated.xcconfig
21 | Flutter/app.flx
22 | Flutter/app.zip
23 | Flutter/flutter_assets/
24 | Flutter/flutter_export_environment.sh
25 | ServiceDefinitions.json
26 | Runner/GeneratedPluginRegistrant.*
27 |
28 | # Exceptions to above rules.
29 | !default.mode1v3
30 | !default.mode2v3
31 | !default.pbxuser
32 | !default.perspectivev3
33 |
--------------------------------------------------------------------------------
/lib/model/lsky_config.dart:
--------------------------------------------------------------------------------
1 | class LskyConfig {
2 | String host;
3 | String email;
4 | String password;
5 | String token;
6 |
7 | LskyConfig({this.host, this.email, this.password, this.token});
8 |
9 | LskyConfig.fromJson(Map json) {
10 | host = json['host'];
11 | email = json['email'];
12 | password = json['password'];
13 | token = json['token'];
14 | }
15 |
16 | Map toJson() {
17 | final Map data = new Map();
18 | data['host'] = this.host;
19 | data['email'] = this.email;
20 | data['password'] = this.password;
21 | data['token'] = this.token;
22 | return data;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/android/app/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | app
4 | Project app created by Buildship.
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 | org.eclipse.buildship.core.gradleprojectbuilder
15 |
16 |
17 |
18 |
19 |
20 | org.eclipse.jdt.core.javanature
21 | org.eclipse.buildship.core.gradleprojectnature
22 |
23 |
24 |
--------------------------------------------------------------------------------
/lib/views/404.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_picgo/routers/application.dart';
3 |
4 | class PageNotFound extends StatelessWidget {
5 | @override
6 | Widget build(BuildContext context) {
7 | return Scaffold(
8 | appBar: AppBar(
9 | title: Text("页面走丢啦~"),
10 | ),
11 | body: Container(
12 | child: Center(
13 | child: OutlinedButton(
14 | style: ButtonStyle(
15 | side:
16 | MaterialStateProperty.all(BorderSide(color: Colors.blue))),
17 | child: Text('关闭'),
18 | onPressed: () {
19 | Application.router.pop(context);
20 | },
21 | ),
22 | ),
23 | ),
24 | );
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/lib/model/github_config.dart:
--------------------------------------------------------------------------------
1 | class GithubConfig {
2 | String branch;
3 | String customUrl;
4 | String path;
5 | String repo;
6 | String token;
7 |
8 | GithubConfig({this.branch, this.customUrl, this.path, this.repo, this.token});
9 |
10 | GithubConfig.fromJson(Map json) {
11 | branch = json['branch'];
12 | customUrl = json['customUrl'];
13 | path = json['path'];
14 | repo = json['repo'];
15 | token = json['token'];
16 | }
17 |
18 | Map toJson() {
19 | final Map data = new Map();
20 | data['branch'] = this.branch;
21 | data['customUrl'] = this.customUrl;
22 | data['path'] = this.path;
23 | data['repo'] = this.repo;
24 | data['token'] = this.token;
25 | return data;
26 | }
27 | }
--------------------------------------------------------------------------------
/test/api/qiniu_api_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_picgo/api/qiniu_api.dart';
2 | import 'package:flutter_test/flutter_test.dart';
3 |
4 | main() {
5 | test('测试UpToken生成', () {
6 | String policy = QiniuApi.generatePutPolicy('image', 'test.png');
7 | QiniuApi.generateUpToken('CRd7Wa4PuSGvs4ArToPTLBMCigGGUY3sk3F8oc8W',
8 | 'f2Jkrlyea5s8h8gLEToa9-k895GNM-BlmQ2RfxwU', policy);
9 | });
10 |
11 | test('测试AuthToken生成', () {
12 | var token = QiniuApi.generateAuthToken(
13 | 'post',
14 | '/move/bmV3ZG9jczpmaW5kX21hbi50eHQ=/bmV3ZG9jczpmaW5kLm1hbi50eHQ=',
15 | null,
16 | 'rs.qiniu.com',
17 | null,
18 | null,
19 | 'MY_ACCESS_KEY',
20 | 'MY_SECRET_KEY');
21 | expect(token, 'MY_ACCESS_KEY:1uLvuZM6l6oCzZFqkJ6oI4oFMVQ=');
22 | });
23 | }
24 |
--------------------------------------------------------------------------------
/docs/static/location.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Flutter-Picgo Release
8 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/lib/model/config.dart:
--------------------------------------------------------------------------------
1 | class Config {
2 | String name;
3 | String value;
4 | String label;
5 | bool needValidate;
6 | String placeholder;
7 |
8 | Config(
9 | {this.name, this.value, this.label, this.needValidate, this.placeholder});
10 |
11 | Config.fromJson(Map json) {
12 | name = json['name'];
13 | value = json['value'];
14 | label = json['label'];
15 | needValidate = json['needValidate'];
16 | placeholder = json['placeholder'];
17 | }
18 |
19 | Map toJson() {
20 | final Map data = new Map();
21 | data['name'] = this.name;
22 | data['value'] = this.value;
23 | data['label'] = this.label;
24 | data['needValidate'] = this.needValidate;
25 | data['placeholder'] = this.placeholder;
26 | return data;
27 | }
28 | }
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.3.50'
3 | repositories {
4 | google()
5 | jcenter()
6 | maven { url "https://storage.flutter-io.cn/download.flutter.io" }
7 | }
8 |
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.5.0'
11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | google()
18 | jcenter()
19 | maven { url "https://storage.flutter-io.cn/download.flutter.io" }
20 | }
21 | }
22 |
23 | rootProject.buildDir = '../build'
24 | subprojects {
25 | project.buildDir = "${rootProject.buildDir}/${project.name}"
26 | }
27 | subprojects {
28 | project.evaluationDependsOn(':app')
29 | }
30 |
31 | task clean(type: Delete) {
32 | delete rootProject.buildDir
33 | }
34 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 8.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/lib/model/pb_setting.dart:
--------------------------------------------------------------------------------
1 | class PBSetting {
2 | int _id;
3 | String _type;
4 | String _name;
5 | String _config;
6 | String _path;
7 | bool _visible;
8 |
9 | PBSetting();
10 |
11 | int get id => _id;
12 | String get type => _type;
13 | String get name => _name;
14 | String get config => _config;
15 | String get path => _path;
16 | bool get visible => _visible;
17 |
18 | PBSetting.fromMap(Map map) {
19 | this._id = map['id'];
20 | this._type = map['type'];
21 | this._name = map['name'];
22 | this._config = map['config'];
23 | this._path = map['path'];
24 | this._visible = map['visible'] == 1;
25 | }
26 |
27 | Map toMap() {
28 | var map = {
29 | 'id': _id,
30 | 'type': _type,
31 | 'name': _name,
32 | 'config': _config,
33 | 'path': _path,
34 | 'visible': _visible ? 1 : 0,
35 | };
36 | return map;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | .dart_tool/
26 | .vscode/
27 | .flutter-plugins
28 | .flutter-plugins-dependencies
29 | .packages
30 | .pub-cache/
31 | .pub/
32 | /build/
33 | pubspec.lock
34 |
35 | # Web related
36 | lib/generated_plugin_registrant.dart
37 |
38 | # Symbolication related
39 | app.*.symbols
40 |
41 | # Obfuscation related
42 | app.*.map.json
43 |
44 | # Exceptions to above rules.
45 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
46 |
47 | # ios build
48 | ios/Flutter/.last_build_id
49 |
--------------------------------------------------------------------------------
/lib/model/gitee_config.dart:
--------------------------------------------------------------------------------
1 | class GiteeConfig {
2 | String owner;
3 | String path;
4 | String repo;
5 | String token;
6 | String customUrl;
7 | String branch;
8 |
9 | GiteeConfig(
10 | {this.owner,
11 | this.path,
12 | this.repo,
13 | this.token,
14 | this.customUrl,
15 | this.branch});
16 |
17 | GiteeConfig.fromJson(Map json) {
18 | owner = json['owner'];
19 | path = json['path'];
20 | repo = json['repo'];
21 | token = json['token'];
22 | customUrl = json['customUrl'];
23 | branch = json['branch'];
24 | }
25 |
26 | Map toJson() {
27 | final Map data = new Map();
28 | data['owner'] = this.owner;
29 | data['path'] = this.path;
30 | data['repo'] = this.repo;
31 | data['token'] = this.token;
32 | data['customUrl'] = this.customUrl;
33 | data['branch'] = this.branch;
34 | return data;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/lib/model/upyun_config.dart:
--------------------------------------------------------------------------------
1 | class UpyunConfig {
2 | String bucket;
3 | String operator;
4 | String options;
5 | String password;
6 | String path;
7 | String url;
8 |
9 | UpyunConfig(
10 | {this.bucket,
11 | this.operator,
12 | this.options,
13 | this.password,
14 | this.path,
15 | this.url});
16 |
17 | UpyunConfig.fromJson(Map json) {
18 | bucket = json['bucket'];
19 | operator = json['operator'];
20 | options = json['options'];
21 | password = json['password'];
22 | path = json['path'];
23 | url = json['url'];
24 | }
25 |
26 | Map toJson() {
27 | final Map data = new Map();
28 | data['bucket'] = this.bucket;
29 | data['operator'] = this.operator;
30 | data['options'] = this.options;
31 | data['password'] = this.password;
32 | data['path'] = this.path;
33 | data['url'] = this.url;
34 | return data;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/lib/model/tcyun_config.dart:
--------------------------------------------------------------------------------
1 | class TcyunConfig {
2 | String area;
3 | String bucket;
4 | String customUrl;
5 | String path;
6 | String secretId;
7 | String secretKey;
8 |
9 | TcyunConfig({
10 | this.area,
11 | this.bucket,
12 | this.customUrl,
13 | this.path,
14 | this.secretId,
15 | this.secretKey});
16 |
17 | TcyunConfig.fromJson(Map json) {
18 | area = json['area'];
19 | bucket = json['bucket'];
20 | customUrl = json['customUrl'];
21 | path = json['path'];
22 | secretId = json['secretId'];
23 | secretKey = json['secretKey'];
24 | }
25 |
26 | Map toJson() {
27 | final Map data = new Map();
28 | data['area'] = this.area;
29 | data['bucket'] = this.bucket;
30 | data['customUrl'] = this.customUrl;
31 | data['path'] = this.path;
32 | data['secretId'] = this.secretId;
33 | data['secretKey'] = this.secretKey;
34 | return data;
35 | }
36 | }
--------------------------------------------------------------------------------
/lib/utils/permission.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:permission_handler/permission_handler.dart';
3 |
4 | class PermissionUtils {
5 | static Future requestPhotos() async {
6 | // 检测权限
7 | var status = await Permission.photos.request();
8 | return status;
9 | }
10 |
11 | static Future requestCemera() async {
12 | var status = await Permission.camera.request();
13 | return status;
14 | }
15 |
16 | static showPermissionDialog(BuildContext context, {text: '无法正常访问,因为没有权限'}) {
17 | showDialog(
18 | context: context,
19 | builder: (context) {
20 | return AlertDialog(
21 | title: Text('警告'),
22 | content: Text(text),
23 | actions: [
24 | TextButton(
25 | child: Text('去设置'),
26 | onPressed: () {
27 | openAppSettings();
28 | },
29 | ),
30 | ],
31 | );
32 | });
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/docs/dist/CertificateSigningRequest.certSigningRequest:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE REQUEST-----
2 | MIIChTCCAW0CAQAwQDEhMB8GCSqGSIb3DQEJARYScWE4OTQxNzg1MjJAcXEuY29t
3 | MQ4wDAYDVQQDDAV6anl6eTELMAkGA1UEBhMCQ04wggEiMA0GCSqGSIb3DQEBAQUA
4 | A4IBDwAwggEKAoIBAQCWX7LYj3eLAJQuUSd2BAKktm6MLrWuGTuXR/csWBFAJpYK
5 | NwFhfoMDHvOUHvv2lIWZm8DyBwworlggxWK7smBjqMYly2uR0ABLG6YeIrPg7AGn
6 | R63gA5z8QR27o/xsbLH9zFwKNgMSiZgKfkRpGWBcyAUsn+FDT+msFVS6vE9w1EeT
7 | F6tTwuc2Bn22RtSvfjXCIkPzyFFP/Kd2ccBVL4791dbL/cwmBWaZaLD0spJrZazW
8 | neJO/qtu8Ay1oWDEvMZG2PL86r7kOnP7wXmBFLYLaCfVx1/4Tk4QUaO/rakl3fOP
9 | ZrdQ+vv/5raPQP017bpFxBY+8SbkcgnGc3EIFCwlAgMBAAGgADANBgkqhkiG9w0B
10 | AQsFAAOCAQEAcV5+95dM+TdMLrDB3QWtwHE04zL3F9tDPkOwC4FyeJuOORQdqN2T
11 | V4LKxtc07jMZubxR+Y8vFkfWKoJnShRWoF8J8tfGUBjxd/fCS7iY2Z4EbgBBst02
12 | /HjLwkhVElUCPTk2vEZjwxJqAR0IBMWf5Pqm3HbZHgbat7Beq6+cm5dvXUSngxeW
13 | dfAt1qRtxv8JDWuBo/Pk5WekdKphOs59l26d5xb36hcrjbNqH+AurXCHYgLlaqck
14 | ZytckuvqTL1ckCaNJ60fHD4vNmb77DvyMvpvwQCzYenE8mjXLM/i7U6SEI/3ndeg
15 | drFfaI1eqCsOubFX0OuN0B5GSJccIM/4MA==
16 | -----END CERTIFICATE REQUEST-----
17 |
--------------------------------------------------------------------------------
/lib/model/qiniu_config.dart:
--------------------------------------------------------------------------------
1 | class QiniuConfig {
2 | String accessKey;
3 | String area;
4 | String bucket;
5 | String options;
6 | String path;
7 | String secretKey;
8 | String url;
9 |
10 | QiniuConfig(
11 | {this.accessKey,
12 | this.area,
13 | this.bucket,
14 | this.options,
15 | this.path,
16 | this.secretKey,
17 | this.url});
18 |
19 | QiniuConfig.fromJson(Map json) {
20 | accessKey = json['accessKey'];
21 | area = json['area'];
22 | bucket = json['bucket'];
23 | options = json['options'];
24 | path = json['path'];
25 | secretKey = json['secretKey'];
26 | url = json['url'];
27 | }
28 |
29 | Map toJson() {
30 | final Map data = new Map();
31 | data['accessKey'] = this.accessKey;
32 | data['area'] = this.area;
33 | data['bucket'] = this.bucket;
34 | data['options'] = this.options;
35 | data['path'] = this.path;
36 | data['secretKey'] = this.secretKey;
37 | data['url'] = this.url;
38 | return data;
39 | }
40 | }
--------------------------------------------------------------------------------
/lib/views/manage_page/base_loading_page_state.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | abstract class BaseLoadingPageState extends State {
4 | /// 当前状态,默认状态为LOADING
5 | LoadState state;
6 |
7 | @override
8 | void initState() {
9 | super.initState();
10 | state = LoadState.LOADING;
11 | }
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return Scaffold(
16 | appBar: appBar ?? AppBar(),
17 | body: _buildStateWidget,
18 | );
19 | }
20 |
21 | Widget get _buildStateWidget {
22 | switch (state) {
23 | case LoadState.EMTPY:
24 | return buildEmtpy();
25 | case LoadState.ERROR:
26 | return buildError();
27 | case LoadState.LOADING:
28 | return buildLoading();
29 | case LoadState.SUCCESS:
30 | return buildSuccess();
31 | }
32 | return buildError();
33 | }
34 |
35 | Widget buildEmtpy();
36 | Widget buildError();
37 | Widget buildLoading();
38 | Widget buildSuccess();
39 | AppBar get appBar;
40 | }
41 |
42 | enum LoadState { LOADING, EMTPY, ERROR, SUCCESS }
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Mr.Yang
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 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | # main.yml
2 | # https://github.com/marketplace/actions/flutter-action
3 | # https://github.com/marketplace/actions/create-release
4 | # 工作流程的名字
5 | name: Build and Release apk
6 |
7 | # 工作流程触发的时机,这里是当一个版本标签推送到仓库时触发
8 | on:
9 | push:
10 | tags:
11 | - v*
12 |
13 | # 这个工作流程需要执行的任务
14 | jobs:
15 | process:
16 | name: all process
17 | runs-on: ubuntu-latest
18 | # 这个任务的步骤
19 | steps:
20 | - uses: actions/checkout@v1
21 | - uses: actions/setup-java@v1
22 | with:
23 | java-version: '12.x'
24 | - uses: subosito/flutter-action@v1
25 | with:
26 | channel: 'stable' # or: 'dev' or 'beta'
27 | - run: flutter pub get
28 | - run: flutter test
29 | - run: flutter build apk --release --no-tree-shake-icons
30 | - uses: actions/upload-artifact@v2
31 | with:
32 | path: build/app/outputs/apk/release/*.apk
33 | - run: |
34 | curl -F "file=@build/app/outputs/apk/release/app-release.apk" -F "uKey=${{ secrets.PGY_USER_KEY }}" -F "_api_key=${{ secrets.PGY_API_KEY }}" https://upload.pgyer.com/apiv1/app/upload
--------------------------------------------------------------------------------
/lib/components/upload_item/upload_item_presenter.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 | import 'package:dio/dio.dart';
3 | import 'package:flutter_picgo/utils/image_upload.dart';
4 | import 'package:flutter_picgo/utils/strategy/upload_strategy_factory.dart';
5 |
6 | abstract class UploadItemContract {
7 | uploadSuccess(String url);
8 | uploadFaild(String errorMsg);
9 | }
10 |
11 | class UploadItemPresenter {
12 | UploadItemContract _view;
13 | UploadItemPresenter(this._view);
14 |
15 | /// 根据配置上传图片
16 | doUploadImage(File file, String renameImage) async {
17 | // 读取配置
18 | try {
19 | String pbType = await ImageUploadUtils.getDefaultPB();
20 | var uploader =
21 | ImageUploadUtils(UploadStrategyFactory.getUploadStrategy(pbType));
22 | var uploadedItem = await uploader.upload(file, renameImage);
23 | if (uploadedItem != null) {
24 | _view.uploadSuccess(uploadedItem.path);
25 | } else {
26 | _view.uploadFaild('上传失败!请重试');
27 | }
28 | } on DioError catch (e) {
29 | _view.uploadFaild('${e.message}');
30 | } catch (e) {
31 | _view.uploadFaild('$e');
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/lib/utils/encrypt.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:convert' as convert;
3 | // 文件相关
4 | import 'dart:io';
5 |
6 | class EncryptUtils {
7 |
8 | /*
9 | * Base64加密
10 | */
11 | static String base64Encode(String data){
12 | var content = convert.utf8.encode(data);
13 | var digest = convert.base64Encode(content);
14 | return digest;
15 | }
16 | /*
17 | * Base64解密
18 | */
19 | static String base64Decode(String data){
20 | List bytes = convert.base64Decode(data);
21 | // 网上找的很多都是String.fromCharCodes,这个中文会乱码
22 | //String txt1 = String.fromCharCodes(bytes);
23 | String result = convert.utf8.decode(bytes);
24 | return result;
25 | }
26 |
27 | /*
28 | * 通过图片路径将图片转换成Base64字符串
29 | */
30 | static Future image2Base64(String path) async {
31 | File file = new File(path);
32 | List imageBytes = await file.readAsBytes();
33 | return convert.base64Encode(imageBytes);
34 | }
35 |
36 | /*
37 | * 将图片文件转换成Base64字符串
38 | */
39 | static Future imageFile2Base64(File file) async {
40 | List imageBytes = await file.readAsBytes();
41 | return convert.base64Encode(imageBytes);
42 | }
43 |
44 | }
--------------------------------------------------------------------------------
/lib/model/aliyun_config.dart:
--------------------------------------------------------------------------------
1 | class AliyunConfig {
2 | String accessKeyId;
3 | String accessKeySecret;
4 | String area;
5 | String bucket;
6 | String customUrl;
7 | String options;
8 | String path;
9 |
10 | AliyunConfig(
11 | {this.accessKeyId,
12 | this.accessKeySecret,
13 | this.area,
14 | this.bucket,
15 | this.customUrl,
16 | this.options,
17 | this.path});
18 |
19 | AliyunConfig.fromJson(Map json) {
20 | accessKeyId = json['accessKeyId'];
21 | accessKeySecret = json['accessKeySecret'];
22 | area = json['area'];
23 | bucket = json['bucket'];
24 | customUrl = json['customUrl'];
25 | options = json['options'];
26 | path = json['path'];
27 | }
28 |
29 | Map toJson() {
30 | final Map data = new Map();
31 | data['accessKeyId'] = this.accessKeyId;
32 | data['accessKeySecret'] = this.accessKeySecret;
33 | data['area'] = this.area;
34 | data['bucket'] = this.bucket;
35 | data['customUrl'] = this.customUrl;
36 | data['options'] = this.options;
37 | data['path'] = this.path;
38 | return data;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lib/utils/extended.dart:
--------------------------------------------------------------------------------
1 | import 'package:extended_image/extended_image.dart';
2 | import 'package:flutter/cupertino.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | Widget defaultLoadStateChanged(ExtendedImageState state,
6 | {double iconSize = 16}) {
7 | switch (state.extendedImageLoadState) {
8 | case LoadState.loading:
9 | return Center(
10 | child: Center(
11 | child: SizedBox(
12 | width: iconSize,
13 | height: iconSize,
14 | child: CupertinoActivityIndicator(),
15 | ),
16 | ),
17 | );
18 | break;
19 | case LoadState.failed:
20 | return GestureDetector(
21 | child: Stack(
22 | fit: StackFit.expand,
23 | alignment: AlignmentDirectional.center,
24 | children: [
25 | Icon(
26 | Icons.error,
27 | size: iconSize,
28 | color: Colors.grey[600],
29 | )
30 | ],
31 | ),
32 | onTap: () {
33 | state.reLoadImage();
34 | },
35 | );
36 | break;
37 | default:
38 | return null;
39 | }
40 | }
41 |
42 | class MiniActivityIndicator {}
43 |
--------------------------------------------------------------------------------
/lib/model/qiniu_content.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_picgo/components/manage_item.dart';
2 |
3 | class QiniuContent {
4 | FileContentType type;
5 | /// FILE 时为空, DIR时不能为空
6 | String url;
7 | String key;
8 | String hash;
9 | int fsize;
10 | String mimeType;
11 | int putTime;
12 | String md5;
13 | int status;
14 |
15 | QiniuContent(
16 | {this.type,
17 | this.key,
18 | this.hash,
19 | this.fsize,
20 | this.mimeType,
21 | this.putTime,
22 | this.md5,
23 | this.status});
24 |
25 | QiniuContent.fromJson(Map json) {
26 | key = json['key'];
27 | hash = json['hash'];
28 | fsize = json['fsize'];
29 | mimeType = json['mimeType'];
30 | putTime = json['putTime'];
31 | md5 = json['md5'];
32 | status = json['status'];
33 | }
34 |
35 | Map toJson() {
36 | final Map data = new Map();
37 | data['key'] = this.key;
38 | data['hash'] = this.hash;
39 | data['fsize'] = this.fsize;
40 | data['mimeType'] = this.mimeType;
41 | data['putTime'] = this.putTime;
42 | data['md5'] = this.md5;
43 | data['status'] = this.status;
44 | return data;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/lib/model/gitee_content.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_picgo/components/manage_item.dart';
2 |
3 | class GiteeContent {
4 | FileContentType type;
5 | int size;
6 | String name;
7 | String path;
8 | String sha;
9 | String url;
10 | String htmlUrl;
11 | String downloadUrl;
12 |
13 | GiteeContent(
14 | {this.type,
15 | this.size,
16 | this.name,
17 | this.path,
18 | this.sha,
19 | this.url,
20 | this.htmlUrl,
21 | this.downloadUrl});
22 |
23 | GiteeContent.fromJson(Map json) {
24 | type = json['type'] == 'file' ? FileContentType.FILE : FileContentType.DIR;
25 | size = json['size'];
26 | name = json['name'];
27 | path = json['path'];
28 | sha = json['sha'];
29 | url = json['url'];
30 | htmlUrl = json['html_url'];
31 | downloadUrl = json['download_url'];
32 | }
33 |
34 | Map toJson() {
35 | final Map data = new Map();
36 | data['type'] = this.type == FileContentType.FILE ? 'file' : 'dir';
37 | data['size'] = this.size;
38 | data['name'] = this.name;
39 | data['path'] = this.path;
40 | data['sha'] = this.sha;
41 | data['url'] = this.url;
42 | data['html_url'] = this.htmlUrl;
43 | data['download_url'] = this.downloadUrl;
44 | return data;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | // Copyright 2014 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | include ':app'
6 |
7 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
8 | def properties = new Properties()
9 |
10 | assert localPropertiesFile.exists()
11 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
12 |
13 | def flutterSdkPath = properties.getProperty("flutter.sdk")
14 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
15 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
16 |
17 | // https://github.com/flutter/flutter/issues/55077#issuecomment-628366487
18 | // fix : Plugin project :url_launcher_web not found. Please update settings.gradle.
19 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
20 |
21 | def plugins = new Properties()
22 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
23 | if (pluginsFile.exists()) {
24 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
25 | }
26 |
27 | plugins.each { name, path ->
28 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
29 | include ":$name"
30 | project(":$name").projectDir = pluginDirectory
31 | }
32 |
--------------------------------------------------------------------------------
/lib/views/manage_page/smms_page/smms_repo_page_presenter.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:flutter_picgo/api/smms_api.dart';
4 | import 'package:flutter_picgo/model/smms_content.dart';
5 |
6 | abstract class SMMSRepoPageContract {
7 | void loadSuccess(List data);
8 |
9 | void loadError(String msg);
10 | }
11 |
12 | class SMMSRepoPagePresenter {
13 | SMMSRepoPageContract _view;
14 |
15 | SMMSRepoPagePresenter(this._view);
16 |
17 | doLoadContents() async {
18 | try {
19 | var result = await SMMSApi.getUploadHistory();
20 | var resultmap = json.decode(result);
21 | if (resultmap["success"]) {
22 | _view.loadSuccess((resultmap['data'] as List)
23 | .map((e) => SMMSContent.fromJson(e))
24 | .toList());
25 | } else {
26 | _view.loadError(resultmap['message'] ?? '未知错误');
27 | }
28 | } catch (e) {
29 | _view.loadError('$e');
30 | }
31 | }
32 |
33 | Future doDeleteContents(String path) async {
34 | try {
35 | var result =
36 | await SMMSApi.delete(path.replaceFirst('https://sm.ms/delete/', ''));
37 | var resultmap = json.decode(result);
38 | if (resultmap["success"]) {
39 | return true;
40 | } else {
41 | return false;
42 | }
43 | } catch (e) {
44 | return false;
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/lib/utils/sql.dart:
--------------------------------------------------------------------------------
1 | import './db_provider.dart';
2 | import '../model/base.dart';
3 |
4 | class Sql extends BaseModel {
5 | final String tableName;
6 |
7 | Sql.setTable(this.tableName) : super(DbProvider.db);
8 |
9 | // sdf
10 | Future get() async {
11 | return await this.query(tableName);
12 | }
13 |
14 | Future getBySql(String where, List whereArgs,
15 | {String orderBy, int limit, int offset}) async {
16 | return await this.query(tableName,
17 | where: where,
18 | whereArgs: whereArgs,
19 | orderBy: orderBy,
20 | limit: limit,
21 | offset: offset);
22 | }
23 |
24 | Future rawUpdate(String sql, [List arguments]) async {
25 | return await this.db.rawUpdate('UPDATE $tableName SET $sql', arguments);
26 | }
27 |
28 | Future rawInsert(String valueNames, [List arguments]) async {
29 | return await this
30 | .db
31 | .rawInsert('INSERT INTO $tableName$valueNames', arguments);
32 | }
33 |
34 | Future rawDelete(String wheres, [List arguments]) async {
35 | return await this
36 | .db
37 | .rawDelete('DELETE FROM $tableName WHERE $wheres', arguments);
38 | }
39 |
40 | String getTableName() {
41 | return tableName;
42 | }
43 |
44 | Future deleteAll() async {
45 | return await this.db.delete(tableName);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/lib/views/home.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import 'album_page/album_page.dart';
4 | import 'setting_page/setting_page.dart';
5 |
6 | class AppPage extends StatefulWidget {
7 |
8 | final int selectedIndex;
9 |
10 | AppPage({this.selectedIndex = 0});
11 |
12 | @override
13 | _MainTabsPageState createState() => _MainTabsPageState(this.selectedIndex);
14 |
15 | }
16 |
17 | class _MainTabsPageState extends State {
18 |
19 | int _selectedIndex;
20 |
21 | _MainTabsPageState(this._selectedIndex);
22 |
23 | List _pageList = [AlbumPage(), SettingPage()];
24 |
25 | @override
26 | Widget build(BuildContext context) {
27 | return Scaffold(
28 | body: this._pageList[this._selectedIndex],
29 | bottomNavigationBar: BottomNavigationBar(
30 | currentIndex: this._selectedIndex,
31 | onTap: (value) {
32 | setState(() {
33 | this._selectedIndex = value;
34 | });
35 | },
36 | type: BottomNavigationBarType.fixed,
37 | items: [
38 | BottomNavigationBarItem(
39 | icon: Icon(IconData(0xe621, fontFamily: 'iconfont')),
40 | label:'相册'
41 | ),
42 | BottomNavigationBarItem(
43 | icon: Icon(IconData(0xe634, fontFamily: 'iconfont')),
44 | label: '设置'
45 | ),
46 | ],
47 | ),
48 | );
49 | }
50 |
51 | }
--------------------------------------------------------------------------------
/lib/model/theme_state.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_picgo/utils/shared_preferences.dart';
3 |
4 | class ThemeState with ChangeNotifier {
5 | ThemeMode _mode;
6 | ThemeMode get currentMode => _mode;
7 |
8 | static const Map modeMap = {
9 | ThemeMode.light: '浅色模式',
10 | ThemeMode.dark: '深色模式',
11 | ThemeMode.system: '跟随系统'
12 | };
13 |
14 | ThemeState() {
15 | _init();
16 | }
17 |
18 | _init() async {
19 | var sp = await SpUtil.getInstance();
20 | int localMode = sp.getInt(SharedPreferencesKeys.localThemeState) ?? 2;
21 | changeThemeState(_parseState(localMode));
22 | }
23 |
24 | changeThemeState(ThemeMode mode) async {
25 | _mode = mode;
26 | notifyListeners();
27 | // save state
28 | var sp = await SpUtil.getInstance();
29 | sp.putInt(SharedPreferencesKeys.localThemeState, _parseInt(mode));
30 | }
31 |
32 | /// num转enum
33 | /// 0:浅色模式 1:深色模式 2:跟随系统
34 | int _parseInt(ThemeMode s) {
35 | if (s == ThemeMode.light) {
36 | return 0;
37 | } else if (s == ThemeMode.dark) {
38 | return 1;
39 | } else {
40 | return 2;
41 | }
42 | }
43 |
44 | /// enmu转num
45 | ThemeMode _parseState(int mode) {
46 | if (mode == 0) {
47 | return ThemeMode.light;
48 | } else if (mode == 1) {
49 | return ThemeMode.dark;
50 | } else {
51 | return ThemeMode.system;
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/lib/model/smms_content.dart:
--------------------------------------------------------------------------------
1 | class SMMSContent {
2 | int width;
3 | int height;
4 | String filename;
5 | String storename;
6 | int size;
7 | String path;
8 | String hash;
9 | int createdAt;
10 | String url;
11 | String delete;
12 | String page;
13 |
14 | SMMSContent(
15 | {this.width,
16 | this.height,
17 | this.filename,
18 | this.storename,
19 | this.size,
20 | this.path,
21 | this.hash,
22 | this.createdAt,
23 | this.url,
24 | this.delete,
25 | this.page});
26 |
27 | SMMSContent.fromJson(Map json) {
28 | width = json['width'];
29 | height = json['height'];
30 | filename = json['filename'];
31 | storename = json['storename'];
32 | size = json['size'];
33 | path = json['path'];
34 | hash = json['hash'];
35 | createdAt = json['created_at'];
36 | url = json['url'];
37 | delete = json['delete'];
38 | page = json['page'];
39 | }
40 |
41 | Map toJson() {
42 | final Map data = new Map();
43 | data['width'] = this.width;
44 | data['height'] = this.height;
45 | data['filename'] = this.filename;
46 | data['storename'] = this.storename;
47 | data['size'] = this.size;
48 | data['path'] = this.path;
49 | data['hash'] = this.hash;
50 | data['created_at'] = this.createdAt;
51 | data['url'] = this.url;
52 | data['delete'] = this.delete;
53 | data['page'] = this.page;
54 | return data;
55 | }
56 | }
--------------------------------------------------------------------------------
/lib/views/album_page/album_page_presenter.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_picgo/model/uploaded.dart';
2 | import 'package:flutter_picgo/resources/table_name_keys.dart';
3 | import 'package:flutter_picgo/utils/image_upload.dart';
4 | import 'package:flutter_picgo/utils/sql.dart';
5 | import 'package:flutter_picgo/utils/strategy/upload_strategy_factory.dart';
6 |
7 | abstract class AlbumPageContract {
8 | void loadUploadedImages(List uploadeds);
9 | void loadError();
10 |
11 | void deleteSuccess(Uploaded uploaded);
12 | void deleteError(String msg);
13 | }
14 |
15 | class AlbumPagePresenter {
16 | AlbumPageContract _view;
17 |
18 | AlbumPagePresenter(this._view);
19 |
20 | doLoadUploadedImages(int limit, int offest) async {
21 | try {
22 | var sql = Sql.setTable(TABLE_NAME_UPLOADED);
23 | var result = await sql.getBySql(null, null,
24 | limit: limit, offset: offest * limit, orderBy: 'id DESC');
25 | List uploadeds = result.map((v) {
26 | return Uploaded.fromMap(v);
27 | }).toList();
28 | _view.loadUploadedImages(uploadeds);
29 | } catch (e) {
30 | _view.loadError();
31 | }
32 | }
33 |
34 | doDeleteImage(Uploaded uploaded) async {
35 | try {
36 | ImageUploadUtils uploader = ImageUploadUtils(
37 | UploadStrategyFactory.getUploadStrategy(uploaded.type));
38 | Uploaded up = await uploader.delete(uploaded);
39 | if (up != null) {
40 | _view.deleteSuccess(uploaded);
41 | } else {
42 | _view.deleteError('删除出错,请重试');
43 | }
44 | } catch (e) {
45 | _view.deleteError('删除出错,请重试 Error >>> $e');
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/lib/utils/strategy/impl/niupic_image_upload.dart:
--------------------------------------------------------------------------------
1 | import 'package:dio/dio.dart';
2 | import 'package:flutter_picgo/api/niupic_api.dart';
3 | import 'package:flutter_picgo/model/uploaded.dart';
4 | import 'package:flutter_picgo/resources/pb_type_keys.dart';
5 | import 'package:flutter_picgo/utils/image_upload.dart';
6 | import 'dart:io';
7 |
8 | import 'package:flutter_picgo/utils/strategy/image_upload_strategy.dart';
9 |
10 | class NiupicImageUpload implements ImageUploadStrategy {
11 | /// 牛图网不支持删除
12 | @override
13 | Future delete(Uploaded uploaded) async {
14 | return uploaded;
15 | }
16 |
17 | @override
18 | Future upload(File file, String renameImage) async {
19 | FormData formData = FormData.fromMap({
20 | "image_field":
21 | await MultipartFile.fromFile(file.path, filename: renameImage),
22 | });
23 | var result = await NiupicApi.upload(formData);
24 | if (result["code"] == 200) {
25 | var uploadedItem = Uploaded(
26 | -1, 'https://${result['data']}', PBTypeKeys.niupic,
27 | info: '');
28 | await ImageUploadUtils.saveUploadedItem(uploadedItem);
29 | return uploadedItem;
30 | } else {
31 | throw new NiupicError(error: '${result['msg']}');
32 | }
33 | }
34 | }
35 |
36 | class NiupicError implements Exception {
37 | NiupicError({
38 | this.error,
39 | });
40 |
41 | dynamic error;
42 |
43 | String get message => (error?.toString() ?? '');
44 |
45 | @override
46 | String toString() {
47 | var msg = 'NiupicError $message';
48 | if (error is Error) {
49 | msg += '\n${error.stackTrace}';
50 | }
51 | return msg;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/lib/api/lsky_api.dart:
--------------------------------------------------------------------------------
1 | import 'package:dio/dio.dart';
2 | import 'package:flutter_picgo/utils/net.dart';
3 | import 'package:path/path.dart' as path;
4 |
5 | class LskyApi {
6 | static Future token(String email, String pwd, String host) async {
7 | String url = path.joinAll([host, 'api/token']);
8 | Response res = await NetUtils.getInstance().post(url, data: {
9 | 'email': email,
10 | 'password': pwd,
11 | });
12 | return res.data;
13 | }
14 |
15 | static Future upload(String token, String host, FormData data) async {
16 | String url = path.joinAll([host, 'api/upload']);
17 | Response res = await NetUtils.getInstance()
18 | .post(url, data: data, options: buildCommonOptions(token));
19 | return res.data;
20 | }
21 |
22 | static Future images(String token, String host, int page,
23 | {int rows = 10}) async {
24 | String url = path.joinAll([host, 'api/images']);
25 | Response res = await NetUtils.getInstance().post(
26 | url,
27 | data: {
28 | 'page': page,
29 | 'rows': rows,
30 | },
31 | options: buildCommonOptions(token),
32 | );
33 | return res.data;
34 | }
35 |
36 | static Future delete(String token, String host, String id) async {
37 | String url = path.joinAll([host, 'api/delete']);
38 | Response res = await NetUtils.getInstance().post(url,
39 | data: {
40 | 'id': id,
41 | },
42 | options: buildCommonOptions(token));
43 | return res.data;
44 | }
45 |
46 | static Options buildCommonOptions(String token) {
47 | return Options(headers: {
48 | 'token': token,
49 | }, contentType: Headers.formUrlEncodedContentType);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/lib/model/github_content.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_picgo/components/manage_item.dart';
2 |
3 | class GithubContent {
4 | FileContentType type;
5 | String encoding;
6 | int size;
7 | String name;
8 | String path;
9 | String content;
10 | String sha;
11 | String url;
12 | String gitUrl;
13 | String htmlUrl;
14 | String downloadUrl;
15 |
16 | GithubContent({
17 | this.type,
18 | this.encoding,
19 | this.size,
20 | this.name,
21 | this.path,
22 | this.content,
23 | this.sha,
24 | this.url,
25 | this.gitUrl,
26 | this.htmlUrl,
27 | this.downloadUrl,
28 | });
29 |
30 | GithubContent.fromJson(Map json) {
31 | type = json['type'] == 'file' ? FileContentType.FILE : FileContentType.DIR;
32 | encoding = json['encoding'];
33 | size = json['size'];
34 | name = json['name'];
35 | path = json['path'];
36 | content = json['content'];
37 | sha = json['sha'];
38 | url = json['url'];
39 | gitUrl = json['git_url'];
40 | htmlUrl = json['html_url'];
41 | downloadUrl = json['download_url'];
42 | }
43 |
44 | Map toJson() {
45 | final Map data = new Map();
46 | data['type'] = this.type == FileContentType.FILE ? 'file' : 'dir';
47 | data['encoding'] = this.encoding;
48 | data['size'] = this.size;
49 | data['name'] = this.name;
50 | data['path'] = this.path;
51 | data['content'] = this.content;
52 | data['sha'] = this.sha;
53 | data['url'] = this.url;
54 | data['git_url'] = this.gitUrl;
55 | data['html_url'] = this.htmlUrl;
56 | data['download_url'] = this.downloadUrl;
57 | return data;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/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/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 flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | use_frameworks!
32 | use_modular_headers!
33 |
34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35 | end
36 |
37 | post_install do |installer|
38 | installer.pods_project.targets.each do |target|
39 | flutter_additional_ios_build_settings(target)
40 | # target.build_configurations.each do |config|
41 | # if config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'].to_f < 8.0
42 | # config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '8.0'
43 | # end
44 | # end
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/test/api/tcyun_api_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_picgo/api/tcyun_api.dart';
2 | import 'package:flutter_test/flutter_test.dart';
3 |
4 | main() {
5 | test('测试生成 KeyTime', () {});
6 |
7 | test('测试生成 SignKey', () {});
8 |
9 | test('测试Api 签名', () async {
10 | try {
11 | await TcyunApi.deleteobject(
12 | 'AKID70e28x4gazd17vKiywkITO1NSWHv6s75',
13 | 'pN0MRGo5HCqDxTqpGPTFlCsHPKzUjhZD',
14 | 'test-1253954259',
15 | 'ap-nanjing',
16 | 'wallhaven-2e9j79.jpg');
17 | } catch (e) {}
18 | });
19 |
20 | test('PostObjecy 提交', () async {
21 | try {
22 | // String pathname = path.joinAll(
23 | // [Directory.current.path, '..\\assets\\' 'images', 'logo.png']);
24 | // String keyTime = TcyunApi.buildKeyTime();
25 | // String policy = TcyunApi.buildPolicy('ap-nanjing', 'logo.png',
26 | // 'AKIDvb0B9rqfeOr44kt2ar46rO2cwzl6JwUk', keyTime);
27 | // TcyunApi.postObject(
28 | // 'test-1253954259',
29 | // 'ap-nanjing',
30 | // 'png',
31 | // FormData.fromMap({
32 | // "key": "logo.png",
33 | // "file": await MultipartFile.fromFile(
34 | // 'C:\\Users\\Administrator\\Desktop\\flutter-picgo\\flutter-picgo\\assets\\images\\logo.png',
35 | // filename: 'logo.png'),
36 | // "policy": base64.encode(utf8.encode(policy)),
37 | // "q-sign-algorithm": "sha1",
38 | // "q-ak": "AKIDvb0B9rqfeOr44kt2ar46rO2cwzl6JwUk",
39 | // "q-key-time": keyTime,
40 | // "q-signature": TcyunApi.buildSignature(
41 | // 'KOEoR1LL5apX1lFRN4VgZB0nJgmdEbie', keyTime, policy)
42 | // }));
43 | } catch (e) {}
44 | });
45 | }
46 |
--------------------------------------------------------------------------------
/lib/resources/theme_colors.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | // Light Theme Color
4 |
5 | const MaterialColor light = MaterialColor(lightPrimaryValue, {
6 | 50: Color(0xFFE9F2FD),
7 | 100: Color(0xFFC7E0F9),
8 | 200: Color(0xFFA2CBF5),
9 | 300: Color(0xFF7DB6F1),
10 | 400: Color(0xFF61A6EE),
11 | 500: Color(lightPrimaryValue),
12 | 600: Color(0xFF3E8EE9),
13 | 700: Color(0xFF3683E5),
14 | 800: Color(0xFF2E79E2),
15 | 900: Color(0xFF1F68DD),
16 | });
17 |
18 | const int lightPrimaryValue = 0xFF4596EB;
19 |
20 | // Dark
21 |
22 | const MaterialColor dark = MaterialColor(_darkPrimaryValue, {
23 | 50: Color(0xFFE2E3E3),
24 | 100: Color(0xFFB8B8B8),
25 | 200: Color(0xFF888989),
26 | 300: Color(0xFF58595A),
27 | 400: Color(0xFF353636),
28 | 500: Color(_darkPrimaryValue),
29 | 600: Color(0xFF0F1011),
30 | 700: Color(0xFF0C0D0E),
31 | 800: Color(0xFF0A0A0B),
32 | 900: Color(0xFF050506),
33 | });
34 | const int _darkPrimaryValue = 0xFF111213;
35 |
36 | // accent
37 | const MaterialColor accent = MaterialColor(_accentValue, {
38 | 50: Color(0xFFFCE9EC),
39 | 100: Color(0xFFF9C9D0),
40 | 200: Color(0xFFF5A5B1),
41 | 300: Color(0xFFF08192),
42 | 400: Color(0xFFED667A),
43 | 500: Color(_accentValue),
44 | 600: Color(0xFFE7445B),
45 | 700: Color(0xFFE43B51),
46 | 800: Color(0xFFE13347),
47 | 900: Color(0xFFDB2335),
48 | });
49 |
50 | const int _accentValue = 0xFFEA4B63;
51 |
52 | // 主题
53 |
54 | final ThemeData lightThemeData = ThemeData(
55 | brightness: Brightness.light,
56 | primarySwatch: light,
57 | primaryColor: light,
58 | accentColor: light,
59 | );
60 |
61 | final ThemeData darkThemeData = ThemeData(
62 | brightness: Brightness.dark,
63 | primarySwatch: dark,
64 | primaryColor: dark,
65 | accentColor: accent,
66 | );
67 |
--------------------------------------------------------------------------------
/lib/views/pb_setting_page/smms_page/smms_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:fluro/fluro.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter/widgets.dart';
6 | import 'package:flutter_picgo/model/config.dart';
7 | import 'package:flutter_picgo/model/smms_config.dart';
8 | import 'package:flutter_picgo/resources/pb_type_keys.dart';
9 | import 'package:flutter_picgo/routers/application.dart';
10 | import 'package:flutter_picgo/routers/routers.dart';
11 | import 'package:flutter_picgo/utils/strings.dart';
12 | import 'package:flutter_picgo/views/pb_setting_page/base_pb_page_state.dart';
13 |
14 | class SMMSPage extends StatefulWidget {
15 | _SMMSPageState createState() => _SMMSPageState();
16 | }
17 |
18 | class _SMMSPageState extends BasePBSettingPageState {
19 |
20 | @override
21 | onLoadConfig(String config) {
22 | List configs = [];
23 | Map map;
24 | if (isBlank(config)) {
25 | map = SMMSConfig().toJson();
26 | } else {
27 | map = SMMSConfig.fromJson(json.decode(config)).toJson();
28 | }
29 | map.forEach((key, value) {
30 | Config config;
31 | if (key == 'token') {
32 | config = Config(
33 | label: '设定Token',
34 | placeholder: 'Token',
35 | needValidate: true,
36 | value: value);
37 | }
38 | config.name = key;
39 | configs.add(config);
40 | });
41 | setConfigs(configs);
42 | }
43 |
44 | @override
45 | String get pbType => PBTypeKeys.smms;
46 |
47 | @override
48 | String get title => 'SM.MS图床';
49 |
50 | @override
51 | bool get isSupportManage => true;
52 |
53 | @override
54 | handleManage() {
55 | Application.router.navigateTo(context,
56 | '${Routes.settingPbSMMSRepo}',
57 | transition: TransitionType.cupertino);
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/docs/design/db.json:
--------------------------------------------------------------------------------
1 | {
2 | "uploaded": [],
3 | "picBed": {
4 | "current": "github",
5 | "list": [
6 | {
7 | "name": "SM.MS图床",
8 | "type": "smms",
9 | "visible": false
10 | },
11 | {
12 | "name": "腾讯云COS",
13 | "type": "tcyun",
14 | "visible": false
15 | },
16 | {
17 | "name": "微博图床",
18 | "type": "weibo",
19 | "visible": false
20 | },
21 | {
22 | "name": "GitHub图床",
23 | "type": "github",
24 | "visible": true
25 | },
26 | {
27 | "name": "七牛图床",
28 | "type": "qiniu",
29 | "visible": true
30 | },
31 | {
32 | "name": "Imgur图床",
33 | "type": "imgur",
34 | "visible": false
35 | },
36 | {
37 | "name": "阿里云OSS",
38 | "type": "aliyun",
39 | "visible": false
40 | },
41 | {
42 | "name": "又拍云图床",
43 | "type": "upyun",
44 | "visible": false
45 | }
46 | ],
47 | "github": {
48 | "branch": "master",
49 | "customUrl": "https://github.static.si-yee.com",
50 | "path": "posts/",
51 | "repo": "hackycy/picture-bed",
52 | "token": "7e84c31bfe3845dc0ababd0fdd1ec4d070cd5212"
53 | }
54 | },
55 | "settings": {
56 | "shortKey": {
57 | "picgo:upload": {
58 | "enable": true,
59 | "key": "CommandOrControl+Shift+P",
60 | "name": "upload",
61 | "label": "快捷上传"
62 | }
63 | },
64 | "server": {
65 | "enable": true,
66 | "host": "127.0.0.1",
67 | "port": 36677
68 | },
69 | "showUpdateTip": false,
70 | "autoRename": true,
71 | "rename": true,
72 | "pasteStyle": "markdown",
73 | "uploadNotification": true
74 | },
75 | "picgoPlugins": {},
76 | "debug": true,
77 | "PICGO_ENV": "GUI",
78 | "needReload": false
79 | }
80 |
--------------------------------------------------------------------------------
/docs/设计文档.md:
--------------------------------------------------------------------------------
1 | # sqlite设计文档
2 |
3 | ## 图床配置表pb_setting
4 |
5 | | 字段 | 类型 | 空 | 默认 | 注释 | 版本 |
6 | | ------- | ------------ | ---- | ---- | --------------------------------- | :-------------------------------: |
7 | | id | int | 否 | | 自增ID | 1+ |
8 | | type | VARCHAR(20) | 否 | | 图床类型 | 1+ |
9 | | name | VARCHAR(50) | 否 | | 图床名称 | 1+ |
10 | | config | VARCHAR(255) | 是 | | 图床配置信息,json格式 | 1+ |
11 | | path | VARCHAR(20) | 否 | | 路由跳转页面路径 | 1+ |
12 | | visible | tinyint(1) | 是 | 1 | 是否在配置中可见,默认为1,即true | 1+ |
13 |
14 | ## 上传图片列表uploaded
15 |
16 | | 字段 | 类型 | 空 | 默认 | 注释 | 版本 |
17 | | ---- | ------------ | ---- | ---- | ---------------------------- | :--------------------------: |
18 | | id | int | 否 | | 自增ID | 1+ |
19 | | path | VARCHAR(255) | 否 | | 图片上传成功后对应的相对路径 | 1+ |
20 | | type | VARCHAR(20) | 否 | | 图床类型 | 1+ |
21 | | info | VARCHAR(255) | 是 | | 图片信息,json格式 | 2+ |
22 |
23 | ## Sqlite升级记录
24 |
25 | | 版本 | 描述 |
26 | | ---- | ------------------------------------------------ |
27 | | 1 | pb_setting,uploaded表初始化,增加Github图床记录 |
28 | | 2 | uploaded新增info列,新增SM.MS图床记录 |
29 | | 3 | 新增Gitee图床记录 |
30 | | 4 | 新增Qiniu图床记录 |
31 | | 5 | 新增阿里云OSS图床记录 |
32 | | 6 | 新增腾讯云COS图床记录 |
33 | | 7 | 新增牛图网图床记录 |
34 | | 8 | 新增兰空图床记录 |
35 | | 9 | 新增又拍云图床记录 |
36 |
37 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "icon-20@2x-ipad.png",
5 | "idiom" : "iphone",
6 | "scale" : "2x",
7 | "size" : "20x20"
8 | },
9 | {
10 | "filename" : "icon-20@3x.png",
11 | "idiom" : "iphone",
12 | "scale" : "3x",
13 | "size" : "20x20"
14 | },
15 | {
16 | "filename" : "icon-29-ipad.png",
17 | "idiom" : "iphone",
18 | "scale" : "1x",
19 | "size" : "29x29"
20 | },
21 | {
22 | "filename" : "icon-29@2x-ipad.png",
23 | "idiom" : "iphone",
24 | "scale" : "2x",
25 | "size" : "29x29"
26 | },
27 | {
28 | "filename" : "icon-29@3x.png",
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "29x29"
32 | },
33 | {
34 | "filename" : "icon-40@2x.png",
35 | "idiom" : "iphone",
36 | "scale" : "2x",
37 | "size" : "40x40"
38 | },
39 | {
40 | "filename" : "icon-40@3x.png",
41 | "idiom" : "iphone",
42 | "scale" : "3x",
43 | "size" : "40x40"
44 | },
45 | {
46 | "filename" : "icon-57.png",
47 | "idiom" : "iphone",
48 | "scale" : "1x",
49 | "size" : "57x57"
50 | },
51 | {
52 | "filename" : "icon-57@2x.png",
53 | "idiom" : "iphone",
54 | "scale" : "2x",
55 | "size" : "57x57"
56 | },
57 | {
58 | "filename" : "icon-60@2x.png",
59 | "idiom" : "iphone",
60 | "scale" : "2x",
61 | "size" : "60x60"
62 | },
63 | {
64 | "filename" : "icon-60@3x.png",
65 | "idiom" : "iphone",
66 | "scale" : "3x",
67 | "size" : "60x60"
68 | },
69 | {
70 | "filename" : "icon-1024.png",
71 | "idiom" : "ios-marketing",
72 | "scale" : "1x",
73 | "size" : "1024x1024"
74 | }
75 | ],
76 | "info" : {
77 | "author" : "xcode",
78 | "version" : 1
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/lib/model/lsky_content.dart:
--------------------------------------------------------------------------------
1 | class LskyContent {
2 | int id;
3 | String strategy;
4 | String path;
5 | String name;
6 | Null aliasName;
7 | String pathname;
8 | String size;
9 | String mime;
10 | String sha1;
11 | String md5;
12 | String ip;
13 | int suspicious;
14 | int uploadTime;
15 | String uploadDate;
16 | String url;
17 |
18 | LskyContent(
19 | {this.id,
20 | this.strategy,
21 | this.path,
22 | this.name,
23 | this.aliasName,
24 | this.pathname,
25 | this.size,
26 | this.mime,
27 | this.sha1,
28 | this.md5,
29 | this.ip,
30 | this.suspicious,
31 | this.uploadTime,
32 | this.uploadDate,
33 | this.url});
34 |
35 | LskyContent.fromJson(Map json) {
36 | id = json['id'];
37 | strategy = json['strategy'];
38 | path = json['path'];
39 | name = json['name'];
40 | aliasName = json['alias_name'];
41 | pathname = json['pathname'];
42 | size = json['size'];
43 | mime = json['mime'];
44 | sha1 = json['sha1'];
45 | md5 = json['md5'];
46 | ip = json['ip'];
47 | suspicious = json['suspicious'];
48 | uploadTime = json['upload_time'];
49 | uploadDate = json['upload_date'];
50 | url = json['url'];
51 | }
52 |
53 | Map toJson() {
54 | final Map data = new Map();
55 | data['id'] = this.id;
56 | data['strategy'] = this.strategy;
57 | data['path'] = this.path;
58 | data['name'] = this.name;
59 | data['alias_name'] = this.aliasName;
60 | data['pathname'] = this.pathname;
61 | data['size'] = this.size;
62 | data['mime'] = this.mime;
63 | data['sha1'] = this.sha1;
64 | data['md5'] = this.md5;
65 | data['ip'] = this.ip;
66 | data['suspicious'] = this.suspicious;
67 | data['upload_time'] = this.uploadTime;
68 | data['upload_date'] = this.uploadDate;
69 | data['url'] = this.url;
70 | return data;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/lib/api/github_api.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'package:dio/dio.dart';
3 | import 'package:flutter_picgo/model/github_config.dart';
4 | import 'package:flutter_picgo/resources/pb_type_keys.dart';
5 | import 'package:flutter_picgo/utils/image_upload.dart';
6 | import 'package:flutter_picgo/utils/net.dart';
7 | import 'package:flutter_picgo/utils/strings.dart';
8 |
9 | class GithubApi {
10 | static const String BASE_URL = 'https://api.github.com/';
11 |
12 | static const String CONTENTS =
13 | 'repos/:owner/:repo/contents/:path'; //PUT DELETE GET
14 |
15 | static Future testToken() async {
16 | Response res = await NetUtils.getInstance().get(BASE_URL);
17 | return res.data;
18 | }
19 |
20 | static Future putContent(String url, data) async {
21 | var op = await oAuth();
22 | Response res = await NetUtils.getInstance()
23 | .put(BASE_URL + url ?? '', data: data, options: op);
24 | return res.data;
25 | }
26 |
27 | static Future deleteContent(String url, data) async {
28 | var op = await oAuth();
29 | Response res = await NetUtils.getInstance()
30 | .delete(BASE_URL + url, data: data, options: op);
31 | return res.data;
32 | }
33 |
34 | static Future getContents(String url, Map params) async {
35 | var op = await oAuth();
36 | Response res = await NetUtils.getInstance()
37 | .get(BASE_URL + url, queryParameters: params, options: op);
38 | return res.data;
39 | }
40 |
41 | /// 获取配置中的Token
42 | static Future oAuth() async {
43 | try {
44 | String configStr = await ImageUploadUtils.getPBConfig(PBTypeKeys.github);
45 | if (!isBlank(configStr)) {
46 | GithubConfig config = GithubConfig.fromJson(json.decode(configStr));
47 | if (config != null && !isBlank(config.token)) {
48 | return Options(headers: {"Authorization": 'Token ${config.token}'});
49 | }
50 | }
51 | return null;
52 | } catch (e) {
53 | return null;
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/lib/utils/strategy/impl/lsky_image_upload.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:dio/dio.dart';
4 | import 'package:flutter_picgo/api/lsky_api.dart';
5 | import 'package:flutter_picgo/model/lsky_config.dart';
6 | import 'package:flutter_picgo/model/uploaded.dart';
7 | import 'package:flutter_picgo/resources/pb_type_keys.dart';
8 | import 'package:flutter_picgo/utils/image_upload.dart';
9 | import 'dart:io';
10 |
11 | import 'package:flutter_picgo/utils/strategy/image_upload_strategy.dart';
12 | import 'package:flutter_picgo/utils/strings.dart';
13 |
14 | class LskyImageUpload implements ImageUploadStrategy {
15 | @override
16 | Future delete(Uploaded uploaded) async {
17 | return uploaded;
18 | }
19 |
20 | @override
21 | Future upload(File file, String renameImage) async {
22 | String configStr = await ImageUploadUtils.getPBConfig(PBTypeKeys.lsky);
23 | if (isBlank(configStr)) {
24 | throw LskyError(error: '读取配置文件错误!请重试');
25 | }
26 | LskyConfig config = LskyConfig.fromJson(json.decode(configStr));
27 | FormData formData = FormData.fromMap({
28 | "image": await MultipartFile.fromFile(file.path, filename: renameImage),
29 | });
30 | var result = await LskyApi.upload(config.token, config.host, formData);
31 | if (result['code'] == 200) {
32 | var uploadedItem =
33 | Uploaded(-1, '${result['data']['url']}', PBTypeKeys.lsky, info: '');
34 | await ImageUploadUtils.saveUploadedItem(uploadedItem);
35 | return uploadedItem;
36 | } else {
37 | throw new LskyError(error: '${result['msg']}');
38 | }
39 | }
40 | }
41 |
42 | class LskyError implements Exception {
43 | LskyError({
44 | this.error,
45 | });
46 |
47 | dynamic error;
48 |
49 | String get message => (error?.toString() ?? '');
50 |
51 | @override
52 | String toString() {
53 | var msg = 'LskyError $message';
54 | if (error is Error) {
55 | msg += '\n${error.stackTrace}';
56 | }
57 | return msg;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/lib/utils/strategy/impl/smms_image_upload.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'package:dio/dio.dart';
3 | import 'package:flutter_picgo/api/smms_api.dart';
4 | import 'package:flutter_picgo/model/uploaded.dart';
5 | import 'package:flutter_picgo/resources/pb_type_keys.dart';
6 | import 'package:flutter_picgo/utils/image_upload.dart';
7 | import 'dart:io';
8 | import 'package:flutter_picgo/utils/strategy/image_upload_strategy.dart';
9 | import 'package:flutter_picgo/utils/strings.dart';
10 |
11 | class SMMSImageUpload implements ImageUploadStrategy {
12 | @override
13 | Future upload(File file, String renameImage) async {
14 | FormData formData = FormData.fromMap({
15 | "smfile": await MultipartFile.fromFile(file.path, filename: renameImage),
16 | });
17 | var result = await SMMSApi.upload(formData);
18 | var resultmap = json.decode(result);
19 | if (resultmap["success"]) {
20 | var uploaded = Uploaded(-1, resultmap["data"]["url"], PBTypeKeys.smms,
21 | info: resultmap["data"]["hash"]);
22 | await ImageUploadUtils.saveUploadedItem(uploaded);
23 | return uploaded;
24 | } else {
25 | throw new SMMSError(error: resultmap['message']);
26 | }
27 | }
28 |
29 | @override
30 | Future delete(Uploaded uploaded) async {
31 | if (!isBlank(uploaded.info)) {
32 | var result = await SMMSApi.delete(uploaded.info);
33 | var resultmap = json.decode(result);
34 | if (resultmap["success"]) {
35 | return uploaded;
36 | } else {
37 | throw new SMMSError(error: resultmap['message']);
38 | }
39 | }
40 | return uploaded;
41 | }
42 | }
43 |
44 | /// SMMSError describes the error info when request failed.
45 | class SMMSError implements Exception {
46 | SMMSError({
47 | this.error,
48 | });
49 |
50 | dynamic error;
51 |
52 | String get message => (error?.toString() ?? '');
53 |
54 | @override
55 | String toString() {
56 | var msg = 'SMMSError $message';
57 | if (error is Error) {
58 | msg += '\n${error.stackTrace}';
59 | }
60 | return msg;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/lib/views/picgo_setting_page/theme_setting_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_picgo/model/theme_state.dart';
3 | import 'package:provider/provider.dart';
4 |
5 | class ThemeSettingPage extends StatefulWidget {
6 | _ThemeSettingPageState createState() => _ThemeSettingPageState();
7 | }
8 |
9 | class _ThemeSettingPageState extends State {
10 | @override
11 | Widget build(BuildContext context) {
12 | return Scaffold(
13 | appBar: AppBar(
14 | centerTitle: true,
15 | title: const Text('主题设置'),
16 | ),
17 | body: Consumer(
18 | builder: (context, themeState, child) {
19 | return ListView(
20 | children: [
21 | ListTile(
22 | title: Text(ThemeState.modeMap[ThemeMode.system]),
23 | trailing: themeState.currentMode == ThemeMode.system
24 | ? Icon(Icons.check)
25 | : null,
26 | onTap: () {
27 | _changeThemeMode(themeState, ThemeMode.system);
28 | },
29 | ),
30 | ListTile(
31 | title: Text(ThemeState.modeMap[ThemeMode.light]),
32 | trailing: themeState.currentMode == ThemeMode.light
33 | ? Icon(Icons.check)
34 | : null,
35 | onTap: () {
36 | _changeThemeMode(themeState, ThemeMode.light);
37 | },
38 | ),
39 | ListTile(
40 | title: Text(ThemeState.modeMap[ThemeMode.dark]),
41 | trailing: themeState.currentMode == ThemeMode.dark
42 | ? Icon(Icons.check)
43 | : null,
44 | onTap: () {
45 | _changeThemeMode(themeState, ThemeMode.dark);
46 | },
47 | )
48 | ],
49 | );
50 | },
51 | ),
52 | );
53 | }
54 |
55 | _changeThemeMode(ThemeState state, ThemeMode mode) {
56 | state.changeThemeState(mode);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/lib/api/smms_api.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:dio/dio.dart';
4 | import 'package:flutter_picgo/model/smms_config.dart';
5 | import 'package:flutter_picgo/resources/pb_type_keys.dart';
6 | import 'package:flutter_picgo/utils/image_upload.dart';
7 | import 'package:flutter_picgo/utils/net.dart';
8 | import 'package:flutter_picgo/utils/strings.dart';
9 |
10 | class SMMSApi {
11 | static const String BASE_URL = 'https://sm.ms/api/v2/';
12 |
13 | static Future getProfile() async {
14 | var op = await oAuth();
15 | Response res =
16 | await NetUtils.getInstance().post(BASE_URL + 'profile', options: op);
17 | return res.data;
18 | }
19 |
20 | static Future upload(FormData formData) async {
21 | var op = await oAuth();
22 | Response res = await NetUtils.getInstance()
23 | .post(BASE_URL + 'upload', data: formData, options: op);
24 | return res.data;
25 | }
26 |
27 | static Future delete(String hash) async {
28 | var op = await oAuth();
29 | Response res = await NetUtils.getInstance()
30 | .get(BASE_URL + 'delete/' + hash ?? '', options: op);
31 | return res.data;
32 | }
33 |
34 | static Future deleteByPath(String path) async {
35 | var op = await oAuth();
36 | Response res = await NetUtils.getInstance().get(path, options: op);
37 | return res.data;
38 | }
39 |
40 | static Future getUploadHistory() async {
41 | var op = await oAuth();
42 | Response res = await NetUtils.getInstance()
43 | .get(BASE_URL + 'upload_history', options: op);
44 | return res.data;
45 | }
46 |
47 | /// 获取配置中的Token
48 | static Future oAuth() async {
49 | try {
50 | String configStr = await ImageUploadUtils.getPBConfig(PBTypeKeys.smms);
51 | if (!isBlank(configStr)) {
52 | SMMSConfig config = SMMSConfig.fromJson(json.decode(configStr));
53 | if (config != null && !isBlank(config.token)) {
54 | return Options(headers: {"Authorization": '${config.token}'});
55 | }
56 | }
57 | return null;
58 | } catch (e) {
59 | return null;
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/lib/views/manage_page/lsky_page/lsky_repo_page_presenter.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:flutter_picgo/api/lsky_api.dart';
4 | import 'package:flutter_picgo/model/lsky_config.dart';
5 | import 'package:flutter_picgo/model/lsky_content.dart';
6 | import 'package:flutter_picgo/resources/pb_type_keys.dart';
7 | import 'package:flutter_picgo/utils/image_upload.dart';
8 | import 'package:flutter_picgo/utils/strings.dart';
9 |
10 | abstract class LskyRepoPageContract {
11 | void loadSuccess(List data, bool isEnd);
12 |
13 | void loadError(String msg);
14 | }
15 |
16 | class LskyRepoPagePresenter {
17 | LskyRepoPageContract _view;
18 |
19 | LskyRepoPagePresenter(this._view);
20 |
21 | doLoadContents(int page) async {
22 | try {
23 | String configStr = await ImageUploadUtils.getPBConfig(PBTypeKeys.lsky);
24 | if (isBlank(configStr)) {
25 | _view.loadError('读取配置错误!');
26 | return;
27 | }
28 | LskyConfig config = LskyConfig.fromJson(json.decode(configStr));
29 | var result = await LskyApi.images(config.token, config.host, page);
30 | if (result['code'] == 200) {
31 | bool isEnd =
32 | result['data']['current_page'] == result['data']['last_page'];
33 | _view.loadSuccess(
34 | (result['data']['data'] as List)
35 | .map((e) => LskyContent.fromJson(e))
36 | .toList(),
37 | isEnd);
38 | } else {
39 | _view.loadError(result['msg']);
40 | }
41 | } catch (e) {
42 | _view.loadError('$e');
43 | }
44 | }
45 |
46 | Future doDeleteContents(String id) async {
47 | try {
48 | String configStr = await ImageUploadUtils.getPBConfig(PBTypeKeys.lsky);
49 | if (isBlank(configStr)) {
50 | return false;
51 | }
52 | LskyConfig config = LskyConfig.fromJson(json.decode(configStr));
53 | var result = await LskyApi.delete(config.token, config.host, id);
54 | if (result['code'] == 200) {
55 | return true;
56 | }
57 | return false;
58 | } catch (e) {
59 | return false;
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/lib/views/pb_setting_page/pb_setting_presenter.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:flutter_picgo/model/pb_setting.dart';
4 | import 'package:flutter_picgo/resources/table_name_keys.dart';
5 | import 'package:flutter_picgo/utils/image_upload.dart';
6 | import 'package:flutter_picgo/utils/sql.dart';
7 | import 'package:flutter_picgo/utils/strings.dart';
8 |
9 | abstract class PBSettingPageContract {
10 | void loadPb(List settings);
11 |
12 | void loadError(String errorMsg);
13 |
14 | void transferError(String e);
15 |
16 | void transferSuccess();
17 |
18 | void exportConfigSuccess(String config);
19 |
20 | void exportConfigError(String message);
21 | }
22 |
23 | class PBSettingPagePresenter {
24 | PBSettingPageContract _view;
25 |
26 | PBSettingPagePresenter(this._view);
27 |
28 | doLoadPb() async {
29 | try {
30 | var sql = Sql.setTable('pb_setting');
31 | var list = await sql.get();
32 | var realList = list.map((map) {
33 | return PBSetting.fromMap(map);
34 | }).toList();
35 | _view.loadPb(realList);
36 | } catch (e) {
37 | _view.loadError('${e.toString()}');
38 | }
39 | }
40 |
41 | /// transfer picgo json config to flutter-picgo
42 | doTransferJson(String jsonStr) async {
43 | try {
44 | Map map = json.decode(jsonStr);
45 | map.forEach((key, value) async {
46 | var config = json.encode(value);
47 | await ImageUploadUtils.savePBConfig(key, config);
48 | });
49 | // success
50 | _view.transferSuccess();
51 | } catch (e) {
52 | _view.transferError('转换失败,请确认配置无误');
53 | }
54 | }
55 |
56 | /// 导出所有配置
57 | doExportConfig() async {
58 | try {
59 | var sql = Sql.setTable(TABLE_NAME_PBSETTING);
60 | var list = await sql.get();
61 | Map map = {};
62 | list.forEach((element) {
63 | var pbItem = PBSetting.fromMap(element);
64 | if (!isBlank(pbItem.config)) {
65 | map[pbItem.type] = json.decode(pbItem.config);
66 | }
67 | });
68 | _view.exportConfigSuccess(json.encode(map));
69 | } catch (e) {
70 | _view.exportConfigError('$e');
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/lib/api/gitee_api.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:dio/dio.dart';
4 | import 'package:flutter_picgo/model/gitee_config.dart';
5 | import 'package:flutter_picgo/resources/pb_type_keys.dart';
6 | import 'package:flutter_picgo/utils/image_upload.dart';
7 | import 'package:flutter_picgo/utils/net.dart';
8 | import 'package:flutter_picgo/utils/strings.dart';
9 |
10 | class GiteeApi {
11 | static const String BASE_URL = 'https://gitee.com/api/v5/';
12 |
13 | static const String KEY_ACCESS_TOKEN = 'access_token';
14 |
15 | static Future testToken() async {
16 | Response res =
17 | await NetUtils.getInstance().get(BASE_URL + 'user', queryParameters: {
18 | KEY_ACCESS_TOKEN: await oAuth(),
19 | });
20 | return res.data;
21 | }
22 |
23 | static Future createFile(String url, Map map) async {
24 | Map realMap = {
25 | KEY_ACCESS_TOKEN: await oAuth(),
26 | };
27 | realMap.addAll(map);
28 | var data = FormData.fromMap(realMap);
29 | Response res =
30 | await NetUtils.getInstance().post(BASE_URL + url, data: data);
31 | map.clear();
32 | map = null;
33 | return res.data;
34 | }
35 |
36 | static Future getContents(String url, Map params) async {
37 | Response res = await NetUtils.getInstance()
38 | .get(BASE_URL + url, queryParameters: params);
39 | return res.data;
40 | }
41 |
42 | static Future deleteFile(String url, Map query) async {
43 | Map realQuery = {
44 | KEY_ACCESS_TOKEN: await oAuth(),
45 | };
46 | realQuery.addAll(query);
47 | Response res = await NetUtils.getInstance()
48 | .delete(BASE_URL + url, queryParameters: realQuery);
49 | return res.data;
50 | }
51 |
52 | /// 获取配置中的Token
53 | static Future oAuth() async {
54 | try {
55 | String configStr = await ImageUploadUtils.getPBConfig(PBTypeKeys.gitee);
56 | if (!isBlank(configStr)) {
57 | GiteeConfig config = GiteeConfig.fromJson(json.decode(configStr));
58 | if (config != null && !isBlank(config.token)) {
59 | return config.token;
60 | }
61 | }
62 | return null;
63 | } catch (e) {
64 | return null;
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/lib/views/pb_setting_page/upyun_page/upyun_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_picgo/model/config.dart';
5 | import 'package:flutter_picgo/model/upyun_config.dart';
6 | import 'package:flutter_picgo/resources/pb_type_keys.dart';
7 | import 'package:flutter_picgo/utils/strings.dart';
8 | import 'package:flutter_picgo/views/pb_setting_page/base_pb_page_state.dart';
9 |
10 | class UpyunPage extends StatefulWidget {
11 | _UpyunPageState createState() => _UpyunPageState();
12 | }
13 |
14 | class _UpyunPageState extends BasePBSettingPageState {
15 | @override
16 | onLoadConfig(String config) {
17 | List configs = [];
18 | Map map;
19 | if (isBlank(config)) {
20 | map = UpyunConfig().toJson();
21 | } else {
22 | map = UpyunConfig.fromJson(json.decode(config)).toJson();
23 | }
24 | map.forEach((key, value) {
25 | Config config;
26 | if (key == 'bucket') {
27 | config = Config(
28 | label: '设定存储空间名',
29 | placeholder: 'Bucket',
30 | needValidate: true,
31 | value: value);
32 | } else if (key == 'operator') {
33 | config = Config(
34 | label: '设定操作员',
35 | placeholder: '例如:me',
36 | needValidate: true,
37 | value: value);
38 | } else if (key == 'password') {
39 | config = Config(
40 | label: '设定操作员密码',
41 | placeholder: '输入密码',
42 | needValidate: true,
43 | value: value);
44 | } else if (key == 'url') {
45 | config = Config(
46 | label: '设定加速域名',
47 | placeholder: '例如https://xxx.yyy.com',
48 | needValidate: true,
49 | value: value);
50 | } else if (key == 'path') {
51 | config = Config(label: '指定存储路径', placeholder: '例如img/', value: value);
52 | } else if (key == 'options') {
53 | config =
54 | Config(label: '设定网址后缀', placeholder: '例如:!imgslim', value: value);
55 | }
56 | config.name = key;
57 | configs.add(config);
58 | });
59 | setConfigs(configs);
60 | }
61 |
62 | @override
63 | String get pbType => PBTypeKeys.upyun;
64 |
65 | @override
66 | String get title => '又拍云图床';
67 | }
68 |
--------------------------------------------------------------------------------
/test/api/aliyun_api_test.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'dart:io';
3 | import 'package:crypto/crypto.dart';
4 | import 'package:dio/dio.dart';
5 | import 'package:flutter_picgo/api/aliyun_api.dart';
6 | import 'package:flutter_picgo/utils/net.dart';
7 | import 'package:flutter_test/flutter_test.dart';
8 | import 'package:path/path.dart' as path;
9 |
10 | main() {
11 | test('测试Content-MD5', () {
12 | var digest = AliyunApi.generateContentMD5('0123456789');
13 | expect(digest, 'eB5eJF1ptWaXm4bijSPyxw==');
14 | });
15 |
16 | test('测试Auth Header签名', () async {
17 | var sign = AliyunApi.buildSignature('LTAIsXml0iczvY0J',
18 | 'yw8eO9Fa9Py2GAPRGG8N3GPKCeKCXl', 'PUT', 'zjyzy', 'test.txt');
19 | try {
20 | await NetUtils.getInstance().put(
21 | 'https://zjyzy.oss-cn-shenzhen.aliyuncs.com/test.txt',
22 | options: Options(headers: {
23 | 'Authorization': sign,
24 | 'Date': HttpDate.format(new DateTime.now()),
25 | }, contentType: 'application/x-www-form-urlencoded'),
26 | );
27 | } on DioError catch (_) {}
28 | });
29 |
30 | test('测试FormData提交图片', () async {
31 | String pathname = path
32 | .joinAll([Directory.current.path, '..', 'assets/images', 'logo.png']);
33 | var policyText = {
34 | "expiration":
35 | "2030-01-01T12:00:00.000Z", // 设置Policy的失效时间,如果超过失效时间,就无法通过此Policy上传文件
36 | "conditions": [
37 | {"key": 'logo.png'} // 设置上传文件的大小限制,如果超过限制,文件上传到OSS会报错
38 | ]
39 | };
40 | var originSign = base64.encode(utf8.encode(json.encode(policyText)));
41 |
42 | var hmacsha1 = Hmac(sha1, utf8.encode('yw8eO9Fa9Py2GAPRGG8N3GPKCeKCXl'));
43 | var sign = hmacsha1.convert(utf8.encode(originSign));
44 | var encodeSign = base64.encode(sign.bytes);
45 | try {
46 | await NetUtils.getInstance().post(
47 | 'https://zjyzy.oss-cn-shenzhen.aliyuncs.com',
48 | data: FormData.fromMap({
49 | 'key': 'logo.png',
50 | 'OSSAccessKeyId': 'LTAIsXml0iczvY0J',
51 | 'policy': originSign,
52 | 'Signature': encodeSign,
53 | 'file': await MultipartFile.fromFile(pathname, filename: 'logo.png')
54 | }),
55 | options: Options(contentType: Headers.formUrlEncodedContentType));
56 | } on DioError catch (_) {}
57 | });
58 | }
59 |
--------------------------------------------------------------------------------
/lib/views/pb_setting_page/tcyun_page/tcyun_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_picgo/model/config.dart';
5 | import 'package:flutter_picgo/model/tcyun_config.dart';
6 | import 'package:flutter_picgo/resources/pb_type_keys.dart';
7 | import 'package:flutter_picgo/utils/strings.dart';
8 | import 'package:flutter_picgo/views/pb_setting_page/base_pb_page_state.dart';
9 |
10 | class TcyunPage extends StatefulWidget {
11 | _TcyunPageState createState() => _TcyunPageState();
12 | }
13 |
14 | class _TcyunPageState extends BasePBSettingPageState {
15 | @override
16 | String get pbType => PBTypeKeys.tcyun;
17 |
18 | @override
19 | onLoadConfig(String config) {
20 | List configs = [];
21 | Map map;
22 | if (isBlank(config)) {
23 | map = TcyunConfig().toJson();
24 | } else {
25 | map = TcyunConfig.fromJson(json.decode(config)).toJson();
26 | }
27 | map.forEach((key, value) {
28 | Config config;
29 | if (key == 'secretId') {
30 | config = Config(
31 | label: '设定SecretId',
32 | placeholder: 'SecretId',
33 | needValidate: true,
34 | value: value);
35 | } else if (key == 'secretKey') {
36 | config = Config(
37 | label: '设定SecretKey',
38 | placeholder: 'SecretKey',
39 | needValidate: true,
40 | value: value);
41 | } else if (key == 'bucket') {
42 | config = Config(
43 | label: '设定存储空间名',
44 | placeholder: '例如test-1253954259',
45 | needValidate: true,
46 | value: value);
47 | } else if (key == 'area') {
48 | config = Config(
49 | label: '确认存储区域',
50 | placeholder: '例如tj',
51 | needValidate: true,
52 | value: value);
53 | } else if (key == 'path') {
54 | config = Config(label: '指定存储路径', placeholder: '例如img/', value: value);
55 | } else if (key == 'customUrl') {
56 | config = Config(
57 | label: '设定自定义域名',
58 | placeholder: '例如:http://xxx.yyy.cn',
59 | value: value);
60 | }
61 | config.name = key;
62 | configs.add(config);
63 | });
64 | setConfigs(configs);
65 | }
66 |
67 | @override
68 | String get title => '腾讯云COS图床';
69 | }
70 |
--------------------------------------------------------------------------------
/lib/utils/strategy/upload_strategy_factory.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_picgo/resources/pb_type_keys.dart';
2 | import 'package:flutter_picgo/utils/strategy/impl/aliyun_image_upload.dart';
3 | import 'package:flutter_picgo/utils/strategy/impl/gitee_image_upload.dart';
4 | import 'package:flutter_picgo/utils/strategy/impl/github_image_upload.dart';
5 | import 'package:flutter_picgo/utils/strategy/image_upload_strategy.dart';
6 | import 'package:flutter_picgo/utils/strategy/impl/lsky_image_upload.dart';
7 | import 'package:flutter_picgo/utils/strategy/impl/niupic_image_upload.dart';
8 | import 'package:flutter_picgo/utils/strategy/impl/qiniu_image_upload.dart';
9 | import 'package:flutter_picgo/utils/strategy/impl/smms_image_upload.dart';
10 | import 'package:flutter_picgo/utils/strategy/impl/tcyun_image_upload.dart';
11 | import 'package:flutter_picgo/utils/strategy/impl/upyun_image_upload.dart';
12 | import 'package:flutter_picgo/utils/strings.dart';
13 |
14 | class UploadStrategyFactory {
15 | static Map cache = {};
16 |
17 | /// UploadStrategy工厂类,负责创建UploadStrategy
18 | static ImageUploadStrategy getUploadStrategy(String type) {
19 | if (isBlank(type)) {
20 | throw new NullThrownError();
21 | }
22 | if (cache[type] == null) {
23 | if (type == PBTypeKeys.github) {
24 | /// Github
25 | cache[type] = new GithubImageUpload();
26 | } else if (type == PBTypeKeys.smms) {
27 | /// SM.MS
28 | cache[type] = new SMMSImageUpload();
29 | } else if (type == PBTypeKeys.gitee) {
30 | /// Gitee
31 | cache[type] = new GiteeImageUpload();
32 | } else if (type == PBTypeKeys.qiniu) {
33 | /// 七牛
34 | cache[type] = new QiniuImageUpload();
35 | } else if (type == PBTypeKeys.aliyun) {
36 | /// 阿里云
37 | cache[type] = new AliyunImageUpload();
38 | } else if (type == PBTypeKeys.tcyun) {
39 | /// 腾讯云
40 | cache[type] = new TcyunImageUpload();
41 | } else if (type == PBTypeKeys.niupic) {
42 | /// 牛图网
43 | cache[type] = new NiupicImageUpload();
44 | } else if (type == PBTypeKeys.lsky) {
45 | /// 兰空
46 | cache[type] = new LskyImageUpload();
47 | } else if (type == PBTypeKeys.upyun) {
48 | /// 又拍云
49 | cache[type] = new UpyunImageUpload();
50 | }
51 | }
52 | return cache[type];
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:fluro/fluro.dart';
3 | import 'package:flutter_picgo/model/theme_state.dart';
4 | import 'package:flutter_picgo/resources/theme_colors.dart';
5 | import 'package:flutter_picgo/routers/application.dart';
6 | import 'package:flutter_picgo/routers/routers.dart';
7 | import 'package:flutter_picgo/utils/db_provider.dart';
8 | import 'package:flutter_picgo/utils/local_notification.dart';
9 | import 'package:provider/provider.dart';
10 |
11 | ThemeState themeState;
12 |
13 | Future main() async {
14 | /// needed if you intend to initialize in the `main` function
15 | WidgetsFlutterBinding.ensureInitialized();
16 | final provider = DbProvider();
17 | await provider.init();
18 |
19 | /// notification initialization
20 | LocalNotificationUtil.getInstance().initialization();
21 |
22 | /// theme initialization
23 | themeState = ThemeState();
24 |
25 | /// run App
26 | runApp(App());
27 | }
28 |
29 | class App extends StatefulWidget {
30 | App() {
31 | final router = new FluroRouter();
32 | Routes.configureRoutes(router);
33 | Application.router = router;
34 | }
35 |
36 | _AppState createState() => _AppState();
37 | }
38 |
39 | class _AppState extends State {
40 | // This widget is the root of your application.
41 | @override
42 | Widget build(BuildContext context) {
43 | return MultiProvider(
44 | providers: [
45 | ChangeNotifierProvider(
46 | create: (context) => themeState,
47 | ),
48 | ],
49 | child: Consumer(
50 | builder: (context, state, child) {
51 | return state.currentMode == ThemeMode.system
52 | ? MaterialApp(
53 | debugShowCheckedModeBanner: false,
54 | theme: lightThemeData,
55 | darkTheme: darkThemeData,
56 | initialRoute: '/',
57 | onGenerateRoute: Application.router.generator,
58 | )
59 | : MaterialApp(
60 | debugShowCheckedModeBanner: false,
61 | theme: state.currentMode == ThemeMode.light
62 | ? lightThemeData
63 | : darkThemeData,
64 | initialRoute: '/',
65 | onGenerateRoute: Application.router.generator,
66 | );
67 | },
68 | ),
69 | );
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/lib/views/pb_setting_page/aliyun_page/aliyun_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_picgo/model/aliyun_config.dart';
5 | import 'package:flutter_picgo/model/config.dart';
6 | import 'package:flutter_picgo/resources/pb_type_keys.dart';
7 | import 'package:flutter_picgo/utils/strings.dart';
8 | import 'package:flutter_picgo/views/pb_setting_page/base_pb_page_state.dart';
9 |
10 | class AliyunPage extends StatefulWidget {
11 | _AliyunPageState createState() => _AliyunPageState();
12 | }
13 |
14 | class _AliyunPageState extends BasePBSettingPageState {
15 | @override
16 | String get pbType => PBTypeKeys.aliyun;
17 |
18 | @override
19 | onLoadConfig(String config) {
20 | List configs = [];
21 | Map map;
22 | if (isBlank(config)) {
23 | map = AliyunConfig().toJson();
24 | } else {
25 | map = AliyunConfig.fromJson(json.decode(config)).toJson();
26 | }
27 | map.forEach((key, value) {
28 | Config config;
29 | if (key == 'accessKeyId') {
30 | config = Config(
31 | label: '设定KeyId',
32 | placeholder: 'AccessKeyId',
33 | needValidate: true,
34 | value: value);
35 | } else if (key == 'accessKeySecret') {
36 | config = Config(
37 | label: '设定KeySecret',
38 | placeholder: 'AccessKeySecret',
39 | needValidate: true,
40 | value: value);
41 | } else if (key == 'bucket') {
42 | config = Config(
43 | label: '设定存储空间名',
44 | placeholder: 'Bucket',
45 | needValidate: true,
46 | value: value);
47 | } else if (key == 'customUrl') {
48 | config = Config(
49 | label: '设定自定义域名',
50 | placeholder: '例如:http://xxx.yyy.cn',
51 | value: value);
52 | } else if (key == 'area') {
53 | config = Config(
54 | label: '确认存储区域',
55 | placeholder: '例如oss-cn-beijing',
56 | needValidate: true,
57 | value: value);
58 | } else if (key == 'options') {
59 | config =
60 | Config(label: '设定网址后缀', placeholder: '例如?imageslim', value: value);
61 | } else if (key == 'path') {
62 | config = Config(label: '指定存储路径', placeholder: '例如img/', value: value);
63 | }
64 | config.name = key;
65 | configs.add(config);
66 | });
67 | setConfigs(configs);
68 | }
69 |
70 | @override
71 | String get title => '阿里云OSS图床';
72 | }
73 |
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | Flutter-PicGo
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | $(FLUTTER_BUILD_NAME)
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSRequiresIPhoneOS
24 |
25 | NSAppleMusicUsageDescription
26 | Media access needed for a better experience.
27 | NSCalendarsUsageDescription
28 | Calendars access needed for a better experience.
29 | NSCameraUsageDescription
30 | 如果不允许,你将无法访问相机进行扫描二维码~
31 | NSContactsUsageDescription
32 | Contacts access needed for a better experience.
33 | NSLocationAlwaysUsageDescription
34 | Location access needed for a better experience.
35 | NSLocationWhenInUseUsageDescription
36 | Location access needed for a better experience.
37 | NSMotionUsageDescription
38 | Motion access needed for a better experience.
39 | NSPhotoLibraryUsageDescription
40 | 如果不允许,你将无法访问系统相册进行选择照片上传至图床以及获取上传后的照片链接~
41 | NSSpeechRecognitionUsageDescription
42 | Speech access needed for a better experience.
43 | UILaunchStoryboardName
44 | LaunchScreen
45 | UIMainStoryboardFile
46 | Main
47 | UISupportedInterfaceOrientations
48 |
49 | UIInterfaceOrientationPortrait
50 |
51 | UISupportedInterfaceOrientations~ipad
52 |
53 | UIInterfaceOrientationPortrait
54 | UIInterfaceOrientationPortraitUpsideDown
55 | UIInterfaceOrientationLandscapeLeft
56 | UIInterfaceOrientationLandscapeRight
57 |
58 | UIViewControllerBasedStatusBarAppearance
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/lib/views/manage_page/github_page/github_repo_page_presenter.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'package:flutter_picgo/api/github_api.dart';
3 | import 'package:flutter_picgo/model/github_config.dart';
4 | import 'package:flutter_picgo/model/github_content.dart';
5 | import 'package:flutter_picgo/resources/pb_type_keys.dart';
6 | import 'package:flutter_picgo/utils/image_upload.dart';
7 | import 'package:flutter_picgo/utils/strings.dart';
8 | import 'package:path/path.dart' as pathutil;
9 |
10 | abstract class GithubRepoPageContract {
11 | void loadSuccess(List data);
12 |
13 | void loadError(String msg);
14 | }
15 |
16 | class GithubRepoPagePresenter {
17 | GithubRepoPageContract _view;
18 |
19 | GithubRepoPagePresenter(this._view);
20 |
21 | doLoadContents(String path, String prePath) async {
22 | try {
23 | String configStr = await ImageUploadUtils.getPBConfig(PBTypeKeys.github);
24 | GithubConfig config = GithubConfig.fromJson(json.decode(configStr));
25 | if (isBlank(config.branch) ||
26 | isBlank(config.repo) ||
27 | isBlank(config.token)) {
28 | _view.loadError('读取配置错误!');
29 | return;
30 | }
31 | String realUrl = pathutil.joinAll([
32 | 'repos',
33 | config.repo,
34 | 'contents',
35 | prePath ?? '',
36 | path == '/' ? '' : path
37 | ]);
38 | List result =
39 | await GithubApi.getContents(realUrl, {"ref": config.branch});
40 | var data = result.map((e) {
41 | return GithubContent.fromJson(e);
42 | }).toList();
43 | _view.loadSuccess(data);
44 | } catch (e) {
45 | _view.loadError('$e');
46 | }
47 | }
48 |
49 | Future doDeleteContents(
50 | String path, String prePath, String name, String sha) async {
51 | try {
52 | String configStr = await ImageUploadUtils.getPBConfig(PBTypeKeys.github);
53 | GithubConfig config = GithubConfig.fromJson(json.decode(configStr));
54 | if (isBlank(config.repo) || isBlank(config.token)) {
55 | _view.loadError('读取配置错误!');
56 | return false;
57 | }
58 | String url = pathutil.joinAll([
59 | 'repos',
60 | config.repo,
61 | 'contents',
62 | prePath ?? '',
63 | path == '/' ? '' : path,
64 | name
65 | ]);
66 | await GithubApi.deleteContent(url, {
67 | "message": 'DELETE BY Flutter-PicGo',
68 | "sha": sha,
69 | "branch": config.branch
70 | });
71 | return true;
72 | } catch (e) {
73 | return false;
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/lib/views/pb_setting_page/github_page/github_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:fluro/fluro.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter_picgo/model/config.dart';
6 | import 'package:flutter_picgo/model/github_config.dart';
7 | import 'package:flutter_picgo/resources/pb_type_keys.dart';
8 | import 'package:flutter_picgo/routers/application.dart';
9 | import 'package:flutter_picgo/routers/routers.dart';
10 | import 'package:flutter_picgo/utils/strings.dart';
11 | import 'package:flutter_picgo/views/pb_setting_page/base_pb_page_state.dart';
12 |
13 | class GithubPage extends StatefulWidget {
14 | @override
15 | _GithubPageState createState() => _GithubPageState();
16 | }
17 |
18 | class _GithubPageState extends BasePBSettingPageState {
19 | @override
20 | onLoadConfig(String config) {
21 | List configs = [];
22 | Map map;
23 | if (isBlank(config)) {
24 | map = GithubConfig().toJson();
25 | } else {
26 | map = GithubConfig.fromJson(json.decode(config)).toJson();
27 | }
28 | map.forEach((key, value) {
29 | Config config;
30 | if (key == 'repo') {
31 | config = Config(
32 | label: '设定仓库名',
33 | placeholder: '例如 hackycy/picBed',
34 | needValidate: true,
35 | value: value);
36 | } else if (key == 'token') {
37 | config = Config(
38 | label: '设定Token',
39 | placeholder: 'Token',
40 | needValidate: true,
41 | value: value);
42 | } else if (key == 'customUrl') {
43 | config = Config(
44 | label: '设定自定义域名',
45 | placeholder: '例如:http://xxx.yyy.cloudcdn.cn',
46 | value: value);
47 | } else if (key == 'branch') {
48 | config = Config(
49 | label: '确认分支名',
50 | placeholder: '例如 master',
51 | value: value,
52 | needValidate: true);
53 | } else if (key == 'path') {
54 | config = Config(label: '指定存储路径', placeholder: '例如img/', value: value);
55 | }
56 | config.name = key;
57 | configs.add(config);
58 | });
59 | setConfigs(configs);
60 | }
61 |
62 | @override
63 | String get pbType => PBTypeKeys.github;
64 |
65 | @override
66 | String get title => 'Github图床';
67 |
68 | @override
69 | bool get isSupportManage => true;
70 |
71 | @override
72 | handleManage() {
73 | Application.router.navigateTo(context,
74 | '${Routes.settingPbGitubRepo}?path=${Uri.encodeComponent("/")}',
75 | transition: TransitionType.cupertino);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/lib/views/manage_page/gitee_page/gitee_repo_page_presenter.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'package:flutter_picgo/api/gitee_api.dart';
3 | import 'package:flutter_picgo/model/gitee_config.dart';
4 | import 'package:flutter_picgo/model/gitee_content.dart';
5 | import 'package:flutter_picgo/resources/pb_type_keys.dart';
6 | import 'package:flutter_picgo/utils/image_upload.dart';
7 | import 'package:flutter_picgo/utils/strings.dart';
8 | import 'package:path/path.dart' as pathutil;
9 |
10 | abstract class GiteeRepoPageContract {
11 | void loadSuccess(List data);
12 |
13 | void loadError(String msg);
14 | }
15 |
16 | class GiteeRepoPagePresenter {
17 | GiteeRepoPageContract _view;
18 |
19 | GiteeRepoPagePresenter(this._view);
20 |
21 | doLoadContents(String path, String prePath) async {
22 | try {
23 | String configStr = await ImageUploadUtils.getPBConfig(PBTypeKeys.gitee);
24 | GiteeConfig config = GiteeConfig.fromJson(json.decode(configStr));
25 | if (isBlank(config.repo) || isBlank(config.token)) {
26 | _view.loadError('读取配置错误!');
27 | return;
28 | }
29 | String realUrl = pathutil.joinAll([
30 | 'repos',
31 | config.owner,
32 | config.repo,
33 | 'contents',
34 | prePath ?? '',
35 | path == '/' ? '' : path
36 | ]);
37 | List result = await GiteeApi.getContents(realUrl,
38 | isBlank(config.branch) ? null : {"ref": config.branch ?? ''});
39 | var data = result.map((e) {
40 | return GiteeContent.fromJson(e);
41 | }).toList();
42 | _view.loadSuccess(data);
43 | } catch (e) {
44 | _view.loadError('$e');
45 | }
46 | }
47 |
48 | Future doDeleteContents(
49 | String path, String prePath, String name, String sha) async {
50 | try {
51 | String configStr = await ImageUploadUtils.getPBConfig(PBTypeKeys.gitee);
52 | GiteeConfig config = GiteeConfig.fromJson(json.decode(configStr));
53 | if (isBlank(config.repo) || isBlank(config.token)) {
54 | _view.loadError('读取配置错误!');
55 | return false;
56 | }
57 | String url = pathutil.joinAll([
58 | 'repos',
59 | config.owner,
60 | config.repo,
61 | 'contents',
62 | prePath ?? '',
63 | path == '/' ? '' : path,
64 | name
65 | ]);
66 | Map query = {
67 | "message": "DELETE BY Flutter-PicGo",
68 | "sha": sha,
69 | };
70 | if (!isBlank(config.branch)) {
71 | query["branch"] = config.branch;
72 | }
73 | await GiteeApi.deleteFile(url, query);
74 | return true;
75 | } catch (e) {
76 | return false;
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/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 | //Get Key Sign Info
25 | def keystoreProperties = new Properties()
26 | def keystorePropertiesFile = rootProject.file('keystore/key.properties')
27 | if (keystorePropertiesFile.exists()) {
28 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
29 | }
30 |
31 | apply plugin: 'com.android.application'
32 | apply plugin: 'kotlin-android'
33 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
34 |
35 | android {
36 | compileSdkVersion 28
37 |
38 | sourceSets {
39 | main.java.srcDirs += 'src/main/kotlin'
40 | }
41 |
42 | lintOptions {
43 | disable 'InvalidPackage'
44 | }
45 |
46 | defaultConfig {
47 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
48 | applicationId "com.siyee.flutterpicgo"
49 | minSdkVersion 19
50 | targetSdkVersion 28
51 | versionCode flutterVersionCode.toInteger()
52 | versionName flutterVersionName
53 |
54 | // support multidex
55 | multiDexEnabled true
56 | }
57 |
58 | signingConfigs {
59 | release {
60 | keyAlias keystoreProperties['keyAlias']
61 | keyPassword keystoreProperties['keyPassword']
62 | storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
63 | storePassword keystoreProperties['storePassword']
64 | }
65 | }
66 |
67 | buildTypes {
68 | release {
69 | // TODO: Add your own signing config for the release build.
70 | // Signing with the debug keys for now, so `flutter run --release` works.
71 | signingConfig signingConfigs.release
72 | }
73 | }
74 | }
75 |
76 | flutter {
77 | source '../..'
78 | }
79 |
80 | dependencies {
81 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
82 | }
83 |
--------------------------------------------------------------------------------
/lib/views/pb_setting_page/gitee_page/gitee_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:fluro/fluro.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter_picgo/model/config.dart';
6 | import 'package:flutter_picgo/model/gitee_config.dart';
7 | import 'package:flutter_picgo/resources/pb_type_keys.dart';
8 | import 'package:flutter_picgo/routers/application.dart';
9 | import 'package:flutter_picgo/routers/routers.dart';
10 | import 'package:flutter_picgo/utils/strings.dart';
11 | import 'package:flutter_picgo/views/pb_setting_page/base_pb_page_state.dart';
12 |
13 | class GiteePage extends StatefulWidget {
14 | @override
15 | _GiteePageState createState() => _GiteePageState();
16 | }
17 |
18 | class _GiteePageState extends BasePBSettingPageState {
19 | @override
20 | onLoadConfig(String config) {
21 | List configs = [];
22 | Map map;
23 | if (isBlank(config)) {
24 | map = GiteeConfig().toJson();
25 | } else {
26 | map = GiteeConfig.fromJson(json.decode(config)).toJson();
27 | }
28 | map.forEach((key, value) {
29 | Config config;
30 | if (key == 'owner') {
31 | config = Config(
32 | label: '设定仓库所属空间地址',
33 | placeholder: '例如 hackycy',
34 | needValidate: true,
35 | value: value);
36 | } else if (key == 'repo') {
37 | config = Config(
38 | label: '设定仓库名',
39 | placeholder: '例如 picBed',
40 | needValidate: true,
41 | value: value);
42 | } else if (key == 'token') {
43 | config = Config(
44 | label: '设定Token',
45 | placeholder: 'Token',
46 | needValidate: true,
47 | value: value);
48 | } else if (key == 'customUrl') {
49 | config = Config(
50 | label: '设定自定义域名',
51 | placeholder: '例如:http://xxx.yyy.cloudcdn.cn,不推荐',
52 | value: value);
53 | } else if (key == 'branch') {
54 | config = Config(
55 | label: '确认分支名', placeholder: '不填默认master,建议填写', value: value);
56 | } else if (key == 'path') {
57 | config = Config(label: '指定存储路径', placeholder: '例如img/', value: value);
58 | }
59 | config.name = key;
60 | configs.add(config);
61 | });
62 | setConfigs(configs);
63 | }
64 |
65 | @override
66 | String get pbType => PBTypeKeys.gitee;
67 |
68 | @override
69 | String get title => 'Gitee图床';
70 |
71 | @override
72 | bool get isSupportManage => true;
73 |
74 | @override
75 | handleManage() {
76 | Application.router.navigateTo(context,
77 | '${Routes.settingPbGiteeRepo}?path=${Uri.encodeComponent("/")}',
78 | transition: TransitionType.cupertino);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/lib/views/pb_setting_page/qiniu_page/qiniu_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:fluro/fluro.dart';
4 | import 'package:flutter/cupertino.dart';
5 | import 'package:flutter/material.dart';
6 | import 'package:flutter_picgo/model/config.dart';
7 | import 'package:flutter_picgo/model/qiniu_config.dart';
8 | import 'package:flutter_picgo/resources/pb_type_keys.dart';
9 | import 'package:flutter_picgo/routers/application.dart';
10 | import 'package:flutter_picgo/routers/routers.dart';
11 | import 'package:flutter_picgo/utils/strings.dart';
12 | import 'package:flutter_picgo/views/pb_setting_page/base_pb_page_state.dart';
13 |
14 | class QiniuPage extends StatefulWidget {
15 | _QiniuPageState createState() => _QiniuPageState();
16 | }
17 |
18 | class _QiniuPageState extends BasePBSettingPageState {
19 | @override
20 | String get pbType => PBTypeKeys.qiniu;
21 |
22 | @override
23 | onLoadConfig(String config) {
24 | List configs = [];
25 | Map map;
26 | if (isBlank(config)) {
27 | map = QiniuConfig().toJson();
28 | } else {
29 | map = QiniuConfig.fromJson(json.decode(config)).toJson();
30 | }
31 | map.forEach((key, value) {
32 | Config config;
33 | if (key == 'accessKey') {
34 | config = Config(
35 | label: '设定AccessKey',
36 | placeholder: 'AccessKey',
37 | needValidate: true,
38 | value: value);
39 | } else if (key == 'secretKey') {
40 | config = Config(
41 | label: '设定SecretKey',
42 | placeholder: 'SecretKey',
43 | needValidate: true,
44 | value: value);
45 | } else if (key == 'bucket') {
46 | config = Config(
47 | label: '设定存储空间名',
48 | placeholder: 'Bucket',
49 | needValidate: true,
50 | value: value);
51 | } else if (key == 'url') {
52 | config = Config(
53 | label: '设定访问网址',
54 | placeholder: '例如:http://xxx.yyy.cloudcdn.cn',
55 | needValidate: true,
56 | value: value);
57 | } else if (key == 'area') {
58 | config = Config(
59 | label: '确认存储区域',
60 | placeholder: '例如z0',
61 | needValidate: true,
62 | value: value);
63 | } else if (key == 'options') {
64 | config =
65 | Config(label: '设定网址后缀', placeholder: '例如?imageslim', value: value);
66 | } else if (key == 'path') {
67 | config = Config(label: '指定存储路径', placeholder: '例如img/', value: value);
68 | }
69 | config.name = key;
70 | configs.add(config);
71 | });
72 | setConfigs(configs);
73 | }
74 |
75 | @override
76 | String get title => '七牛图床';
77 |
78 | @override
79 | bool get isSupportManage => true;
80 |
81 | @override
82 | handleManage() {
83 | Application.router.navigateTo(context,
84 | '${Routes.settingPbQiniuRepo}?path=${Uri.encodeComponent("/")}',
85 | transition: TransitionType.cupertino);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
18 |
25 |
29 |
33 |
38 |
42 |
43 |
44 |
45 |
46 |
47 |
49 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/lib/components/loading.dart:
--------------------------------------------------------------------------------
1 | //
2 | // Created with Android Studio.
3 | // User: 三帆
4 | // Date: 07/08/2019
5 | // Time: 08:40
6 | // email: sanfan.hx@alibaba-inc.com
7 | // tartget: 代码获取自: https://blog.csdn.net/O_time/article/details/86496537
8 | //
9 | import 'dart:async';
10 | import 'package:flutter/material.dart';
11 |
12 | // ignore: must_be_immutable
13 | class NetLoadingDialog extends StatefulWidget {
14 | String loadingText;
15 | bool outsideDismiss;
16 | bool loading;
17 | Function dismissCallback;
18 | Future requestCallBack;
19 |
20 | NetLoadingDialog(
21 | {Key key,
22 | this.loadingText = "loading...",
23 | this.outsideDismiss = false,
24 | this.dismissCallback,
25 | this.loading,
26 | this.requestCallBack})
27 | : super(key: key);
28 |
29 | @override
30 | State createState() => _LoadingDialog();
31 | }
32 |
33 | class _LoadingDialog extends State {
34 | _dismissDialog() {
35 | if (widget.dismissCallback != null) {
36 | widget.dismissCallback();
37 | }
38 | Navigator.of(context).pop();
39 | }
40 |
41 | @override
42 | void initState() {
43 | super.initState();
44 | if (widget.requestCallBack != null) {
45 | widget.requestCallBack.then((_) {
46 | Navigator.pop(context);
47 | }).catchError((_) {
48 | Navigator.pop(context);
49 | });
50 | }
51 | }
52 |
53 | @override
54 | Widget build(BuildContext context) {
55 | if (!widget.loading) {
56 | return Container();
57 | }
58 | return new GestureDetector(
59 | onTap: widget.outsideDismiss ? _dismissDialog : null,
60 | child: Material(
61 | type: MaterialType.transparency,
62 | child: new Center(
63 | child: new SizedBox(
64 | width: 120.0,
65 | height: 120.0,
66 | child: new Container(
67 | decoration: ShapeDecoration(
68 | color: Theme.of(context).brightness == Brightness.dark
69 | ? Colors.black
70 | : Colors.white,
71 | shape: RoundedRectangleBorder(
72 | borderRadius: BorderRadius.all(
73 | Radius.circular(8.0),
74 | ),
75 | ),
76 | ),
77 | child: new Column(
78 | mainAxisAlignment: MainAxisAlignment.center,
79 | crossAxisAlignment: CrossAxisAlignment.center,
80 | children: [
81 | new CircularProgressIndicator(),
82 | new Padding(
83 | padding: const EdgeInsets.only(
84 | top: 20.0,
85 | ),
86 | child: new Text(
87 | widget.loadingText,
88 | style: new TextStyle(fontSize: 12.0),
89 | ),
90 | ),
91 | ],
92 | ),
93 | ),
94 | ),
95 | ),
96 | ),
97 | );
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/lib/utils/strategy/impl/upyun_image_upload.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:flutter_picgo/api/upyun_api.dart';
4 | import 'package:flutter_picgo/model/uploaded.dart';
5 | import 'package:flutter_picgo/model/upyun_config.dart';
6 | import 'package:flutter_picgo/resources/pb_type_keys.dart';
7 | import 'package:flutter_picgo/utils/image_upload.dart';
8 | import 'dart:io';
9 | import 'package:path/path.dart';
10 | import 'package:flutter_picgo/utils/strategy/image_upload_strategy.dart';
11 | import 'package:flutter_picgo/utils/strings.dart';
12 |
13 | class UpyunImageUpload implements ImageUploadStrategy {
14 | @override
15 | Future delete(Uploaded uploaded) async {
16 | UpyunUploadedInfo info;
17 | try {
18 | info = UpyunUploadedInfo.fromJson(json.decode(uploaded.info));
19 | } catch (e) {}
20 | if (info != null) {
21 | await UpyunApi.deleteObject(
22 | info.bucket, info.operator, info.password, info.key);
23 | }
24 | return uploaded;
25 | }
26 |
27 | @override
28 | Future upload(File file, String renameImage) async {
29 | String configStr = await ImageUploadUtils.getPBConfig(PBTypeKeys.upyun);
30 | if (isBlank(configStr)) {
31 | throw UpyunError(error: '读取配置文件错误!请重试');
32 | }
33 | UpyunConfig config = UpyunConfig.fromJson(json.decode(configStr));
34 | await UpyunApi.putObject(
35 | file, config.operator, config.password, renameImage, config.bucket,
36 | path: config.path);
37 | String wholeKey = joinAll([config.path ?? '', renameImage]);
38 | String imagePath =
39 | joinAll([config.url, '$wholeKey${config.options ?? ''}']);
40 | var uploadedItem = Uploaded(-1, '$imagePath', PBTypeKeys.upyun,
41 | info: json.encode(UpyunUploadedInfo(
42 | operator: config.operator,
43 | password: config.password,
44 | bucket: config.bucket,
45 | key: wholeKey,
46 | )));
47 | await ImageUploadUtils.saveUploadedItem(uploadedItem);
48 | return uploadedItem;
49 | }
50 | }
51 |
52 | class UpyunError implements Exception {
53 | UpyunError({
54 | this.error,
55 | });
56 |
57 | dynamic error;
58 |
59 | String get message => (error?.toString() ?? '');
60 |
61 | @override
62 | String toString() {
63 | var msg = 'UpyunError $message';
64 | if (error is Error) {
65 | msg += '\n${error.stackTrace}';
66 | }
67 | return msg;
68 | }
69 | }
70 |
71 | class UpyunUploadedInfo {
72 | String operator;
73 | String bucket;
74 | String password;
75 | String key;
76 |
77 | UpyunUploadedInfo({this.operator, this.bucket, this.password, this.key});
78 |
79 | UpyunUploadedInfo.fromJson(Map json) {
80 | operator = json['operator'];
81 | bucket = json['bucket'];
82 | password = json['password'];
83 | key = json['key'];
84 | }
85 |
86 | Map toJson() {
87 | final Map data = new Map();
88 | data['operator'] = this.operator;
89 | data['bucket'] = this.bucket;
90 | data['password'] = this.password;
91 | data['key'] = this.key;
92 | return data;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/lib/utils/net.dart:
--------------------------------------------------------------------------------
1 | import 'package:dio/dio.dart';
2 | import 'package:flutter_picgo/api/qiniu_api.dart';
3 | import 'package:flutter_picgo/api/tcyun_api.dart';
4 | import 'package:flutter_picgo/api/upyun_api.dart';
5 |
6 | const bool inProduction = const bool.fromEnvironment("dart.vm.product");
7 |
8 | class NetUtils {
9 | Dio _dio;
10 | static NetUtils _instance;
11 |
12 | NetUtils._internal() {
13 | _dio = new Dio(BaseOptions(
14 | connectTimeout: 30000, receiveTimeout: 30000, sendTimeout: 30000));
15 |
16 | /// Tcyun Interceptor
17 | dio.interceptors.add(TcyunInterceptor());
18 |
19 | /// Upyun Interceptor
20 | dio.interceptors.add(UpyunInterceptor());
21 |
22 | /// Qiniu Interceptor
23 | dio.interceptors.add(QiniuInterceptor());
24 |
25 | /// Log Interceptor
26 | if (!inProduction) {
27 | /// Log
28 | dio.interceptors
29 | .add(LogInterceptor(requestBody: true, responseBody: true));
30 | }
31 | }
32 |
33 | Dio get dio => _dio;
34 |
35 | static NetUtils getInstance() {
36 | if (_instance == null) {
37 | _instance = NetUtils._internal();
38 | }
39 | return _instance;
40 | }
41 |
42 | /// get method
43 | Future get(String path,
44 | {Map queryParameters,
45 | Options options,
46 | CancelToken cancelToken,
47 | ProgressCallback onReceiveProgress}) async {
48 | return await dio.get(path,
49 | queryParameters: queryParameters,
50 | options: options,
51 | cancelToken: cancelToken,
52 | onReceiveProgress: onReceiveProgress);
53 | }
54 |
55 | /// post method
56 | Future post(
57 | String path, {
58 | data,
59 | Map queryParameters,
60 | Options options,
61 | CancelToken cancelToken,
62 | ProgressCallback onSendProgress,
63 | ProgressCallback onReceiveProgress,
64 | }) async {
65 | return await dio.post(path,
66 | data: data,
67 | queryParameters: queryParameters,
68 | options: options,
69 | cancelToken: cancelToken,
70 | onSendProgress: onSendProgress,
71 | onReceiveProgress: onReceiveProgress);
72 | }
73 |
74 | /// put method
75 | Future put(
76 | String path, {
77 | data,
78 | Map queryParameters,
79 | Options options,
80 | CancelToken cancelToken,
81 | ProgressCallback onSendProgress,
82 | ProgressCallback onReceiveProgress,
83 | }) async {
84 | return await dio.put(path,
85 | data: data,
86 | queryParameters: queryParameters,
87 | options: options,
88 | cancelToken: cancelToken,
89 | onSendProgress: onSendProgress,
90 | onReceiveProgress: onReceiveProgress);
91 | }
92 |
93 | /// delete method
94 | Future delete(String path,
95 | {data,
96 | Map queryParameters,
97 | Options options,
98 | CancelToken cancelToken}) async {
99 | return await dio.delete(path,
100 | data: data,
101 | queryParameters: queryParameters,
102 | options: options,
103 | cancelToken: cancelToken);
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/lib/api/upyun_api.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'dart:io';
3 |
4 | import 'package:crypto/crypto.dart';
5 | import 'package:dio/dio.dart';
6 | import 'package:flutter_picgo/utils/net.dart';
7 | import 'package:path/path.dart';
8 |
9 | class UpyunApi {
10 | static const BASE_URL = 'http://v0.api.upyun.com';
11 |
12 | static const String operatorKey = 'operatorKey';
13 | static const String passwordKey = 'passwordKey';
14 |
15 | /// REST API PUT
16 | static Future putObject(
17 | File file,
18 | String operator,
19 | String password,
20 | String name,
21 | String bucket, {
22 | String path = '',
23 | }) async {
24 | String wholePath = joinAll([BASE_URL, bucket, path, name]);
25 | var bytes = file.readAsBytesSync();
26 | Response res = await NetUtils.getInstance().put(wholePath,
27 | data: Stream.fromIterable(bytes.map((e) => [e])),
28 | options: Options(
29 | headers: {
30 | Headers.contentLengthHeader: bytes.length,
31 | },
32 | contentType: 'image/${extension(name).replaceFirst('.', '')}',
33 | extra: {
34 | operatorKey: operator,
35 | passwordKey: password,
36 | }));
37 | return res.headers;
38 | }
39 |
40 | /// REST API DELETE
41 | static Future deleteObject(
42 | String bucket,
43 | String operator,
44 | String password,
45 | String key,
46 | ) async {
47 | String wholePath = joinAll([BASE_URL, bucket, key]);
48 | Response res = await NetUtils.getInstance().delete(wholePath,
49 | options: Options(
50 | extra: {
51 | operatorKey: operator,
52 | passwordKey: password,
53 | },
54 | ));
55 | return res.headers;
56 | }
57 |
58 | /// build policy
59 | static String buildPolicy(String bucket, String saveKey) {
60 | Map map = {
61 | 'bucket': bucket,
62 | 'save-key': saveKey,
63 | 'expiration': DateTime.now().millisecondsSinceEpoch + 30 * 60 * 1000,
64 | };
65 | return base64.encode(utf8.encode(json.encode(map)));
66 | }
67 | }
68 |
69 | class UpyunInterceptor extends InterceptorsWrapper {
70 | @override
71 | Future onRequest(
72 | RequestOptions options,
73 | RequestInterceptorHandler handler,
74 | ) async {
75 | if (options.path.contains(UpyunApi.BASE_URL.replaceFirst('http://', ''))) {
76 | /// 请求方式,如:GET、POST、PUT、HEAD 等
77 | String method = options.method.toUpperCase();
78 | String path = options.uri.path;
79 | String date = HttpDate.format(DateTime.now());
80 | String pwdMd5 =
81 | '${md5.convert(utf8.encode(options.extra[UpyunApi.passwordKey]))}';
82 | String operator = options.extra[UpyunApi.operatorKey];
83 | String sign = '$method&$path&$date';
84 |
85 | /// 签名构造
86 | var hmacsha1 = Hmac(sha1, utf8.encode('$pwdMd5'));
87 | var auth = hmacsha1.convert(utf8.encode(sign));
88 | String realAuth = base64.encode(auth.bytes);
89 |
90 | /// Add Common Header
91 | options.headers.addAll({
92 | 'Date': date,
93 | 'Authorization': 'UPYUN $operator:$realAuth',
94 | });
95 | }
96 | return handler.next(options);
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/lib/views/manage_page/qiniu_page/qiniu_repo_page_presenter.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:flutter_picgo/api/qiniu_api.dart';
4 | import 'package:flutter_picgo/components/manage_item.dart';
5 | import 'package:flutter_picgo/model/qiniu_config.dart';
6 | import 'package:flutter_picgo/model/qiniu_content.dart';
7 | import 'package:flutter_picgo/resources/pb_type_keys.dart';
8 | import 'package:flutter_picgo/utils/image_upload.dart';
9 | import 'package:flutter_picgo/utils/strings.dart';
10 | import 'package:path/path.dart' as pathlib;
11 |
12 | abstract class QiniuRepoPageContract {
13 | void loadSuccess(List data);
14 |
15 | void loadError(String msg);
16 | }
17 |
18 | class QiniuRepoPagePresenter {
19 | QiniuRepoPageContract _view;
20 |
21 | QiniuRepoPagePresenter(this._view);
22 |
23 | doLoadContents(String prefix) async {
24 | try {
25 | String configStr = await ImageUploadUtils.getPBConfig(PBTypeKeys.qiniu);
26 | QiniuConfig config = QiniuConfig.fromJson(json.decode(configStr));
27 | if (isBlank(config.accessKey) || isBlank(config.secretKey)) {
28 | _view.loadError('读取配置文件错误');
29 | return;
30 | }
31 | var result = await QiniuApi.list({
32 | 'bucket': config.bucket,
33 | 'prefix': prefix == '/' ? '' : prefix,
34 | 'delimiter': Uri.decodeComponent('/'),
35 | }, config.accessKey, config.secretKey);
36 | List data = (result['items'] as List).map((e) {
37 | QiniuContent c = QiniuContent.fromJson(e);
38 | c.type = FileContentType.FILE;
39 | c.url = pathlib.joinAll([
40 | config.url,
41 | c.key,
42 | ]);
43 | c.key = '${c.key}'.replaceFirst(prefix == '/' ? '' : prefix, '');
44 | return c;
45 | }).toList();
46 | if (result['commonPrefixes'] != null) {
47 | (result['commonPrefixes'] as List).forEach((element) {
48 | QiniuContent c = QiniuContent();
49 | c.type = FileContentType.DIR;
50 | c.url = element;
51 |
52 | /// 例如 xin/ax 去除 xin/ 只显示最后一个 ax
53 | List keys = '$element'.split('/');
54 | c.key = keys[keys.length - 2];
55 | data.add(c);
56 | });
57 | }
58 | _view.loadSuccess(data);
59 | } catch (e) {
60 | _view.loadError('$e');
61 | }
62 | }
63 |
64 | Future doDeleteContents(String key) async {
65 | try {
66 | String configStr = await ImageUploadUtils.getPBConfig(PBTypeKeys.qiniu);
67 | QiniuConfig config = QiniuConfig.fromJson(json.decode(configStr));
68 | if (isBlank(config.accessKey) || isBlank(config.secretKey)) {
69 | _view.loadError('读取配置文件错误');
70 | return false;
71 | }
72 | String encodedEntryURI =
73 | QiniuApi.urlSafeBase64Encode(utf8.encode('${config.bucket}:$key'));
74 | String url = '${QiniuApi.BASE_URL}/delete/$encodedEntryURI';
75 | var result =
76 | await QiniuApi.delete(url, config.accessKey, config.secretKey);
77 | if (isBlank(result.toString())) {
78 | return true;
79 | } else {
80 | _view.loadError('${result['error']}');
81 | return false;
82 | }
83 | } catch (e) {
84 | return false;
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/lib/api/aliyun_api.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'dart:io';
3 |
4 | import 'package:crypto/crypto.dart';
5 | import 'package:dio/dio.dart';
6 | import 'package:flutter_picgo/utils/net.dart';
7 | import 'package:flutter_picgo/utils/strings.dart';
8 |
9 | class AliyunApi {
10 | // 还需要拼接buckername 例如zjyzy.oss-cn-shenzhen.aliyuncs.com
11 | static const BASE_URL = 'aliyuncs.com';
12 |
13 | static postObject(String bucket, String aera, FormData data) async {
14 | Response res = await NetUtils.getInstance().post(
15 | 'https://$bucket.$aera.aliyuncs.com',
16 | data: data,
17 | );
18 | return res.headers;
19 | }
20 |
21 | static deleteObject(
22 | String bucket, String aera, String object, String auth) async {
23 | Response res = await NetUtils.getInstance()
24 | .delete('https://$bucket.$aera.aliyuncs.com/$object',
25 | options: Options(headers: {
26 | 'Authorization': auth,
27 | 'Date': HttpDate.format(new DateTime.now()),
28 | }));
29 | return res.headers;
30 | }
31 |
32 | /// Content-MD5的计算
33 | static String generateContentMD5(String content) {
34 | // 先计算MD5加密的二进制数组
35 | var digest = md5.convert(utf8.encode(content));
36 | return base64.encode(digest.bytes);
37 | }
38 |
39 | /// 构建CanonicalizedResource
40 | static String buildCanonicalizedResource(
41 | String bucketName, String objectName) {
42 | if (isBlank(bucketName)) {
43 | return '/';
44 | }
45 | if (isBlank(objectName)) {
46 | return '/$bucketName/';
47 | } else {
48 | return '/$bucketName/$objectName';
49 | }
50 | }
51 |
52 | /// form policy
53 | static String buildEncodePolicy(String objectName) {
54 | var policyText = {
55 | "expiration":
56 | "2030-01-01T00:00:00.000Z", // 设置Policy的失效时间,如果超过失效时间,就无法通过此Policy上传文件
57 | "conditions": [
58 | {
59 | "key": objectName,
60 | } // 设置上传文件的大小限制,如果超过限制,文件上传到OSS会报错
61 | ]
62 | };
63 | var encodePolicy = base64.encode(utf8.encode(json.encode(policyText)));
64 | return encodePolicy;
65 | }
66 |
67 | /// Form Authorization
68 | static String buildPostSignature(
69 | String accessKeyId, String accessKeySecret, String encodePolicy) {
70 | // 使用SecertKey对上一步生成的原始字符串计算HMAC-SHA1签名:
71 | var hmacsha1 = Hmac(sha1, utf8.encode(accessKeySecret));
72 | var sign = hmacsha1.convert(utf8.encode(encodePolicy));
73 | var encodeSign = base64.encode(sign.bytes);
74 | return encodeSign;
75 | }
76 |
77 | /// Authorization
78 | static String buildSignature(String accessKeyId, String accessKeySecret,
79 | String verb, String bucketName, String objectName,
80 | {String contentMd5 = '', String contentType = ''}) {
81 | var canonicalizedResource =
82 | buildCanonicalizedResource(bucketName, objectName);
83 | var date = HttpDate.format(new DateTime.now());
84 | var originSign =
85 | '${verb.toUpperCase()}\n$contentMd5\n$contentType\n$date\n$canonicalizedResource';
86 | // 使用SecertKey对上一步生成的原始字符串计算HMAC-SHA1签名:
87 | var hmacsha1 = Hmac(sha1, utf8.encode(accessKeySecret));
88 | var sign = hmacsha1.convert(utf8.encode(originSign));
89 | var encodeSign = base64.encode(sign.bytes);
90 | return 'OSS $accessKeyId:$encodeSign';
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/lib/views/setting_page/setting_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:fluro/fluro.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_picgo/routers/application.dart';
4 | import 'package:flutter_picgo/routers/routers.dart';
5 | import 'package:package_info/package_info.dart';
6 | import 'package:url_launcher/url_launcher.dart';
7 |
8 | class SettingPage extends StatefulWidget {
9 | @override
10 | _SettingPageState createState() => _SettingPageState();
11 | }
12 |
13 | class _SettingPageState extends State {
14 | String version;
15 |
16 | @override
17 | void initState() {
18 | super.initState();
19 | _getVersion();
20 | }
21 |
22 | void _getVersion() async {
23 | final PackageInfo info = await PackageInfo.fromPlatform();
24 | setState(() {
25 | this.version = info.version;
26 | });
27 | }
28 |
29 | @override
30 | Widget build(BuildContext context) {
31 | return Scaffold(
32 | appBar: AppBar(
33 | title: Text('设置'),
34 | centerTitle: true,
35 | ),
36 | body: ListView(
37 | children: [
38 | // 图标面板
39 | Container(
40 | width: double.infinity,
41 | height: 160.0,
42 | child: Column(
43 | children: [
44 | SizedBox(height: 20),
45 | Center(
46 | child: Container(
47 | width: 80,
48 | height: 80,
49 | child: ClipOval(
50 | child: Image.asset(
51 | 'assets/images/logo.png',
52 | fit: BoxFit.cover,
53 | width: 80,
54 | height: 80,
55 | ),
56 | ),
57 | ),
58 | ),
59 | SizedBox(height: 20),
60 | Center(
61 | child: Text('v${this.version}'),
62 | ),
63 | ],
64 | ),
65 | ),
66 | // 菜单列表
67 | ListTile(
68 | title: Text('图床设置'),
69 | onTap: () {
70 | Application.router.navigateTo(context, Routes.settingPb,
71 | transition: TransitionType.cupertino);
72 | },
73 | trailing: Icon(Icons.arrow_right),
74 | ),
75 | ListTile(
76 | title: Text('PicGo设置'),
77 | onTap: () {
78 | Application.router.navigateTo(context, Routes.settingPicgo,
79 | transition: TransitionType.cupertino);
80 | },
81 | trailing: Icon(Icons.arrow_right),
82 | ),
83 | ListTile(
84 | title: Text('关于Flutter-PicGo'),
85 | onTap: () {
86 | launch('https://github.com/hackycy/flutter-picgo');
87 | },
88 | trailing: Icon(Icons.arrow_right),
89 | ),
90 | ListTile(
91 | title: Text('建议或报告问题'),
92 | onTap: () {
93 | launch('https://github.com/PicGo/flutter-picgo/issues');
94 | },
95 | trailing: Icon(Icons.arrow_right),
96 | ),
97 | ],
98 | ),
99 | );
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - barcode_scan (0.0.1):
3 | - Flutter
4 | - MTBBarcodeScanner
5 | - SwiftProtobuf
6 | - Flutter (1.0.0)
7 | - flutter_local_notifications (0.0.1):
8 | - Flutter
9 | - FMDB (2.7.5):
10 | - FMDB/standard (= 2.7.5)
11 | - FMDB/standard (2.7.5)
12 | - MTBBarcodeScanner (5.0.11)
13 | - package_info (0.0.1):
14 | - Flutter
15 | - path_provider (0.0.1):
16 | - Flutter
17 | - "permission_handler (5.0.1+1)":
18 | - Flutter
19 | - photo_manager (0.0.1):
20 | - Flutter
21 | - shared_preferences (0.0.1):
22 | - Flutter
23 | - sqflite (0.0.2):
24 | - Flutter
25 | - FMDB (>= 2.7.5)
26 | - SwiftProtobuf (1.9.0)
27 | - url_launcher (0.0.1):
28 | - Flutter
29 | - video_player (0.0.1):
30 | - Flutter
31 |
32 | DEPENDENCIES:
33 | - barcode_scan (from `.symlinks/plugins/barcode_scan/ios`)
34 | - Flutter (from `Flutter`)
35 | - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
36 | - package_info (from `.symlinks/plugins/package_info/ios`)
37 | - path_provider (from `.symlinks/plugins/path_provider/ios`)
38 | - permission_handler (from `.symlinks/plugins/permission_handler/ios`)
39 | - photo_manager (from `.symlinks/plugins/photo_manager/ios`)
40 | - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
41 | - sqflite (from `.symlinks/plugins/sqflite/ios`)
42 | - url_launcher (from `.symlinks/plugins/url_launcher/ios`)
43 | - video_player (from `.symlinks/plugins/video_player/ios`)
44 |
45 | SPEC REPOS:
46 | trunk:
47 | - FMDB
48 | - MTBBarcodeScanner
49 | - SwiftProtobuf
50 |
51 | EXTERNAL SOURCES:
52 | barcode_scan:
53 | :path: ".symlinks/plugins/barcode_scan/ios"
54 | Flutter:
55 | :path: Flutter
56 | flutter_local_notifications:
57 | :path: ".symlinks/plugins/flutter_local_notifications/ios"
58 | package_info:
59 | :path: ".symlinks/plugins/package_info/ios"
60 | path_provider:
61 | :path: ".symlinks/plugins/path_provider/ios"
62 | permission_handler:
63 | :path: ".symlinks/plugins/permission_handler/ios"
64 | photo_manager:
65 | :path: ".symlinks/plugins/photo_manager/ios"
66 | shared_preferences:
67 | :path: ".symlinks/plugins/shared_preferences/ios"
68 | sqflite:
69 | :path: ".symlinks/plugins/sqflite/ios"
70 | url_launcher:
71 | :path: ".symlinks/plugins/url_launcher/ios"
72 | video_player:
73 | :path: ".symlinks/plugins/video_player/ios"
74 |
75 | SPEC CHECKSUMS:
76 | barcode_scan: a5c27959edfafaa0c771905bad0b29d6d39e4479
77 | Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
78 | flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743
79 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
80 | MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
81 | package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
82 | path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
83 | permission_handler: eac8e15b4a1a3fba55b761d19f3f4e6b005d15b6
84 | photo_manager: f7c619c2cc8c2adb8d85c63363babac477de9c67
85 | shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
86 | sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
87 | SwiftProtobuf: ecbec1be9036d15655f6b3443a1c4ea693c97932
88 | url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
89 | video_player: 9cc823b1d9da7e8427ee591e8438bfbcde500e6e
90 |
91 | PODFILE CHECKSUM: 08e41981fcdc78ff11c898bc59c6c49635c114b3
92 |
93 | COCOAPODS: 1.9.1
94 |
--------------------------------------------------------------------------------
/lib/views/pb_setting_page/lsky_page/lsky_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:fluro/fluro.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter_picgo/api/lsky_api.dart';
6 | import 'package:flutter_picgo/model/config.dart';
7 | import 'package:flutter_picgo/model/lsky_config.dart';
8 | import 'package:flutter_picgo/resources/pb_type_keys.dart';
9 | import 'package:flutter_picgo/routers/application.dart';
10 | import 'package:flutter_picgo/routers/routers.dart';
11 | import 'package:flutter_picgo/utils/strings.dart';
12 | import 'package:flutter_picgo/views/pb_setting_page/base_pb_page_state.dart';
13 | import 'package:toast/toast.dart';
14 |
15 | class LskyPage extends StatefulWidget {
16 | _LskyPageState createState() => _LskyPageState();
17 | }
18 |
19 | class _LskyPageState extends BasePBSettingPageState {
20 | @override
21 | onLoadConfig(String config) {
22 | List configs = [];
23 | Map map;
24 | if (isBlank(config)) {
25 | map = LskyConfig().toJson();
26 | } else {
27 | map = LskyConfig.fromJson(json.decode(config)).toJson();
28 | }
29 | map.forEach((key, value) {
30 | Config config;
31 | if (key == 'host') {
32 | config = Config(
33 | label: '设定Host',
34 | placeholder: '例如:https://lsky.si-yee.com',
35 | needValidate: true,
36 | value: value);
37 | } else if (key == 'token') {
38 | config = Config(
39 | label: '设定Token',
40 | placeholder: 'Token',
41 | needValidate: false,
42 | value: value);
43 | } else if (key == 'email') {
44 | config = Config(
45 | label: '设定邮箱',
46 | placeholder: 'Email',
47 | needValidate: true,
48 | value: value);
49 | } else if (key == 'password') {
50 | config = Config(
51 | label: '设定密码',
52 | placeholder: '设定密码',
53 | needValidate: true,
54 | value: value);
55 | }
56 | config.name = key;
57 | configs.add(config);
58 | });
59 | setConfigs(configs);
60 | }
61 |
62 | @override
63 | String get tip => '点击保存会自动获取Token,如果已填写字段则不会自动生成';
64 |
65 | @override
66 | String get pbType => PBTypeKeys.lsky;
67 |
68 | @override
69 | save() async {
70 | if (isBlank(controllers['token'].text.trim())) {
71 | // 如果Token为空,则自动获取
72 | String email = controllers['email'].value.text.trim();
73 | String pwd = controllers['password'].value.text.trim();
74 | String host = controllers['host'].value.text.trim();
75 | var result = await LskyApi.token(email, pwd, host);
76 | if (result["code"] == 200) {
77 | // controllers["token"].text = '${result["data"]["token"]}';
78 | setState(() {
79 | configs[3].value = '${result["data"]["token"]}';
80 | });
81 | Future.delayed(Duration(milliseconds: 500), () {
82 | super.save();
83 | });
84 | } else {
85 | Toast.show('Token获取失败,请检查配置', context);
86 | }
87 | } else {
88 | super.save();
89 | }
90 | }
91 |
92 | @override
93 | bool get isSupportManage => true;
94 |
95 | @override
96 | handleManage() {
97 | Application.router.navigateTo(context, Routes.settingPbLskyRepo,
98 | transition: TransitionType.cupertino);
99 | }
100 |
101 | @override
102 | String get title => '兰空图床';
103 | }
104 |
--------------------------------------------------------------------------------
/lib/utils/shared_preferences.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter_picgo/resources/pb_type_keys.dart';
4 | import 'package:flutter_picgo/resources/shared_preferences_keys.dart';
5 | import 'package:shared_preferences/shared_preferences.dart';
6 |
7 | export 'package:flutter_picgo/resources/shared_preferences_keys.dart';
8 |
9 | /// 用来做shared_preferences的存储
10 | class SpUtil {
11 | static SpUtil _instance;
12 | static Future get instance async {
13 | return await getInstance();
14 | }
15 |
16 | static SharedPreferences _spf;
17 |
18 | SpUtil._();
19 |
20 | Future _init() async {
21 | _spf = await SharedPreferences.getInstance();
22 | }
23 |
24 | static Future getInstance() async {
25 | if (_instance == null) {
26 | _instance = new SpUtil._();
27 | }
28 | if (_spf == null) {
29 | await _instance._init();
30 | }
31 | return _instance;
32 | }
33 |
34 | static bool _beforeCheck() {
35 | if (_spf == null) {
36 | return true;
37 | }
38 | return false;
39 | }
40 |
41 | /// 设置默认图床
42 | setDefaultPB(String type) {
43 | putString(SharedPreferencesKeys.settingDefaultPB, type);
44 | }
45 |
46 | /// 获取当前默认图床,默认为github
47 | String getDefaultPB() {
48 | return getString(SharedPreferencesKeys.settingDefaultPB) ??
49 | PBTypeKeys.github;
50 | }
51 |
52 | // 判断是否存在数据
53 | bool hasKey(String key) {
54 | Set keys = getKeys();
55 | return keys.contains(key);
56 | }
57 |
58 | Set getKeys() {
59 | if (_beforeCheck()) return null;
60 | return _spf.getKeys();
61 | }
62 |
63 | get(String key) {
64 | if (_beforeCheck()) return null;
65 | return _spf.get(key);
66 | }
67 |
68 | getString(String key) {
69 | if (_beforeCheck()) return null;
70 | return _spf.getString(key);
71 | }
72 |
73 | Future putString(String key, String value) {
74 | if (_beforeCheck()) return null;
75 | return _spf.setString(key, value);
76 | }
77 |
78 | bool getBool(String key) {
79 | if (_beforeCheck()) return null;
80 | return _spf.getBool(key);
81 | }
82 |
83 | Future putBool(String key, bool value) {
84 | if (_beforeCheck()) return null;
85 | return _spf.setBool(key, value);
86 | }
87 |
88 | int getInt(String key) {
89 | if (_beforeCheck()) return null;
90 | return _spf.getInt(key);
91 | }
92 |
93 | Future putInt(String key, int value) {
94 | if (_beforeCheck()) return null;
95 | return _spf.setInt(key, value);
96 | }
97 |
98 | double getDouble(String key) {
99 | if (_beforeCheck()) return null;
100 | return _spf.getDouble(key);
101 | }
102 |
103 | Future putDouble(String key, double value) {
104 | if (_beforeCheck()) return null;
105 | return _spf.setDouble(key, value);
106 | }
107 |
108 | List getStringList(String key) {
109 | return _spf.getStringList(key);
110 | }
111 |
112 | Future putStringList(String key, List value) {
113 | if (_beforeCheck()) return null;
114 | return _spf.setStringList(key, value);
115 | }
116 |
117 | dynamic getDynamic(String key) {
118 | if (_beforeCheck()) return null;
119 | return _spf.get(key);
120 | }
121 |
122 | Future remove(String key) {
123 | if (_beforeCheck()) return null;
124 | return _spf.remove(key);
125 | }
126 |
127 | Future clear() {
128 | if (_beforeCheck()) return null;
129 | return _spf.clear();
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_picgo
2 | description: PicGo For Flutter
3 |
4 | # The following line prevents the package from being accidentally published to
5 | # pub.dev using `pub publish`. This is preferred for private packages.
6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev
7 |
8 | # The following defines the version and build number for your application.
9 | # A version number is three numbers separated by dots, like 1.2.43
10 | # followed by an optional build number separated by a +.
11 | # Both the version and the builder number may be overridden in flutter
12 | # build by specifying --build-name and --build-number, respectively.
13 | # In Android, build-name is used as versionName while build-number used as versionCode.
14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
16 | # Read more about iOS versioning at
17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
18 | version: 1.9.2+24
19 |
20 | environment:
21 | sdk: ">=2.7.0 <3.0.0"
22 |
23 | dependencies:
24 | flutter:
25 | sdk: flutter
26 | shared_preferences: ^2.0.5
27 | dio: ^4.0.0
28 | package_info: ^2.0.0
29 | sqflite: ^1.3.2+1
30 | fluro: ^2.0.3
31 | toast: ^0.1.5
32 | json_serializable: ^3.3.0
33 | url_launcher: ^5.7.10
34 | permission_handler: ^5.0.1
35 | barcode_scan: ^3.0.1
36 | provider: ^5.0.0
37 | crypto: ^3.0.1
38 | flutter_local_notifications: ^3.0.1+4
39 | pull_to_refresh: ^1.6.5
40 | wechat_assets_picker: ^5.1.3
41 | extended_image: ^4.0.1
42 |
43 |
44 | # The following adds the Cupertino Icons font to your application.
45 | # Use with the CupertinoIcons class for iOS style icons.
46 | cupertino_icons: ^0.1.3
47 |
48 | dev_dependencies:
49 | flutter_test:
50 | sdk: flutter
51 |
52 | # For information on the generic Dart part of this file, see the
53 | # following page: https://dart.dev/tools/pub/pubspec
54 |
55 | # The following section is specific to Flutter.
56 | flutter:
57 |
58 | # The following line ensures that the Material Icons font is
59 | # included with your application, so that you can use the icons in
60 | # the material Icons class.
61 | uses-material-design: true
62 |
63 | # To add assets to your application, add an assets section, like this:
64 | assets:
65 | - assets/images/logo.png
66 | - assets/images/2.0x/logo.png
67 | - assets/images/3.0x/logo.png
68 | - assets/images/4.0x/logo.png
69 | - assets/images/icon_empty_album.png
70 | # - images/a_dot_ham.jpeg
71 |
72 | # An image asset can refer to one or more resolution-specific "variants", see
73 | # https://flutter.dev/assets-and-images/#resolution-aware.
74 |
75 | # For details regarding adding assets from package dependencies, see
76 | # https://flutter.dev/assets-and-images/#from-packages
77 |
78 | # To add custom fonts to your application, add a fonts section here,
79 | # in this "flutter" section. Each entry in this list should have a
80 | # "family" key with the font family name, and a "fonts" key with a
81 | # list giving the asset and other descriptors for the font. For
82 | # example:
83 | fonts:
84 | - family: Iconfont
85 | fonts:
86 | - asset: assets/fonts/iconfont.ttf
87 | # - asset: fonts/Schyler-Italic.ttf
88 | # style: italic
89 | # - family: Trajan Pro
90 | # fonts:
91 | # - asset: fonts/TrajanPro.ttf
92 | # - asset: fonts/TrajanPro_Bold.ttf
93 | # weight: 700
94 | #
95 | # For details regarding fonts from package dependencies,
96 | # see https://flutter.dev/custom-fonts/#from-packages
97 |
--------------------------------------------------------------------------------
/lib/utils/image_upload.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 | import 'package:flutter_picgo/model/uploaded.dart';
3 | import 'package:flutter_picgo/resources/table_name_keys.dart';
4 | import 'package:flutter_picgo/utils/shared_preferences.dart';
5 | import 'package:flutter_picgo/utils/sql.dart';
6 | import 'package:flutter_picgo/utils/strategy/image_upload_strategy.dart';
7 |
8 | /// 图像上传类
9 | class ImageUploadUtils {
10 | ImageUploadStrategy _strategy;
11 |
12 | ImageUploadUtils(this._strategy);
13 |
14 | Future delete(Uploaded uploaded) async {
15 | var sp = await SpUtil.getInstance();
16 | var isForceDelete =
17 | sp.getBool(SharedPreferencesKeys.settingIsForceDelete) ?? false;
18 | // 关闭仅关闭本地图片,则需要根据对应策略来删除图片
19 | if (!isForceDelete) {
20 | var upTmp = await _strategy.delete(uploaded);
21 | if (upTmp == null) {
22 | throw Exception('delete remote error');
23 | }
24 | }
25 | // 删除本地项
26 | var raw = await deleteUploadedItem(uploaded);
27 | if (raw > 0) {
28 | return uploaded;
29 | } else {
30 | throw Exception('delete local error');
31 | }
32 | }
33 |
34 | Future upload(File file, String renameImage) {
35 | return _strategy.upload(file, renameImage);
36 | }
37 |
38 | /// 获取默认图床类型
39 | static Future getDefaultPB() async {
40 | var sp = await SpUtil.getInstance();
41 | String pbType = sp.getDefaultPB();
42 | return pbType;
43 | }
44 |
45 | /// 设置默认图床
46 | static Future setDefaultPB(String type) async {
47 | var sp = await SpUtil.getInstance();
48 | if (sp.getDefaultPB() != type) {
49 | sp.setDefaultPB(type);
50 | }
51 | }
52 |
53 | /// 保存图床配置
54 | static Future savePBConfig(String type, String config) async {
55 | var sql = Sql.setTable(TABLE_NAME_PBSETTING);
56 | int row = await sql.rawUpdate('config = ? WHERE type = ?', [config, type]);
57 | return row;
58 | }
59 |
60 | /// 获取当前图床配置
61 | static Future getPBConfig(String type) async {
62 | var sql = Sql.setTable(TABLE_NAME_PBSETTING);
63 | var pbsettingRow = await sql.getBySql('type = ?', [type]);
64 | if (pbsettingRow != null && pbsettingRow.length > 0) {
65 | return pbsettingRow.first["config"];
66 | }
67 | return null;
68 | }
69 |
70 | /// 获取图床名称
71 | static Future getPBName(String type) async {
72 | var sql = Sql.setTable(TABLE_NAME_PBSETTING);
73 | var pbsettingRow = (await sql.getBySql('type = ?', [type]));
74 | if (pbsettingRow != null && pbsettingRow.length > 0) {
75 | return pbsettingRow.first["name"];
76 | }
77 | return null;
78 | }
79 |
80 | /// 保存已上传项
81 | static Future saveUploadedItem(Uploaded item) async {
82 | var sql = Sql.setTable(TABLE_NAME_UPLOADED);
83 | return await sql.rawInsert('(path, type, info) VALUES(?, ?, ?)',
84 | [item.path, item.type, item.info]);
85 | }
86 |
87 | /// 删除已上传的本地数据库项
88 | static Future deleteUploadedItem(Uploaded item) async {
89 | var sql = Sql.setTable(TABLE_NAME_UPLOADED);
90 | var result = await sql.rawDelete('id = ?', [item.id]);
91 | return result;
92 | }
93 |
94 | /// 获取已上传项的信息
95 | static Future getUploadedItemInfo(int id) async {
96 | var sql = Sql.setTable(TABLE_NAME_UPLOADED);
97 | return (await sql.getBySql('id = ?', [id]))?.first["info"];
98 | }
99 |
100 | /// 获取已上传列表
101 | static Future> getUploadeds() async {
102 | var sql = Sql.setTable(TABLE_NAME_UPLOADED);
103 | var result = await sql.get();
104 | List uploadeds = result.map((v) {
105 | return Uploaded.fromMap(v);
106 | }).toList();
107 | return uploadeds;
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/lib/routers/routers.dart:
--------------------------------------------------------------------------------
1 | import 'package:fluro/fluro.dart';
2 | import 'package:flutter_picgo/routers/router_handler.dart';
3 |
4 | class Routes {
5 | // 路由Map
6 | static const String root = '/';
7 | static const String album = '/album';
8 | static const String notfound = '/404';
9 | static const String setting = '/setting';
10 | static const String upload = '/upload';
11 | static const String handleUpload = '/handle/upload';
12 | static const String settingPb = '/setting/pb';
13 | static const String settingPicgo = '/setting/picgo';
14 | static const String settingPicgoTheme = '/setting/picgo/theme';
15 | // --------- github ------------------
16 | static const String settingPbGithub = '/setting/pb/github';
17 | static const String settingPbGitubRepo = '/setting/pb/github/repo';
18 | // -----------------------------------
19 | // --------- smms ------------------
20 | static const String settingPbSMMS = '/setting/pb/smms';
21 | static const String settingPbSMMSRepo = '/setting/pb/smms/repo';
22 | // -----------------------------------
23 | // --------- gitee ------------------
24 | static const String settingPbGitee = '/setting/pb/gitee';
25 | static const String settingPbGiteeRepo = '/setting/pb/gitee/repo';
26 | // -----------------------------------
27 | // --------- qiniu -------------------
28 | static const String settingPbQiniu = '/setting/pb/qiniu';
29 | static const String settingPbQiniuRepo = 'setting/pb/qiniu/repo';
30 | // -----------------------------------
31 | // --------- aliyun -------------------
32 | static const String settingPbAliyun = '/setting/pb/aliyun';
33 | // -----------------------------------
34 | // --------- tcyun -------------------
35 | static const String settingPbTcyun = '/setting/pb/tcyun';
36 | // -----------------------------------
37 | // --------- niupic -------------------
38 | static const String settingPbNiupic = '/setting/pb/niupic';
39 | // -----------------------------------
40 | // --------- lsky -------------------
41 | static const String settingPbLsky = '/setting/pb/lsky';
42 | static const String settingPbLskyRepo = '/setting/pb/lsky/repo';
43 | // -----------------------------------
44 | // --------- upyun -------------------
45 | static const String settingPbUpyun = '/setting/pb/upyun';
46 | // -----------------------------------
47 |
48 | static void configureRoutes(FluroRouter router) {
49 | router.notFoundHandler = notfoundHandler;
50 | router.define(root, handler: appHandler);
51 | router.define(notfound, handler: notfoundHandler);
52 | router.define(album, handler: albumHandler);
53 | router.define(handleUpload, handler: preUploadHandler);
54 | router.define(setting, handler: settingHandler);
55 | router.define(settingPb, handler: pbsettingHandler);
56 | router.define(settingPbGithub, handler: pbsettingGithubHandler);
57 | router.define(settingPbGitubRepo, handler: pbsettingGithubRepoHandler);
58 | router.define(settingPicgo, handler: picgosettingHandler);
59 | router.define(settingPbSMMS, handler: pbsettingSMMSHandler);
60 | router.define(settingPbGitee, handler: pbsettingGiteeHandler);
61 | router.define(settingPbGiteeRepo, handler: pbsettingGiteeRepoHandler);
62 | router.define(settingPicgoTheme, handler: picggsettingThemeHandler);
63 | router.define(settingPbQiniu, handler: pbsettingQiniuHandler);
64 | router.define(settingPbAliyun, handler: pbsettingAliyunHandler);
65 | router.define(settingPbTcyun, handler: pbsettingTcyunHandler);
66 | router.define(settingPbNiupic, handler: pbsettingNiupicHandler);
67 | router.define(settingPbLsky, handler: pbsettingLskyHandler);
68 | router.define(settingPbUpyun, handler: pbsettingUpyunHandler);
69 | router.define(settingPbSMMSRepo, handler: pbsettingSMMSRepoHandler);
70 | router.define(settingPbLskyRepo, handler: pbsettingLskyRepoHandler);
71 | router.define(settingPbQiniuRepo, handler: pbsettingQiniuRepoHandler);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/lib/components/manage_item.dart:
--------------------------------------------------------------------------------
1 | import 'package:extended_image/extended_image.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_picgo/utils/extended.dart';
4 | import 'package:path/path.dart';
5 |
6 | typedef GestureTapCallback = void Function();
7 | typedef DismissDirectionCallback = void Function(DismissDirection direction);
8 |
9 | class ManageItem extends StatelessWidget {
10 | final Key key;
11 | final String url;
12 | final String name;
13 | final String info;
14 | final FileContentType type;
15 | final GestureTapCallback onTap;
16 | final DismissDirectionCallback onDismiss;
17 | final ConfirmDismissCallback confirmDismiss;
18 |
19 | ManageItem(
20 | this.key,
21 | this.url,
22 | this.name,
23 | this.info,
24 | this.type, {
25 | this.onTap,
26 | this.onDismiss,
27 | this.confirmDismiss,
28 | });
29 |
30 | @override
31 | Widget build(BuildContext context) {
32 | return Dismissible(
33 | key: key,
34 | direction: DismissDirection.endToStart,
35 | child: Stack(
36 | children: [
37 | ListTile(
38 | leading: buildLeading(),
39 | title: Text(
40 | this.name,
41 | maxLines: 1,
42 | overflow: TextOverflow.ellipsis,
43 | textWidthBasis: TextWidthBasis.parent,
44 | style: TextStyle(fontSize: 16, fontWeight: FontWeight.w400),
45 | ),
46 | subtitle: Text(
47 | this.info,
48 | maxLines: 1,
49 | style: TextStyle(color: Colors.grey),
50 | ),
51 | onTap: onTap,
52 | ),
53 | Align(
54 | alignment: Alignment.bottomCenter,
55 | child: Divider(
56 | height: 1,
57 | color: Theme.of(context).dividerColor,
58 | indent: 80,
59 | ),
60 | )
61 | ],
62 | ),
63 | onDismissed: onDismiss,
64 | confirmDismiss: confirmDismiss,
65 | background: Container(
66 | color: Colors.red,
67 | ),
68 | );
69 | }
70 |
71 | Widget buildLeading() {
72 | if (this.type == FileContentType.DIR) {
73 | return buildCenterIcon(Icon(IconData(0xe63f, fontFamily: 'iconfont')));
74 | }
75 | try {
76 | String path = Uri.parse(this.url).path;
77 | String suffix = extension(path).replaceFirst('.', '');
78 | var imageSuffixs = ['png', 'bmp', 'jpeg', 'gif', 'jpg'];
79 | if (imageSuffixs.contains(suffix)) {
80 | return SizedBox(
81 | height: 50,
82 | width: 50,
83 | child: Card(
84 | clipBehavior: Clip.antiAlias,
85 | shape: RoundedRectangleBorder(
86 | borderRadius: BorderRadiusDirectional.circular(8)),
87 | child: ExtendedImage.network(
88 | this.url,
89 | width: 50,
90 | height: 50,
91 | fit: BoxFit.cover,
92 | cache: true,
93 | // border: Border.all(color: Colors.grey, width: 1.0),
94 | borderRadius: BorderRadius.all(Radius.circular(8)),
95 | loadStateChanged: (state) => defaultLoadStateChanged(state),
96 | ),
97 | ));
98 | } else {
99 | return buildCenterIcon(Icon(IconData(0xe654, fontFamily: 'iconfont')));
100 | }
101 | } catch (e) {
102 | return buildCenterIcon(Icon(IconData(0xe654, fontFamily: 'iconfont')));
103 | }
104 | }
105 |
106 | Widget buildCenterIcon(Icon icon) {
107 | return SizedBox(
108 | height: 50,
109 | width: 50,
110 | child: Center(
111 | child: icon,
112 | ),
113 | );
114 | }
115 | }
116 |
117 | /// 文件类型
118 | enum FileContentType {
119 | /// 文件
120 | FILE,
121 |
122 | /// 文件夹
123 | DIR,
124 | }
125 |
--------------------------------------------------------------------------------
/lib/utils/local_notification.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flutter_local_notifications/flutter_local_notifications.dart';
4 |
5 | class LocalNotificationUtil {
6 | static LocalNotificationUtil _instance;
7 |
8 | FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin;
9 | NotificationAppLaunchDetails _notificationAppLaunchDetails;
10 |
11 | FlutterLocalNotificationsPlugin get flutterLocalNotificationsPlugin =>
12 | _flutterLocalNotificationsPlugin;
13 |
14 | NotificationAppLaunchDetails get notificationAppLaunchDetails =>
15 | _notificationAppLaunchDetails;
16 |
17 | LocalNotificationUtil._();
18 |
19 | /// 必须先调用
20 | initialization() async {
21 | _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
22 | // notification
23 | _notificationAppLaunchDetails = await _flutterLocalNotificationsPlugin
24 | .getNotificationAppLaunchDetails();
25 |
26 | var initializationSettingsAndroid =
27 | AndroidInitializationSettings('ic_launcher');
28 | // Note: permissions aren't requested here just to demonstrate that can be done later using the `requestPermissions()` method
29 | // of the `IOSFlutterLocalNotificationsPlugin` class
30 | var initializationSettingsIOS = IOSInitializationSettings(
31 | requestAlertPermission: false, //初始化不请求权限
32 | requestBadgePermission: false,
33 | requestSoundPermission: false,
34 | onDidReceiveLocalNotification: null);
35 | var initializationSettings = InitializationSettings(
36 | android: initializationSettingsAndroid,
37 | iOS: initializationSettingsIOS,
38 | macOS: new MacOSInitializationSettings());
39 | await _flutterLocalNotificationsPlugin.initialize(initializationSettings,
40 | onSelectNotification: (String payload) async {
41 | if (payload != null) {}
42 | });
43 | }
44 |
45 | /// 需要先请求权限
46 | requestPermissions() {
47 | /// iOS获取权限
48 | if (Platform.isIOS) {
49 | _flutterLocalNotificationsPlugin
50 | .resolvePlatformSpecificImplementation<
51 | IOSFlutterLocalNotificationsPlugin>()
52 | ?.requestPermissions(
53 | alert: true,
54 | badge: true,
55 | sound: true,
56 | );
57 | }
58 | if (Platform.isAndroid) {
59 | _flutterLocalNotificationsPlugin
60 | .resolvePlatformSpecificImplementation<
61 | AndroidFlutterLocalNotificationsPlugin>()
62 | ?.createNotificationChannel(AndroidNotificationChannel(
63 | AndroidChannelId.upload_channel, '通知提示', '上传通知提示'));
64 | }
65 | }
66 |
67 | /// 显示
68 | show(int id, String title, String body,
69 | NotificationDetails notificationDetails) async {
70 | await _flutterLocalNotificationsPlugin.show(
71 | id, title, body, notificationDetails);
72 | }
73 |
74 | /// 实例获取
75 | static LocalNotificationUtil getInstance() {
76 | if (_instance == null) {
77 | _instance = LocalNotificationUtil._();
78 | }
79 | return _instance;
80 | }
81 |
82 | /// 上传Channel
83 | static AndroidNotificationDetails uploadAndroidChannel() {
84 | return AndroidNotificationDetails(
85 | AndroidChannelId.upload_channel, '上传通知', '上传通知提示',
86 | importance: Importance.max, priority: Priority.high, ticker: 'ticker');
87 | }
88 |
89 | /// 默认IOS
90 | static IOSNotificationDetails normalIOSNotificationDetails() {
91 | return IOSNotificationDetails();
92 | }
93 |
94 | /// 默认MacOS
95 | static MacOSNotificationDetails normalMacOSNotificationDetails() {
96 | return MacOSNotificationDetails();
97 | }
98 |
99 | static NotificationDetails createNotificationDetails(
100 | AndroidNotificationDetails android,
101 | IOSNotificationDetails iOS,
102 | MacOSNotificationDetails macOS) {
103 | return NotificationDetails(android: android, iOS: iOS, macOS: macOS);
104 | }
105 | }
106 |
107 | class AndroidChannelId {
108 | static const upload_channel = '10000';
109 | }
110 |
--------------------------------------------------------------------------------
/lib/utils/strategy/impl/tcyun_image_upload.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:dio/dio.dart';
4 | import 'package:flutter_picgo/api/tcyun_api.dart';
5 | import 'package:flutter_picgo/model/tcyun_config.dart';
6 | import 'package:flutter_picgo/model/uploaded.dart';
7 | import 'package:flutter_picgo/resources/pb_type_keys.dart';
8 | import 'package:flutter_picgo/utils/image_upload.dart';
9 | import 'dart:io';
10 |
11 | import 'package:path/path.dart' as path;
12 | import 'package:flutter_picgo/utils/strategy/image_upload_strategy.dart';
13 | import 'package:flutter_picgo/utils/strings.dart';
14 |
15 | class TcyunImageUpload implements ImageUploadStrategy {
16 | @override
17 | Future delete(Uploaded uploaded) async {
18 | TcyunUploaddedInfo info;
19 | try {
20 | info = TcyunUploaddedInfo.fromJson(json.decode(uploaded.info));
21 | } catch (e) {}
22 | if (info != null) {
23 | await TcyunApi.deleteobject(
24 | info.secretId, info.secretKey, info.bucket, info.area, info.key);
25 | }
26 | return uploaded;
27 | }
28 |
29 | @override
30 | Future upload(File file, String renameImage) async {
31 | String configStr = await ImageUploadUtils.getPBConfig(PBTypeKeys.tcyun);
32 | if (isBlank(configStr)) {
33 | throw TcyunError(error: '读取配置文件错误!请重试');
34 | }
35 | TcyunConfig config = TcyunConfig.fromJson(json.decode(configStr));
36 | String objectPath = isBlank(config.path)
37 | ? renameImage
38 | : path.joinAll([config.path, renameImage]);
39 |
40 | /// post
41 | String keyTime = TcyunApi.buildKeyTime();
42 | String policy = TcyunApi.buildPolicy(
43 | config.bucket, objectPath, config.secretId, keyTime);
44 | FormData formData = FormData.fromMap({
45 | "key": objectPath,
46 | "file": await MultipartFile.fromFile(file.path, filename: renameImage),
47 | "policy": base64.encode(utf8.encode(policy)),
48 | "q-sign-algorithm": "sha1",
49 | "q-ak": config.secretId,
50 | "q-key-time": keyTime,
51 | "q-sign-time": keyTime,
52 | "q-signature": TcyunApi.buildSignature(config.secretKey, keyTime, policy)
53 | });
54 | await TcyunApi.postObject(
55 | config.secretId,
56 | config.secretKey,
57 | config.bucket,
58 | config.area,
59 | path.extension(renameImage).replaceFirst('.', ''),
60 | formData);
61 | String imgPath = path.joinAll([
62 | isBlank(config.customUrl)
63 | ? 'https://${config.bucket}.cos.${config.area}.myqcloud.com'
64 | : config.customUrl,
65 | objectPath
66 | ]);
67 | var uploadedItem = Uploaded(-1, '$imgPath', PBTypeKeys.tcyun,
68 | info: json.encode(TcyunUploaddedInfo(
69 | secretId: config.secretId,
70 | secretKey: config.secretKey,
71 | area: config.area,
72 | key: objectPath,
73 | bucket: config.bucket)));
74 | await ImageUploadUtils.saveUploadedItem(uploadedItem);
75 | return uploadedItem;
76 | }
77 | }
78 |
79 | class TcyunError implements Exception {
80 | TcyunError({
81 | this.error,
82 | });
83 |
84 | dynamic error;
85 |
86 | String get message => (error?.toString() ?? '');
87 |
88 | @override
89 | String toString() {
90 | var msg = 'TcyunError $message';
91 | if (error is Error) {
92 | msg += '\n${error.stackTrace}';
93 | }
94 | return msg;
95 | }
96 | }
97 |
98 | class TcyunUploaddedInfo {
99 | String area;
100 | String bucket;
101 | String secretId;
102 | String secretKey;
103 | String key;
104 |
105 | TcyunUploaddedInfo(
106 | {this.area, this.bucket, this.secretId, this.secretKey, this.key});
107 |
108 | TcyunUploaddedInfo.fromJson(Map json) {
109 | area = json['area'];
110 | bucket = json['bucket'];
111 | secretId = json['secretId'];
112 | secretKey = json['secretKey'];
113 | key = json['key'];
114 | }
115 |
116 | Map toJson() {
117 | final Map data = new Map();
118 | data['area'] = this.area;
119 | data['bucket'] = this.bucket;
120 | data['secretId'] = this.secretId;
121 | data['secretKey'] = this.secretKey;
122 | data['key'] = this.key;
123 | return data;
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/lib/utils/strategy/impl/github_image_upload.dart:
--------------------------------------------------------------------------------
1 | import 'package:dio/dio.dart';
2 | import 'package:flutter_picgo/api/github_api.dart';
3 | import 'package:flutter_picgo/model/github_config.dart';
4 | import 'package:flutter_picgo/model/uploaded.dart';
5 | import 'package:flutter_picgo/resources/pb_type_keys.dart';
6 | import 'package:flutter_picgo/utils/encrypt.dart';
7 | import 'package:flutter_picgo/utils/image_upload.dart';
8 | import 'package:flutter_picgo/utils/strings.dart';
9 | import 'dart:io';
10 | import 'dart:convert';
11 | import 'package:path/path.dart' as path;
12 | import 'package:flutter_picgo/utils/strategy/image_upload_strategy.dart';
13 |
14 | class GithubImageUpload implements ImageUploadStrategy {
15 | static const UPLOAD_COMMIT_MESSAGE = "Upload by Flutter-PicGo";
16 | static const DELETE_COMMIT_MESSAGE = "Delete by Flutter-PicGo";
17 |
18 | @override
19 | Future delete(Uploaded uploaded) async {
20 | GithubUploadedInfo info;
21 | try {
22 | info = GithubUploadedInfo.fromJson(json.decode(uploaded.info));
23 | } catch (e) {}
24 | if (info != null) {
25 | String realUrl =
26 | path.joinAll(['repos', info.ownerrepo, 'contents', info.path]);
27 | await GithubApi.deleteContent(realUrl, {
28 | "message": DELETE_COMMIT_MESSAGE,
29 | "sha": info.sha,
30 | "branch": info.branch
31 | });
32 | }
33 | return uploaded;
34 | }
35 |
36 | @override
37 | Future upload(File file, String renameImage) async {
38 | try {
39 | String configStr = await ImageUploadUtils.getPBConfig(PBTypeKeys.github);
40 | if (!isBlank(configStr)) {
41 | GithubConfig config = GithubConfig.fromJson(json.decode(configStr));
42 | String realUrl = path.joinAll(
43 | ['repos', config.repo, 'contents', config.path, renameImage]);
44 | var result = await GithubApi.putContent(realUrl, {
45 | "message": UPLOAD_COMMIT_MESSAGE,
46 | "content": await EncryptUtils.image2Base64(file.path),
47 | "branch": config.branch
48 | });
49 | String imagePath = result["content"]["path"];
50 | String downloadUrl = result["content"]["download_url"];
51 | String sha = result["content"]["sha"];
52 | String imageUrl = isBlank(config.customUrl)
53 | ? downloadUrl
54 | : '${path.joinAll([config.customUrl, imagePath])}';
55 | var uploadedItem = Uploaded(-1, imageUrl, PBTypeKeys.github,
56 | info: json.encode(GithubUploadedInfo(
57 | path: imagePath,
58 | sha: sha,
59 | branch: config.branch,
60 | ownerrepo: config.repo)));
61 | await ImageUploadUtils.saveUploadedItem(uploadedItem);
62 | return uploadedItem;
63 | } else {
64 | throw GithubError(error: '读取配置文件错误!请重试');
65 | }
66 | } on DioError catch (e) {
67 | if (e.type == DioErrorType.response &&
68 | e.error.toString().indexOf('422') > 0) {
69 | throw GithubError(error: '文件已存在!');
70 | } else {
71 | throw e;
72 | }
73 | }
74 | }
75 | }
76 |
77 | /// GithubError describes the error info when request failed.
78 | class GithubError implements Exception {
79 | GithubError({
80 | this.error,
81 | });
82 |
83 | dynamic error;
84 |
85 | String get message => (error?.toString() ?? '');
86 |
87 | @override
88 | String toString() {
89 | var msg = 'GithubError $message';
90 | if (error is Error) {
91 | msg += '\n${error.stackTrace}';
92 | }
93 | return msg;
94 | }
95 | }
96 |
97 | class GithubUploadedInfo {
98 | String sha;
99 | String branch;
100 | String path;
101 | String ownerrepo;
102 |
103 | GithubUploadedInfo({this.sha, this.branch, this.path, this.ownerrepo});
104 |
105 | GithubUploadedInfo.fromJson(Map json) {
106 | sha = json['sha'];
107 | branch = json['branch'];
108 | path = json['path'];
109 | ownerrepo = json['ownerrepo'];
110 | }
111 |
112 | Map toJson() {
113 | final Map data = new Map();
114 | data['sha'] = this.sha;
115 | data['branch'] = this.branch;
116 | data['path'] = this.path;
117 | data['ownerrepo'] = this.ownerrepo;
118 | return data;
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/lib/utils/strategy/impl/aliyun_image_upload.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:dio/dio.dart';
4 | import 'package:flutter_picgo/api/aliyun_api.dart';
5 | import 'package:flutter_picgo/model/aliyun_config.dart';
6 | import 'package:flutter_picgo/model/uploaded.dart';
7 | import 'package:flutter_picgo/resources/pb_type_keys.dart';
8 | import 'package:flutter_picgo/utils/image_upload.dart';
9 | import 'dart:io';
10 | import 'package:path/path.dart' as path;
11 | import 'package:flutter_picgo/utils/strategy/image_upload_strategy.dart';
12 | import 'package:flutter_picgo/utils/strings.dart';
13 |
14 | class AliyunImageUpload implements ImageUploadStrategy {
15 | @override
16 | Future delete(Uploaded uploaded) async {
17 | AliyunUploadedInfo info;
18 | try {
19 | info = AliyunUploadedInfo.fromJson(json.decode(uploaded.info));
20 | } catch (e) {}
21 | if (info != null) {
22 | String auth = AliyunApi.buildSignature(info.accessKeyId,
23 | info.accessKeySecret, 'delete', info.bucket, info.object);
24 | await AliyunApi.deleteObject(info.bucket, info.area, info.object, auth);
25 | }
26 | return uploaded;
27 | }
28 |
29 | @override
30 | Future upload(File file, String renameImage) async {
31 | String configStr = await ImageUploadUtils.getPBConfig(PBTypeKeys.aliyun);
32 | if (isBlank(configStr)) {
33 | throw AliyunError(error: '读取配置文件错误!请重试');
34 | }
35 | AliyunConfig config = AliyunConfig.fromJson(json.decode(configStr));
36 | String objectPath = isBlank(config.path)
37 | ? renameImage
38 | : path.joinAll([config.path, renameImage]);
39 | String policy = AliyunApi.buildEncodePolicy(objectPath);
40 | await AliyunApi.postObject(
41 | config.bucket,
42 | config.area,
43 | FormData.fromMap({
44 | 'key': objectPath,
45 | 'OSSAccessKeyId': config.accessKeyId,
46 | 'policy': policy,
47 | 'Signature': AliyunApi.buildPostSignature(
48 | config.accessKeyId, config.accessKeySecret, policy),
49 | 'file':
50 | await MultipartFile.fromFile(file.path, filename: renameImage),
51 | // OSS支持用户在Post请求体中增加x-oss-content-type,该项允许用户指定Content-Type
52 | 'x-oss-content-type':
53 | 'image/${path.extension(renameImage).replaceFirst('.', '')}'
54 | }));
55 | String imgPath = path.joinAll([
56 | isBlank(config.customUrl)
57 | ? 'https://${config.bucket}.${config.area}.aliyuncs.com'
58 | : config.customUrl,
59 | objectPath
60 | ]);
61 | var uploadedItem = Uploaded(
62 | -1, '$imgPath${config.options ?? ''}', PBTypeKeys.aliyun,
63 | info: json.encode(AliyunUploadedInfo(
64 | accessKeyId: config.accessKeyId,
65 | accessKeySecret: config.accessKeySecret,
66 | area: config.area,
67 | bucket: config.bucket,
68 | object: objectPath)));
69 | await ImageUploadUtils.saveUploadedItem(uploadedItem);
70 | return uploadedItem;
71 | }
72 | }
73 |
74 | class AliyunError implements Exception {
75 | AliyunError({
76 | this.error,
77 | });
78 |
79 | dynamic error;
80 |
81 | String get message => (error?.toString() ?? '');
82 |
83 | @override
84 | String toString() {
85 | var msg = 'AliyunError $message';
86 | if (error is Error) {
87 | msg += '\n${error.stackTrace}';
88 | }
89 | return msg;
90 | }
91 | }
92 |
93 | class AliyunUploadedInfo {
94 | String accessKeyId;
95 | String accessKeySecret;
96 | String area;
97 | String bucket;
98 | String object;
99 |
100 | AliyunUploadedInfo(
101 | {this.accessKeyId,
102 | this.accessKeySecret,
103 | this.area,
104 | this.bucket,
105 | this.object});
106 |
107 | AliyunUploadedInfo.fromJson(Map json) {
108 | accessKeyId = json['accessKeyId'];
109 | accessKeySecret = json['accessKeySecret'];
110 | area = json['area'];
111 | bucket = json['bucket'];
112 | object = json['object'];
113 | }
114 |
115 | Map toJson() {
116 | final Map data = new Map();
117 | data['accessKeyId'] = this.accessKeyId;
118 | data['accessKeySecret'] = this.accessKeySecret;
119 | data['area'] = this.area;
120 | data['bucket'] = this.bucket;
121 | data['object'] = this.object;
122 | return data;
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/lib/utils/strategy/impl/gitee_image_upload.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:dio/dio.dart';
4 | import 'package:flutter_picgo/api/gitee_api.dart';
5 | import 'package:flutter_picgo/model/gitee_config.dart';
6 | import 'package:flutter_picgo/model/uploaded.dart';
7 | import 'package:flutter_picgo/resources/pb_type_keys.dart';
8 | import 'package:flutter_picgo/utils/encrypt.dart';
9 | import 'package:flutter_picgo/utils/image_upload.dart';
10 | import 'package:flutter_picgo/utils/strings.dart';
11 | import 'dart:io';
12 |
13 | import 'package:path/path.dart' as path;
14 | import 'package:flutter_picgo/utils/strategy/image_upload_strategy.dart';
15 |
16 | class GiteeImageUpload implements ImageUploadStrategy {
17 | static const UPLOAD_COMMIT_MESSAGE = "Upload by Flutter-PicGo";
18 | static const DELETE_COMMIT_MESSAGE = "Delete by Flutter-PicGo";
19 |
20 | @override
21 | Future delete(Uploaded uploaded) async {
22 | GiteeUploadedInfo info;
23 | try {
24 | info = GiteeUploadedInfo.fromJson(json.decode(uploaded.info));
25 | } catch (e) {}
26 | if (info != null) {
27 | String realUrl =
28 | path.joinAll(['repos', info.ownerrepo, 'contents', info.path]);
29 | Map mapData = {
30 | "message": DELETE_COMMIT_MESSAGE,
31 | "sha": info.sha,
32 | };
33 | if (!isBlank(info.branch)) {
34 | mapData["branch"] = info.branch;
35 | }
36 | await GiteeApi.deleteFile(realUrl, mapData);
37 | }
38 | return uploaded;
39 | }
40 |
41 | @override
42 | Future upload(File file, String renameImage) async {
43 | try {
44 | String configStr = await ImageUploadUtils.getPBConfig(PBTypeKeys.gitee);
45 | if (isBlank(configStr)) {
46 | throw GiteeError(error: '读取配置文件错误!请重试');
47 | }
48 | GiteeConfig config = GiteeConfig.fromJson(json.decode(configStr));
49 | String realUrl = path.joinAll([
50 | 'repos',
51 | config.owner,
52 | config.repo,
53 | 'contents',
54 | config.path,
55 | renameImage
56 | ]);
57 | Map mapData = {
58 | "message": UPLOAD_COMMIT_MESSAGE,
59 | "content": await EncryptUtils.image2Base64(file.path)
60 | };
61 | if (!isBlank(config.branch)) {
62 | mapData["branch"] = config.branch;
63 | }
64 | var result = await GiteeApi.createFile(realUrl, mapData);
65 | String imagePath = result["content"]["path"];
66 | String downloadUrl = result["content"]["download_url"];
67 | String sha = result["content"]["sha"];
68 | String imageUrl = config.customUrl == null || config.customUrl == ''
69 | ? downloadUrl
70 | : '${path.joinAll([config.customUrl, imagePath])}';
71 | var uploadedItem = Uploaded(-1, imageUrl, PBTypeKeys.gitee,
72 | info: json.encode(GiteeUploadedInfo(
73 | path: imagePath,
74 | sha: sha,
75 | branch: config.branch,
76 | ownerrepo: '${config.owner}/${config.repo}')));
77 | await ImageUploadUtils.saveUploadedItem(uploadedItem);
78 | return uploadedItem;
79 | } on DioError catch (e) {
80 | if (e.type == DioErrorType.response &&
81 | e.error.toString().indexOf('400') > 0) {
82 | throw GiteeError(error: '文件已存在!');
83 | } else {
84 | throw e;
85 | }
86 | }
87 | }
88 | }
89 |
90 | /// GithubError describes the error info when request failed.
91 | class GiteeError implements Exception {
92 | GiteeError({
93 | this.error,
94 | });
95 |
96 | dynamic error;
97 |
98 | String get message => (error?.toString() ?? '');
99 |
100 | @override
101 | String toString() {
102 | var msg = 'GithubError $message';
103 | if (error is Error) {
104 | msg += '\n${error.stackTrace}';
105 | }
106 | return msg;
107 | }
108 | }
109 |
110 | class GiteeUploadedInfo {
111 | String sha;
112 | String branch;
113 | String path;
114 | String ownerrepo;
115 |
116 | GiteeUploadedInfo({this.sha, this.branch, this.path, this.ownerrepo});
117 |
118 | GiteeUploadedInfo.fromJson(Map json) {
119 | sha = json['sha'];
120 | branch = json['branch'];
121 | path = json['path'];
122 | ownerrepo = json['ownerrepo'];
123 | }
124 |
125 | Map toJson() {
126 | final Map data = new Map();
127 | data['sha'] = this.sha;
128 | data['branch'] = this.branch;
129 | data['path'] = this.path;
130 | data['ownerrepo'] = this.ownerrepo;
131 | return data;
132 | }
133 | }
134 |
--------------------------------------------------------------------------------