forked from lxm_tools/flutter-picgo
Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
807e94159c | ||
|
|
9fbdb0cf01 | ||
|
|
4b76d588b4 | ||
|
|
6b9d8d8c3e | ||
|
|
93d3c3ab15 | ||
|
|
d4d8b36f8e | ||
|
|
81481a99f9 | ||
|
|
8c0b2f772c | ||
|
|
434152c063 | ||
|
|
b91692e237 | ||
|
|
b0cbcbb1d8 | ||
|
|
02c65a6875 | ||
|
|
c85ad49515 | ||
|
|
3b016f170a | ||
|
|
a7723d8ea7 | ||
|
|
87eba041d8 | ||
|
|
1080ae4534 | ||
|
|
1ef205ca71 | ||
|
|
d951e1a83b | ||
|
|
3647607e7c | ||
|
|
82ab0a5a14 | ||
|
|
bfe58ef8a1 | ||
|
|
a639cb8d09 | ||
|
|
3837f184c5 | ||
|
|
79557cd14a | ||
|
|
eda3dea7a5 | ||
|
|
5e7907a125 | ||
|
|
bc18d869c3 | ||
|
|
8348d84c44 | ||
|
|
1bef17638c | ||
|
|
ee2d3b7e3e | ||
|
|
32f121d93b | ||
|
|
46f1470aec | ||
|
|
44aeddbb7b | ||
|
|
b38e94c53c | ||
|
|
8bdd43b757 | ||
|
|
67dbaa1cc0 | ||
|
|
72e84620e2 | ||
|
|
caba6995c3 | ||
|
|
0c314b918c | ||
|
|
3f96199d44 | ||
|
|
1a7869251c | ||
|
|
a2ee90dd70 | ||
|
|
a21ab4174e | ||
|
|
f91c5c80ac | ||
|
|
780cd7c3c2 | ||
|
|
78c8df5e77 | ||
|
|
b45b0e452d | ||
|
|
afdb4313f9 | ||
|
|
61c07dda5f |
2
.github/ISSUE_TEMPLATE/bug-report.md
vendored
2
.github/ISSUE_TEMPLATE/bug-report.md
vendored
@@ -7,7 +7,7 @@ about: Something isn't working as expected
|
||||
|
||||
**仅限中文与英文**, 其他语言的提交将直接被关闭
|
||||
|
||||
### 您当前的flutter doctor信息
|
||||
### 您当前的flutter doctor信息或设备信息
|
||||
|
||||
### 预期的表现
|
||||
|
||||
|
||||
10
README.md
10
README.md
@@ -13,15 +13,16 @@
|
||||
|
||||
Flutter-PicGo: 一个用于快速上传图片并获取图片URL链接的**手机版**工具
|
||||
|
||||
Flutter-PicGo 本体支持如下图床:
|
||||
**Flutter-PicGo 本体支持如下图床:**
|
||||
|
||||
- GitHub [v1.0+]
|
||||
- SM.MS [v1.1+]
|
||||
- Gitee [v1.2+]
|
||||
- 七牛云 [v1.3+]
|
||||
- 阿里云OSS [v1.4+]
|
||||
- 腾讯云COS [v1.5+]
|
||||
|
||||
> 开发进度可以查看 Projects,会同步更新开发进度
|
||||
> 开发进度可以查看 [Projects](https://github.com/PicGo/flutter-picgo/projects),会同步更新开发进度
|
||||
|
||||
# 特色功能
|
||||
|
||||
@@ -30,13 +31,16 @@ Flutter-PicGo 本体支持如下图床:
|
||||
- 适配深色模式,可跟随系统或手动设置
|
||||
- 支持将flutter-picgo的配置导出至剪切板
|
||||
|
||||
> 基本使用配置与PicGo无异,可参考[配置文档](https://picgo.github.io/PicGo-Doc/zh/guide/config.html#%E5%9B%BE%E5%BA%8A%E5%8C%BA)。
|
||||
> 腾讯云COS仅支持v5版配置
|
||||
|
||||
# 应用截图
|
||||
|
||||

|
||||
|
||||
# 下载安装
|
||||
|
||||
点击此处下载[应用](https://github.com/hackycy/flutter-picgo/releases)。
|
||||
点击此处下载应用[releases](https://github.com/hackycy/flutter-picgo/releases)或[蒲公英](https://www.pgyer.com/flutter-picgo)。
|
||||
|
||||
Android版直接下载apk安装包或扫码
|
||||
|
||||
|
||||
10
docs/version.json
Normal file
10
docs/version.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"iOS": {
|
||||
"versionName": "1.5.2",
|
||||
"versionCode": "12"
|
||||
},
|
||||
"Android": {
|
||||
"versionName": "1.5.1",
|
||||
"versionCode": "12"
|
||||
}
|
||||
}
|
||||
@@ -29,4 +29,5 @@
|
||||
| 3 | 新增Gitee图床记录 |
|
||||
| 4 | 新增Qiniu图床记录 |
|
||||
| 5 | 新增阿里云OSS图床记录 |
|
||||
| 6 | 新增腾讯云COS图床记录 |
|
||||
|
||||
|
||||
11
lib/api/picgo_api.dart
Normal file
11
lib/api/picgo_api.dart
Normal file
@@ -0,0 +1,11 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_picgo/utils/net.dart';
|
||||
|
||||
class PicgoApi {
|
||||
/// 获取App最新版本
|
||||
static Future getLatestVersion() async {
|
||||
Response res = await NetUtils.getInstance().get(
|
||||
'https://cdn.jsdelivr.net/gh/PicGo/flutter-picgo@dev/docs/version.json');
|
||||
return res;
|
||||
}
|
||||
}
|
||||
173
lib/api/tcyun_api.dart
Normal file
173
lib/api/tcyun_api.dart
Normal file
@@ -0,0 +1,173 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_picgo/utils/net.dart';
|
||||
import 'package:flutter_picgo/utils/strings.dart';
|
||||
|
||||
class TcyunApi {
|
||||
static const String BASE_URL = 'myqcloud.com';
|
||||
|
||||
static const String secretKey = 'secretKey';
|
||||
static const String secretId = 'secretId';
|
||||
|
||||
static Future postObject(String secretId, String secretKey, String bucket, String area, String ext, FormData formData) async {
|
||||
Response res = await NetUtils.getInstance().post(
|
||||
'https://$bucket.cos.$area.$BASE_URL/',
|
||||
data: formData,
|
||||
options: Options(
|
||||
extra: {TcyunApi.secretId: secretId, TcyunApi.secretKey: secretKey},
|
||||
contentType: 'image/$ext'
|
||||
)
|
||||
);
|
||||
return res.headers;
|
||||
}
|
||||
|
||||
static Future deleteobject(String secretId, String secretKey, String bucket,
|
||||
String area, String key) async {
|
||||
Response res = await NetUtils.getInstance().delete(
|
||||
'https://$bucket.cos.$area.$BASE_URL/$key',
|
||||
options: Options(
|
||||
extra: {TcyunApi.secretId: secretId, TcyunApi.secretKey: secretKey},
|
||||
));
|
||||
return res.headers;
|
||||
}
|
||||
|
||||
/// 构造“策略”(Policy)
|
||||
/// 经过 Base64 编码的“策略”(Policy)内容
|
||||
static String buildPolicy(
|
||||
String bucket, String key, String secretId, String keyTime) {
|
||||
Map<String, dynamic> map = {
|
||||
"expiration": "2030-01-01T00:00:00.000Z",
|
||||
"conditions": [
|
||||
{"bucket": bucket},
|
||||
{"key": key},
|
||||
{"q-sign-algorithm": "sha1"},
|
||||
{"q-ak": secretId},
|
||||
{"q-sign-time": keyTime}
|
||||
]
|
||||
};
|
||||
return json.encode(map);
|
||||
}
|
||||
|
||||
/// post Signature
|
||||
static String buildSignature(
|
||||
String secretKey, String keyTime, String policy) {
|
||||
/// signkey
|
||||
var hmacsha1Signkey = Hmac(sha1, utf8.encode(secretKey));
|
||||
var signKey = hmacsha1Signkey.convert(utf8.encode(keyTime));
|
||||
// string to sign
|
||||
var stringToSign = sha1.convert(utf8.encode(policy));
|
||||
// signature
|
||||
var hmacsha1Signature = Hmac(sha1, utf8.encode('$signKey'));
|
||||
var signature = hmacsha1Signature.convert(utf8.encode('$stringToSign'));
|
||||
return '$signature';
|
||||
}
|
||||
|
||||
/// 生成 KeyTime
|
||||
/// 获取当前时间对应的 Unix 时间戳 StartTimestamp,Unix 时间戳是从 UTC(协调世界时,
|
||||
/// 或 GMT 格林威治时间)1970年1月1日0时0分0秒(北京时间 1970年1月1日8时0分0秒)起至现在的总秒数。
|
||||
/// 根据上述时间戳和期望的签名有效时长算出签名过期时间对应的 Unix 时间戳 EndTimestamp。
|
||||
/// 拼接签名有效时间,格式为StartTimestamp;EndTimestamp,即为 KeyTime。
|
||||
/// 示例:1557902800;1557910000
|
||||
static String buildKeyTime() {
|
||||
int startTime = (new DateTime.now().millisecondsSinceEpoch) ~/ 1000;
|
||||
int endTime = startTime + 99999;
|
||||
return '$startTime;$endTime';
|
||||
}
|
||||
|
||||
/// 生成 SignKey
|
||||
/// 使用 HMAC-SHA1 以 SecretKey 为密钥,以 KeyTime 为消息,计算消息摘要(哈希值),即为 SignKey。
|
||||
static String buildSignKey(String secretKey, String keyTime) {
|
||||
// 使用SecertKey对上一步生成的原始字符串计算HMAC-SHA1签名:
|
||||
var hmacsha1 = Hmac(sha1, utf8.encode(secretKey));
|
||||
var sign = hmacsha1.convert(utf8.encode(keyTime));
|
||||
return '$sign';
|
||||
}
|
||||
}
|
||||
|
||||
/// TcYun验签拦截器
|
||||
class TcyunInterceptor extends InterceptorsWrapper {
|
||||
@override
|
||||
Future onRequest(RequestOptions options) async {
|
||||
if (options.path.contains(TcyunApi.BASE_URL)) {
|
||||
/// 生成 KeyTime
|
||||
var keytime = TcyunApi.buildKeyTime();
|
||||
|
||||
/// 生成 SignKey
|
||||
var signKey = TcyunApi.buildSignKey(
|
||||
'${options.extra[TcyunApi.secretKey]}', keytime);
|
||||
|
||||
/// 生成 UrlParamList 和 HttpParameters
|
||||
String urlParamList = '';
|
||||
String httpParameters = '';
|
||||
if (options.queryParameters != null) {
|
||||
options.queryParameters.forEach((key, value) {
|
||||
urlParamList += '${key.toLowerCase()};';
|
||||
httpParameters +=
|
||||
'${key.toLowerCase()}=${isBlank(value) ? '' : value}&';
|
||||
});
|
||||
}
|
||||
if (urlParamList.length > 0) {
|
||||
urlParamList = urlParamList.substring(0, urlParamList.length - 1);
|
||||
}
|
||||
if (httpParameters.length > 0) {
|
||||
httpParameters = httpParameters.substring(0, httpParameters.length - 1);
|
||||
}
|
||||
|
||||
/// 生成 HeaderList 和 HttpHeaders
|
||||
String headerList = '';
|
||||
String httpHeaders = '';
|
||||
Map<String, dynamic> headers = {
|
||||
// 'Date': HttpDate.format(new DateTime.now()),
|
||||
'Host': options.uri.host
|
||||
};
|
||||
headers.addAll(options.headers);
|
||||
if (options.method.toUpperCase() == 'GET' ||
|
||||
options.method.toUpperCase() == 'HEAD' ||
|
||||
options.method.toUpperCase() == 'DELETE' ||
|
||||
options.method.toUpperCase() == 'OPTIONS') {
|
||||
headers.remove('content-type');
|
||||
}
|
||||
if (headers != null) {
|
||||
headers.forEach((key, value) {
|
||||
print('key = $key, value = $value');
|
||||
headerList += '${Uri.encodeComponent(key).toLowerCase()};';
|
||||
httpHeaders +=
|
||||
'${Uri.encodeComponent(key).toLowerCase()}=${Uri.encodeComponent(value)}&';
|
||||
});
|
||||
}
|
||||
if (headerList.length > 0) {
|
||||
headerList = headerList.substring(0, headerList.length - 1);
|
||||
}
|
||||
if (httpHeaders.length > 0) {
|
||||
httpHeaders = httpHeaders.substring(0, httpHeaders.length - 1);
|
||||
}
|
||||
|
||||
/// 生成 HttpString
|
||||
String method = options.method.toLowerCase();
|
||||
String httpString =
|
||||
'$method\n${options.uri.path}\n$httpParameters\n$httpHeaders\n';
|
||||
|
||||
/// 生成 StringToSign
|
||||
String stringtoSign =
|
||||
'sha1\n$keytime\n${sha1.convert(utf8.encode(httpString))}\n';
|
||||
|
||||
/// 生成 Signature
|
||||
var hmacsha1 = Hmac(sha1, utf8.encode(signKey));
|
||||
var sign = hmacsha1.convert(utf8.encode(stringtoSign));
|
||||
|
||||
/// 生成签名
|
||||
var realSign = 'q-sign-algorithm=sha1' +
|
||||
'&q-ak=${options.extra[TcyunApi.secretId]}' +
|
||||
'&q-sign-time=$keytime' +
|
||||
'&q-key-time=$keytime' +
|
||||
'&q-header-list=$headerList' +
|
||||
'&q-url-param-list=$urlParamList' +
|
||||
'&q-signature=$sign';
|
||||
headers.addAll({'Authorization': realSign});
|
||||
options.headers = headers;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
}
|
||||
36
lib/model/tcyun_config.dart
Normal file
36
lib/model/tcyun_config.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
class TcyunConfig {
|
||||
String area;
|
||||
String bucket;
|
||||
String customUrl;
|
||||
String path;
|
||||
String secretId;
|
||||
String secretKey;
|
||||
|
||||
TcyunConfig({
|
||||
this.area,
|
||||
this.bucket,
|
||||
this.customUrl,
|
||||
this.path,
|
||||
this.secretId,
|
||||
this.secretKey});
|
||||
|
||||
TcyunConfig.fromJson(Map<String, dynamic> json) {
|
||||
area = json['area'];
|
||||
bucket = json['bucket'];
|
||||
customUrl = json['customUrl'];
|
||||
path = json['path'];
|
||||
secretId = json['secretId'];
|
||||
secretKey = json['secretKey'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||
data['area'] = this.area;
|
||||
data['bucket'] = this.bucket;
|
||||
data['customUrl'] = this.customUrl;
|
||||
data['path'] = this.path;
|
||||
data['secretId'] = this.secretId;
|
||||
data['secretKey'] = this.secretKey;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -8,4 +8,6 @@ class PBTypeKeys {
|
||||
static const qiniu = 'qiniu';
|
||||
|
||||
static const aliyun = 'aliyun';
|
||||
|
||||
static const tcyun = 'tcyun';
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'package:flutter_picgo/views/pb_setting_page/github_page/github_repo_page
|
||||
import 'package:flutter_picgo/views/pb_setting_page/pb_setting_page.dart';
|
||||
import 'package:flutter_picgo/views/pb_setting_page/qiniu_page/qiniu_page.dart';
|
||||
import 'package:flutter_picgo/views/pb_setting_page/smms_page/smms_page.dart';
|
||||
import 'package:flutter_picgo/views/pb_setting_page/tcyun_page/tcyun_page.dart';
|
||||
import 'package:flutter_picgo/views/picgo_setting_page/theme_setting_page.dart';
|
||||
import 'package:flutter_picgo/views/upload_page/upload_page.dart';
|
||||
import 'package:flutter_picgo/views/pb_setting_page/github_page/github_page.dart';
|
||||
@@ -114,6 +115,11 @@ var pbsettingAliyunHandler = new Handler(
|
||||
handlerFunc: (context, parameters) => AliyunPage(),
|
||||
);
|
||||
|
||||
// 腾讯云COS图床设置页面
|
||||
var pbsettingTcyunHandler = new Handler(
|
||||
handlerFunc: (context, parameters) => TcyunPage(),
|
||||
);
|
||||
|
||||
// picgo设置页面
|
||||
var picgosettingHandler = new Handler(
|
||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) =>
|
||||
|
||||
@@ -25,9 +25,12 @@ class Routes {
|
||||
// --------- qiniu -------------------
|
||||
static const String settingPbQiniu = '/setting/pb/qiniu';
|
||||
// -----------------------------------
|
||||
// --------- qiniu -------------------
|
||||
// --------- aliyun -------------------
|
||||
static const String settingPbAliyun = '/setting/pb/aliyun';
|
||||
// -----------------------------------
|
||||
// --------- aliyun -------------------
|
||||
static const String settingPbTcyun = '/setting/pb/tcyun';
|
||||
// -----------------------------------
|
||||
|
||||
static void configureRoutes(Router router) {
|
||||
router.notFoundHandler = notfoundHandler;
|
||||
@@ -46,5 +49,6 @@ class Routes {
|
||||
router.define(settingPicgoTheme, handler: picggsettingThemeHandler);
|
||||
router.define(settingPbQiniu, handler: pbsettingQiniuHandler);
|
||||
router.define(settingPbAliyun, handler: pbsettingAliyunHandler);
|
||||
router.define(settingPbTcyun, handler: pbsettingTcyunHandler);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ class DbProvider {
|
||||
try {
|
||||
db = await openDatabase(
|
||||
path,
|
||||
version: 5,
|
||||
version: 6,
|
||||
onCreate: (db, version) async {
|
||||
// 创建pb_setting表
|
||||
_initPb(db);
|
||||
@@ -102,6 +102,9 @@ class DbProvider {
|
||||
// 阿里云OSS
|
||||
await db.rawInsert(
|
||||
'INSERT INTO $TABLE_NAME_PBSETTING(type, path, name, config, visible) VALUES("${PBTypeKeys.aliyun}", "/setting/pb/aliyun", "阿里云OSS图床", NULL, 1)');
|
||||
// 腾讯云COS
|
||||
await db.rawInsert(
|
||||
'INSERT INTO $TABLE_NAME_PBSETTING(type, path, name, config, visible) VALUES("${PBTypeKeys.tcyun}", "/setting/pb/tcyun", "腾讯云COS图床", NULL, 1)');
|
||||
// copy data
|
||||
// update authors set dynasty_index=(select id from dynasties where dynasties .name=authors.dynasty) where dynasty in (select name from dynasties )
|
||||
if (isExists) {
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'dart:async';
|
||||
|
||||
class Debounce {
|
||||
final int milliseconds;
|
||||
VoidCallback action;
|
||||
Timer _timer;
|
||||
|
||||
Debounce({this.milliseconds});
|
||||
|
||||
run(VoidCallback action) {
|
||||
if (_timer != null) {
|
||||
_timer.cancel();
|
||||
}
|
||||
|
||||
_timer = Timer(Duration(milliseconds: milliseconds), action);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_picgo/api/tcyun_api.dart';
|
||||
|
||||
const bool inProduction = const bool.fromEnvironment("dart.vm.product");
|
||||
|
||||
@@ -10,9 +11,13 @@ class NetUtils {
|
||||
_dio = new Dio(BaseOptions(
|
||||
connectTimeout: 30000, receiveTimeout: 30000, sendTimeout: 30000));
|
||||
if (!inProduction) {
|
||||
/// Log
|
||||
dio.interceptors
|
||||
.add(LogInterceptor(requestBody: true, responseBody: true));
|
||||
}
|
||||
|
||||
/// Tcyun Interceptor
|
||||
dio.interceptors.add(TcyunInterceptor());
|
||||
}
|
||||
|
||||
Dio get dio => _dio;
|
||||
|
||||
@@ -11,8 +11,14 @@ class Sql extends BaseModel {
|
||||
return await this.query(tableName);
|
||||
}
|
||||
|
||||
Future<List> getBySql(String where, List<dynamic> whereArgs) async {
|
||||
return await this.query(tableName, where: where, whereArgs: whereArgs);
|
||||
Future<List> getBySql(String where, List<dynamic> whereArgs,
|
||||
{String orderBy, int limit, int offset}) async {
|
||||
return await this.query(tableName,
|
||||
where: where,
|
||||
whereArgs: whereArgs,
|
||||
orderBy: orderBy,
|
||||
limit: limit,
|
||||
offset: offset);
|
||||
}
|
||||
|
||||
Future<int> rawUpdate(String sql, [List<dynamic> arguments]) async {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_picgo/api/aliyun_api.dart';
|
||||
import 'package:flutter_picgo/model/aliyun_config.dart';
|
||||
import 'package:flutter_picgo/model/uploaded.dart';
|
||||
@@ -29,7 +30,7 @@ class AliyunImageUpload implements ImageUploadStrategy {
|
||||
@override
|
||||
Future<Uploaded> upload(File file, String renameImage) async {
|
||||
String configStr = await ImageUploadUtils.getPBConfig(PBTypeKeys.aliyun);
|
||||
print(configStr);
|
||||
debugPrint(configStr);
|
||||
if (isBlank(configStr)) {
|
||||
throw AliyunError(error: '读取配置文件错误!请重试');
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'dart:convert';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:flutter_picgo/api/qiniu_api.dart';
|
||||
import 'package:flutter_picgo/model/qiniu_config.dart';
|
||||
@@ -37,12 +36,11 @@ class QiniuImageUpload extends ImageUploadStrategy {
|
||||
if (isBlank(result.toString())) {
|
||||
return uploaded;
|
||||
} else {
|
||||
throw new QiniuError(error: result['error'].toString());
|
||||
throw new QiniuError(error: '${result['error']}');
|
||||
}
|
||||
}
|
||||
return uploaded;
|
||||
} on DioError catch (e) {
|
||||
print(e.response.data);
|
||||
if (e.type == DioErrorType.RESPONSE &&
|
||||
e.error.toString().indexOf('400') > 0) {
|
||||
throw QiniuError(error: '400 请求报文格式错误');
|
||||
@@ -100,7 +98,6 @@ class QiniuImageUpload extends ImageUploadStrategy {
|
||||
await ImageUploadUtils.saveUploadedItem(uploadedItem);
|
||||
return uploadedItem;
|
||||
} on DioError catch (e) {
|
||||
debugPrint(e.response.data);
|
||||
if (e.type == DioErrorType.RESPONSE &&
|
||||
e.error.toString().indexOf('400') > 0) {
|
||||
throw QiniuError(error: '400 请求报文格式错误');
|
||||
|
||||
126
lib/utils/strategy/impl/tcyun_image_upload.dart
Normal file
126
lib/utils/strategy/impl/tcyun_image_upload.dart
Normal file
@@ -0,0 +1,126 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_picgo/api/tcyun_api.dart';
|
||||
import 'package:flutter_picgo/model/tcyun_config.dart';
|
||||
import 'package:flutter_picgo/model/uploaded.dart';
|
||||
import 'package:flutter_picgo/resources/pb_type_keys.dart';
|
||||
import 'package:flutter_picgo/utils/image_upload.dart';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:flutter_picgo/utils/strategy/image_upload_strategy.dart';
|
||||
import 'package:flutter_picgo/utils/strings.dart';
|
||||
|
||||
class TcyunImageUpload implements ImageUploadStrategy {
|
||||
@override
|
||||
Future<Uploaded> delete(Uploaded uploaded) async {
|
||||
TcyunUploaddedInfo info;
|
||||
try {
|
||||
info = TcyunUploaddedInfo.fromJson(json.decode(uploaded.info));
|
||||
} catch (e) {}
|
||||
if (info != null) {
|
||||
await TcyunApi.deleteobject(
|
||||
info.secretId, info.secretKey, info.bucket, info.area, info.key);
|
||||
}
|
||||
return uploaded;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uploaded> upload(File file, String renameImage) async {
|
||||
String configStr = await ImageUploadUtils.getPBConfig(PBTypeKeys.tcyun);
|
||||
if (isBlank(configStr)) {
|
||||
throw TcyunError(error: '读取配置文件错误!请重试');
|
||||
}
|
||||
TcyunConfig config = TcyunConfig.fromJson(json.decode(configStr));
|
||||
String objectPath = isBlank(config.path)
|
||||
? renameImage
|
||||
: path.joinAll([config.path, renameImage]);
|
||||
|
||||
/// post
|
||||
String keyTime = TcyunApi.buildKeyTime();
|
||||
String policy = TcyunApi.buildPolicy(
|
||||
config.bucket, objectPath, config.secretId, keyTime);
|
||||
FormData formData = FormData.fromMap({
|
||||
"key": objectPath,
|
||||
"file": await MultipartFile.fromFile(file.path, filename: renameImage),
|
||||
"policy": base64.encode(utf8.encode(policy)),
|
||||
"q-sign-algorithm": "sha1",
|
||||
"q-ak": config.secretId,
|
||||
"q-key-time": keyTime,
|
||||
"q-sign-time": keyTime,
|
||||
"q-signature": TcyunApi.buildSignature(config.secretKey, keyTime, policy)
|
||||
});
|
||||
print(TcyunApi.buildSignature(config.secretKey, keyTime, policy));
|
||||
await TcyunApi.postObject(
|
||||
config.secretId,
|
||||
config.secretKey,
|
||||
config.bucket,
|
||||
config.area,
|
||||
path.extension(renameImage).replaceFirst('.', ''),
|
||||
formData);
|
||||
String imgPath = path.joinAll([
|
||||
isBlank(config.customUrl)
|
||||
? 'https://${config.bucket}.cos.${config.area}.myqcloud.com'
|
||||
: config.customUrl,
|
||||
objectPath
|
||||
]);
|
||||
var uploadedItem = Uploaded(-1, '$imgPath', PBTypeKeys.tcyun,
|
||||
info: json.encode(TcyunUploaddedInfo(
|
||||
secretId: config.secretId,
|
||||
secretKey: config.secretKey,
|
||||
area: config.area,
|
||||
key: objectPath,
|
||||
bucket: config.bucket)));
|
||||
await ImageUploadUtils.saveUploadedItem(uploadedItem);
|
||||
return uploadedItem;
|
||||
}
|
||||
}
|
||||
|
||||
class TcyunError implements Exception {
|
||||
TcyunError({
|
||||
this.error,
|
||||
});
|
||||
|
||||
dynamic error;
|
||||
|
||||
String get message => (error?.toString() ?? '');
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var msg = 'TcyunError $message';
|
||||
if (error is Error) {
|
||||
msg += '\n${error.stackTrace}';
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
||||
class TcyunUploaddedInfo {
|
||||
String area;
|
||||
String bucket;
|
||||
String secretId;
|
||||
String secretKey;
|
||||
String key;
|
||||
|
||||
TcyunUploaddedInfo(
|
||||
{this.area, this.bucket, this.secretId, this.secretKey, this.key});
|
||||
|
||||
TcyunUploaddedInfo.fromJson(Map<String, dynamic> json) {
|
||||
area = json['area'];
|
||||
bucket = json['bucket'];
|
||||
secretId = json['secretId'];
|
||||
secretKey = json['secretKey'];
|
||||
key = json['key'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||
data['area'] = this.area;
|
||||
data['bucket'] = this.bucket;
|
||||
data['secretId'] = this.secretId;
|
||||
data['secretKey'] = this.secretKey;
|
||||
data['key'] = this.key;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -5,25 +5,38 @@ import 'package:flutter_picgo/utils/strategy/impl/github_image_upload.dart';
|
||||
import 'package:flutter_picgo/utils/strategy/image_upload_strategy.dart';
|
||||
import 'package:flutter_picgo/utils/strategy/impl/qiniu_image_upload.dart';
|
||||
import 'package:flutter_picgo/utils/strategy/impl/smms_image_upload.dart';
|
||||
import 'package:flutter_picgo/utils/strategy/impl/tcyun_image_upload.dart';
|
||||
import 'package:flutter_picgo/utils/strings.dart';
|
||||
|
||||
class UploadStrategyFactory {
|
||||
static Map<String, ImageUploadStrategy> cache = {};
|
||||
|
||||
/// UploadStrategy工厂类,负责创建UploadStrategy
|
||||
static ImageUploadStrategy getUploadStrategy(String type) {
|
||||
if (isBlank(type)) {
|
||||
throw new NullThrownError();
|
||||
}
|
||||
if (type == PBTypeKeys.github) {
|
||||
return new GithubImageUpload();
|
||||
} else if (type == PBTypeKeys.smms) {
|
||||
return new SMMSImageUpload();
|
||||
} else if (type == PBTypeKeys.gitee) {
|
||||
return new GiteeImageUpload();
|
||||
} else if (type == PBTypeKeys.qiniu) {
|
||||
return new QiniuImageUpload();
|
||||
} else if (type == PBTypeKeys.aliyun) {
|
||||
return new AliyunImageUpload();
|
||||
if (cache[type] == null) {
|
||||
if (type == PBTypeKeys.github) {
|
||||
/// Github
|
||||
cache[type] = new GithubImageUpload();
|
||||
} else if (type == PBTypeKeys.smms) {
|
||||
/// SM.MS
|
||||
cache[type] = new SMMSImageUpload();
|
||||
} else if (type == PBTypeKeys.gitee) {
|
||||
/// Gitee
|
||||
cache[type] = new GiteeImageUpload();
|
||||
} else if (type == PBTypeKeys.qiniu) {
|
||||
/// 七牛
|
||||
cache[type] = new QiniuImageUpload();
|
||||
} else if (type == PBTypeKeys.aliyun) {
|
||||
/// 阿里云
|
||||
cache[type] = new AliyunImageUpload();
|
||||
} else if (type == PBTypeKeys.tcyun) {
|
||||
/// 腾讯云
|
||||
cache[type] = new TcyunImageUpload();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return cache[type];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'package:flutter_picgo/views/album_page/album_page_presenter.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:toast/toast.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||
|
||||
class AlbumPage extends StatefulWidget {
|
||||
@override
|
||||
@@ -19,6 +20,10 @@ class AlbumPage extends StatefulWidget {
|
||||
class _AlbumPageState extends State<AlbumPage> implements AlbumPageContract {
|
||||
AlbumPagePresenter _presenter;
|
||||
List<Uploaded> _uploadeds = [];
|
||||
RefreshController _refreshController =
|
||||
RefreshController(initialRefresh: false);
|
||||
int _perPageItemSize = 8;
|
||||
int _currentPage = 0;
|
||||
|
||||
_AlbumPageState() {
|
||||
_presenter = AlbumPagePresenter(this);
|
||||
@@ -27,7 +32,7 @@ class _AlbumPageState extends State<AlbumPage> implements AlbumPageContract {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_presenter.doLoadUploadedImages();
|
||||
_onRefresh();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -49,8 +54,44 @@ class _AlbumPageState extends State<AlbumPage> implements AlbumPageContract {
|
||||
transition: TransitionType.cupertino);
|
||||
},
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
body: SmartRefresher(
|
||||
enablePullDown: true,
|
||||
enablePullUp: this._uploadeds.length >= _perPageItemSize,
|
||||
header: ClassicHeader(
|
||||
refreshStyle: RefreshStyle.Follow,
|
||||
idleText: '下拉刷新',
|
||||
releaseText: '释放刷新',
|
||||
completeText: '加载完成',
|
||||
refreshingText: '刷新中',
|
||||
failedText: '加载失败,请重试',
|
||||
),
|
||||
footer: CustomFooter(
|
||||
builder: (BuildContext context, LoadStatus mode) {
|
||||
Widget body;
|
||||
if (mode == LoadStatus.idle) {
|
||||
body = Text("上拉加载");
|
||||
} else if (mode == LoadStatus.loading) {
|
||||
body = SizedBox(
|
||||
width: 15,
|
||||
height: 15,
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
} else if (mode == LoadStatus.failed) {
|
||||
body = Text("加载失败!点击重试!");
|
||||
} else if (mode == LoadStatus.canLoading) {
|
||||
body = Text("松手,加载更多!");
|
||||
} else {
|
||||
body = Text("没有更多数据了!");
|
||||
}
|
||||
return Container(
|
||||
height: 55.0,
|
||||
child: Center(child: body),
|
||||
);
|
||||
},
|
||||
),
|
||||
onRefresh: _onRefresh,
|
||||
onLoading: _onLoading,
|
||||
controller: _refreshController,
|
||||
child: _uploadeds.length > 0 ? albumView() : emptyView()),
|
||||
);
|
||||
}
|
||||
@@ -142,35 +183,44 @@ class _AlbumPageState extends State<AlbumPage> implements AlbumPageContract {
|
||||
}
|
||||
|
||||
Widget emptyView() {
|
||||
return ListView(
|
||||
children: <Widget>[
|
||||
SizedBox(height: 100),
|
||||
Center(
|
||||
child: Container(
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
width: 200,
|
||||
height: 200,
|
||||
child: Image.asset('assets/images/icon_empty_album.png',
|
||||
fit: BoxFit.fill),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
Center(
|
||||
child: Text(
|
||||
'相册暂无任何照片,快点击右下角按钮去上传吧',
|
||||
style: TextStyle(color: Colors.grey),
|
||||
),
|
||||
)
|
||||
],
|
||||
Center(
|
||||
child: Text(
|
||||
'相册暂无任何照片,快点击右下角按钮去上传吧',
|
||||
style: TextStyle(color: Colors.grey),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<dynamic> _onRefresh() async {
|
||||
/// 刷新
|
||||
_onRefresh() async {
|
||||
// _uploadeds.clear();
|
||||
setState(() {
|
||||
_uploadeds.clear();
|
||||
this._currentPage = 0;
|
||||
this._uploadeds.clear();
|
||||
_refreshController.resetNoData();
|
||||
});
|
||||
return _presenter.doLoadUploadedImages();
|
||||
_presenter.doLoadUploadedImages(_perPageItemSize, this._currentPage);
|
||||
}
|
||||
|
||||
/// 上拉加载
|
||||
_onLoading() async {
|
||||
_presenter.doLoadUploadedImages(_perPageItemSize, _currentPage += 1);
|
||||
}
|
||||
|
||||
/// 处理图片点击
|
||||
handleTap(int index) {
|
||||
Clipboard.setData(ClipboardData(text: _uploadeds[index].path));
|
||||
Toast.show('已复制到剪切板', context);
|
||||
@@ -179,13 +229,29 @@ class _AlbumPageState extends State<AlbumPage> implements AlbumPageContract {
|
||||
@override
|
||||
void loadUploadedImages(List<Uploaded> uploadeds) {
|
||||
setState(() {
|
||||
this._uploadeds.addAll(uploadeds);
|
||||
if (this._currentPage == 0) {
|
||||
_refreshController.refreshCompleted();
|
||||
} else if (this._currentPage > 0 &&
|
||||
(uploadeds == null || uploadeds.length == 0)) {
|
||||
_refreshController.loadNoData();
|
||||
} else {
|
||||
this._currentPage += 1;
|
||||
_refreshController.loadComplete();
|
||||
}
|
||||
if (uploadeds != null && uploadeds.length > 0) {
|
||||
this._uploadeds.addAll(uploadeds);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void loadError() {
|
||||
Toast.show('加载失败', context);
|
||||
if (this._currentPage == 1) {
|
||||
_refreshController.refreshFailed();
|
||||
} else {
|
||||
_refreshController.loadFailed();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -17,10 +17,11 @@ class AlbumPagePresenter {
|
||||
|
||||
AlbumPagePresenter(this._view);
|
||||
|
||||
doLoadUploadedImages() async {
|
||||
doLoadUploadedImages(int limit, int offest) async {
|
||||
try {
|
||||
var sql = Sql.setTable(TABLE_NAME_UPLOADED);
|
||||
var result = await sql.get();
|
||||
var result = await sql.getBySql(null, null,
|
||||
limit: limit, offset: offest * limit, orderBy: 'id DESC');
|
||||
List<Uploaded> uploadeds = result.map((v) {
|
||||
return Uploaded.fromMap(v);
|
||||
}).toList();
|
||||
|
||||
@@ -1,26 +1,20 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_picgo/model/aliyun_config.dart';
|
||||
import 'package:flutter_picgo/model/config.dart';
|
||||
import 'package:flutter_picgo/resources/pb_type_keys.dart';
|
||||
import 'package:flutter_picgo/views/pb_setting_page/aliyun_page/aliyun_page_presenter.dart';
|
||||
import 'package:flutter_picgo/utils/strings.dart';
|
||||
import 'package:flutter_picgo/views/pb_setting_page/base_pb_page_state.dart';
|
||||
|
||||
class AliyunPage extends StatefulWidget {
|
||||
_AliyunPageState createState() => _AliyunPageState();
|
||||
}
|
||||
|
||||
class _AliyunPageState extends BasePBSettingPageState<AliyunPage>
|
||||
implements AliyunPageContract {
|
||||
AliyunPagePresenter _presenter;
|
||||
|
||||
_AliyunPageState() {
|
||||
_presenter = AliyunPagePresenter(this);
|
||||
}
|
||||
|
||||
class _AliyunPageState extends BasePBSettingPageState<AliyunPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_presenter.doLoadConfig();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -33,9 +27,15 @@ class _AliyunPageState extends BasePBSettingPageState<AliyunPage>
|
||||
String get pbType => PBTypeKeys.aliyun;
|
||||
|
||||
@override
|
||||
loadConfig(AliyunConfig config) {
|
||||
onLoadConfig(String config) {
|
||||
List<Config> configs = [];
|
||||
config.toJson().forEach((key, value) {
|
||||
Map<String, dynamic> map;
|
||||
if (isBlank(config)) {
|
||||
map = AliyunConfig().toJson();
|
||||
} else {
|
||||
map = AliyunConfig.fromJson(json.decode(config)).toJson();
|
||||
}
|
||||
map.forEach((key, value) {
|
||||
Config config;
|
||||
if (key == 'accessKeyId') {
|
||||
config = Config(
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter_picgo/model/aliyun_config.dart';
|
||||
import 'package:flutter_picgo/resources/pb_type_keys.dart';
|
||||
import 'package:flutter_picgo/utils/image_upload.dart';
|
||||
import 'package:flutter_picgo/utils/strings.dart';
|
||||
|
||||
abstract class AliyunPageContract {
|
||||
loadConfig(AliyunConfig config);
|
||||
}
|
||||
|
||||
class AliyunPagePresenter {
|
||||
AliyunPageContract _view;
|
||||
|
||||
AliyunPagePresenter(this._view);
|
||||
|
||||
doLoadConfig() async {
|
||||
try {
|
||||
var configStr = await ImageUploadUtils.getPBConfig(PBTypeKeys.aliyun);
|
||||
if (!isBlank(configStr)) {
|
||||
AliyunConfig config = AliyunConfig.fromJson(json.decode(configStr));
|
||||
_view.loadConfig(config);
|
||||
} else {
|
||||
_view.loadConfig(AliyunConfig());
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,12 @@ abstract class BasePBSettingPageState<T extends StatefulWidget>
|
||||
Map<String, TextEditingController> controllers = {};
|
||||
GlobalKey<FormState> _formKey;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
loadConfig();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@@ -140,6 +146,9 @@ abstract class BasePBSettingPageState<T extends StatefulWidget>
|
||||
/// 当前图床类型
|
||||
String get pbType;
|
||||
|
||||
/// 子类根据config String自定义需求
|
||||
onLoadConfig(String config);
|
||||
|
||||
/// 表单验证
|
||||
bool get validate => _formKey?.currentState?.validate() ?? true;
|
||||
|
||||
@@ -165,6 +174,14 @@ abstract class BasePBSettingPageState<T extends StatefulWidget>
|
||||
}
|
||||
}
|
||||
|
||||
/// 加载图床配置
|
||||
loadConfig() async {
|
||||
try {
|
||||
var configStr = await ImageUploadUtils.getPBConfig(pbType);
|
||||
onLoadConfig(configStr);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
/// 配置默认图床
|
||||
_setDefaultPB() async {
|
||||
if (!isBlank(pbType)) {
|
||||
|
||||
@@ -47,7 +47,7 @@ class _PBSettingPageState extends State<PBSettingPage>
|
||||
return AlertDialog(
|
||||
title: Text('关于使用二维码扫描说明'),
|
||||
content: Text('该功能是为了方便使用了PicGo PC端的用户。\n\n' +
|
||||
'在PC端已经配置好的用户可以将PicGo的配置文件转换成二维码。\n\n' +
|
||||
'在PC端已经配置好的用户可以使用PicGo新版的生成图床配置二维码功能。\n\n' +
|
||||
'然后使用Flutter-PicGo直接扫描即可进行配置转换。'),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
|
||||
@@ -1,27 +1,21 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_picgo/model/config.dart';
|
||||
import 'package:flutter_picgo/model/qiniu_config.dart';
|
||||
import 'package:flutter_picgo/resources/pb_type_keys.dart';
|
||||
import 'package:flutter_picgo/utils/strings.dart';
|
||||
import 'package:flutter_picgo/views/pb_setting_page/base_pb_page_state.dart';
|
||||
import 'package:flutter_picgo/views/pb_setting_page/qiniu_page/qiniu_page_presenter.dart';
|
||||
|
||||
class QiniuPage extends StatefulWidget {
|
||||
_QiniuPageState createState() => _QiniuPageState();
|
||||
}
|
||||
|
||||
class _QiniuPageState extends BasePBSettingPageState<QiniuPage>
|
||||
implements QiniuPageContract {
|
||||
QiniuPagePresenter _presenter;
|
||||
|
||||
_QiniuPageState() {
|
||||
_presenter = QiniuPagePresenter(this);
|
||||
}
|
||||
|
||||
class _QiniuPageState extends BasePBSettingPageState<QiniuPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_presenter.doLoadConfig();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -34,9 +28,15 @@ class _QiniuPageState extends BasePBSettingPageState<QiniuPage>
|
||||
);
|
||||
|
||||
@override
|
||||
loadConfig(QiniuConfig config) {
|
||||
onLoadConfig(String config) {
|
||||
List<Config> configs = [];
|
||||
config.toJson().forEach((key, value) {
|
||||
Map<String, dynamic> map;
|
||||
if (isBlank(config)) {
|
||||
map = QiniuConfig().toJson();
|
||||
} else {
|
||||
map = QiniuConfig.fromJson(json.decode(config)).toJson();
|
||||
}
|
||||
map.forEach((key, value) {
|
||||
Config config;
|
||||
if (key == 'accessKey') {
|
||||
config = Config(
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter_picgo/model/qiniu_config.dart';
|
||||
import 'package:flutter_picgo/resources/pb_type_keys.dart';
|
||||
import 'package:flutter_picgo/utils/image_upload.dart';
|
||||
import 'package:flutter_picgo/utils/strings.dart';
|
||||
|
||||
abstract class QiniuPageContract {
|
||||
loadConfig(QiniuConfig config);
|
||||
}
|
||||
|
||||
class QiniuPagePresenter {
|
||||
QiniuPageContract _view;
|
||||
|
||||
QiniuPagePresenter(this._view);
|
||||
|
||||
doLoadConfig() async {
|
||||
try {
|
||||
var configStr = await ImageUploadUtils.getPBConfig(PBTypeKeys.qiniu);
|
||||
if (!isBlank(configStr)) {
|
||||
QiniuConfig config = QiniuConfig.fromJson(json.decode(configStr));
|
||||
_view.loadConfig(config);
|
||||
} else {
|
||||
_view.loadConfig(QiniuConfig());
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
72
lib/views/pb_setting_page/tcyun_page/tcyun_page.dart
Normal file
72
lib/views/pb_setting_page/tcyun_page/tcyun_page.dart
Normal file
@@ -0,0 +1,72 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_picgo/model/config.dart';
|
||||
import 'package:flutter_picgo/model/tcyun_config.dart';
|
||||
import 'package:flutter_picgo/resources/pb_type_keys.dart';
|
||||
import 'package:flutter_picgo/utils/strings.dart';
|
||||
import 'package:flutter_picgo/views/pb_setting_page/base_pb_page_state.dart';
|
||||
|
||||
class TcyunPage extends StatefulWidget {
|
||||
_TcyunPageState createState() => _TcyunPageState();
|
||||
}
|
||||
|
||||
class _TcyunPageState extends BasePBSettingPageState<TcyunPage> {
|
||||
@override
|
||||
AppBar get appbar => AppBar(
|
||||
title: Text('腾讯云COS图床'),
|
||||
centerTitle: true,
|
||||
);
|
||||
|
||||
@override
|
||||
String get pbType => PBTypeKeys.tcyun;
|
||||
|
||||
@override
|
||||
onLoadConfig(String config) {
|
||||
List<Config> configs = [];
|
||||
Map<String, dynamic> map;
|
||||
if (isBlank(config)) {
|
||||
map = TcyunConfig().toJson();
|
||||
} else {
|
||||
map = TcyunConfig.fromJson(json.decode(config)).toJson();
|
||||
}
|
||||
map.forEach((key, value) {
|
||||
Config config;
|
||||
if (key == 'secretId') {
|
||||
config = Config(
|
||||
label: '设定SecretId',
|
||||
placeholder: 'SecretId',
|
||||
needValidate: true,
|
||||
value: value);
|
||||
} else if (key == 'secretKey') {
|
||||
config = Config(
|
||||
label: '设定SecretKey',
|
||||
placeholder: 'SecretKey',
|
||||
needValidate: true,
|
||||
value: value);
|
||||
} else if (key == 'bucket') {
|
||||
config = Config(
|
||||
label: '设定存储空间名',
|
||||
placeholder: '例如test-1253954259',
|
||||
needValidate: true,
|
||||
value: value);
|
||||
} else if (key == 'area') {
|
||||
config = Config(
|
||||
label: '确认存储区域',
|
||||
placeholder: '例如tj',
|
||||
needValidate: true,
|
||||
value: value);
|
||||
} else if (key == 'path') {
|
||||
config = Config(label: '指定存储路径', placeholder: '例如img/', value: value);
|
||||
} else if (key == 'customUrl') {
|
||||
config = Config(
|
||||
label: '设定自定义域名',
|
||||
placeholder: '例如:http://xxx.yyy.cn',
|
||||
value: value);
|
||||
}
|
||||
config.name = key;
|
||||
configs.add(config);
|
||||
});
|
||||
setConfigs(configs);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,17 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:fluro/fluro.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_picgo/api/picgo_api.dart';
|
||||
import 'package:flutter_picgo/routers/application.dart';
|
||||
import 'package:flutter_picgo/routers/routers.dart';
|
||||
import 'package:flutter_picgo/utils/local_notification.dart';
|
||||
import 'package:flutter_picgo/utils/shared_preferences.dart';
|
||||
import 'package:package_info/package_info.dart';
|
||||
import 'package:toast/toast.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class PicGoSettingPage extends StatefulWidget {
|
||||
@override
|
||||
@@ -17,6 +23,7 @@ class _PicGoSettingPageState extends State<PicGoSettingPage> {
|
||||
bool isTimestampRename = false;
|
||||
bool isUploadedTip = false;
|
||||
bool isForceDelete = false;
|
||||
bool isNeedUpdate = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -33,6 +40,8 @@ class _PicGoSettingPageState extends State<PicGoSettingPage> {
|
||||
u?.getBool(SharedPreferencesKeys.settingIsForceDelete) ?? false;
|
||||
});
|
||||
});
|
||||
// update
|
||||
_getLatestVersion();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -111,13 +120,26 @@ class _PicGoSettingPageState extends State<PicGoSettingPage> {
|
||||
transition: TransitionType.cupertino);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text('设置显示图床'),
|
||||
onTap: () {},
|
||||
),
|
||||
// ListTile(
|
||||
// title: Text('设置显示图床'),
|
||||
// onTap: () {},
|
||||
// ),
|
||||
ListTile(
|
||||
title: Text('检查更新'),
|
||||
onTap: () {},
|
||||
onTap: () {
|
||||
_handleUpdateTap();
|
||||
},
|
||||
trailing: CircleAvatar(
|
||||
backgroundColor: Colors.transparent,
|
||||
child: Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
// color: Colors.red,
|
||||
decoration: BoxDecoration(
|
||||
color: isNeedUpdate ? Colors.red : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(4)),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -131,4 +153,38 @@ class _PicGoSettingPageState extends State<PicGoSettingPage> {
|
||||
instance.putBool(key, value);
|
||||
Toast.show('保存成功', context);
|
||||
}
|
||||
|
||||
_getLatestVersion() async {
|
||||
try {
|
||||
Response res = await PicgoApi.getLatestVersion();
|
||||
PackageInfo info = await PackageInfo.fromPlatform();
|
||||
int version = int.parse(info.buildNumber);
|
||||
debugPrint('$version');
|
||||
int remoteVersion = 0;
|
||||
if (Platform.isAndroid) {
|
||||
remoteVersion = int.parse('${res.data["Android"]["versionCode"]}');
|
||||
} else if (Platform.isIOS) {
|
||||
remoteVersion = int.parse('${res.data["iOS"]["versionCode"]}');
|
||||
}
|
||||
if (version < remoteVersion) {
|
||||
setState(() {
|
||||
this.isNeedUpdate = true;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
|
||||
_handleUpdateTap() async {
|
||||
if (isNeedUpdate) {
|
||||
if (Platform.isAndroid) {
|
||||
launch('https://www.pgyer.com/flutter-picgo');
|
||||
} else if (Platform.isIOS) {
|
||||
launch('https://apps.apple.com/cn/app/flutter-picgo/id1519714305');
|
||||
}
|
||||
} else {
|
||||
Toast.show('当前已经是最新版本', context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,10 +21,11 @@ class _UploadPageState extends State<UploadPage>
|
||||
implements UploadPageContract {
|
||||
String _title = '';
|
||||
String _previewPath = '';
|
||||
String _renameImage;
|
||||
String _clipUrl;
|
||||
String _renameImage = '';
|
||||
String _clipUrl = '';
|
||||
TextEditingController _controller;
|
||||
int _selectButton = 1;
|
||||
// 默认不选中则不复制
|
||||
int _selectButton = 0;
|
||||
|
||||
// 通知提示
|
||||
bool needNotify = false;
|
||||
@@ -280,16 +281,16 @@ class _UploadPageState extends State<UploadPage>
|
||||
}
|
||||
|
||||
@override
|
||||
uploadFaild(String errorMsg) {
|
||||
uploadFaild(String errorMsg) async {
|
||||
if (needNotify) {
|
||||
_showNotification(0, '上传失败:$errorMsg');
|
||||
await _showNotification(0, '上传失败:$errorMsg');
|
||||
}
|
||||
Toast.show(errorMsg ?? '', context);
|
||||
}
|
||||
|
||||
/// 设置剪切板
|
||||
setClipData([bool needShowTip = true]) {
|
||||
if (_clipUrl == null || _clipUrl == '') {
|
||||
if (isBlank(_clipUrl)) {
|
||||
Toast.show('暂无可获取图片', context);
|
||||
return;
|
||||
}
|
||||
@@ -313,11 +314,13 @@ class _UploadPageState extends State<UploadPage>
|
||||
default:
|
||||
{
|
||||
//statements;
|
||||
cliptext = _clipUrl;
|
||||
cliptext = '';
|
||||
}
|
||||
break;
|
||||
}
|
||||
Clipboard.setData(ClipboardData(text: cliptext));
|
||||
if (!isBlank(cliptext)) {
|
||||
Clipboard.setData(ClipboardData(text: cliptext));
|
||||
}
|
||||
if (needShowTip) {
|
||||
Toast.show('已复制到剪切板', context);
|
||||
}
|
||||
@@ -326,11 +329,11 @@ class _UploadPageState extends State<UploadPage>
|
||||
@override
|
||||
uploadSuccess(String imageUrl) async {
|
||||
if (needNotify) {
|
||||
await _showNotification(1, '上传成功:图片链接已复制到剪切板,原链接:$imageUrl');
|
||||
await _showNotification(1, '上传成功, 图片链接:$imageUrl');
|
||||
}
|
||||
this._clipUrl = imageUrl;
|
||||
setClipData(false);
|
||||
Toast.show('上传成功:已复制到剪切板,图片链接为:$imageUrl', context);
|
||||
Toast.show('上传成功,图片链接为:$imageUrl', context);
|
||||
}
|
||||
|
||||
Future<void> _showNotification(int id, String body) async {
|
||||
|
||||
@@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 1.4.1+9
|
||||
version: 1.5.2+12
|
||||
|
||||
environment:
|
||||
sdk: ">=2.7.0 <3.0.0"
|
||||
@@ -39,6 +39,7 @@ dependencies:
|
||||
provider: ^4.1.3
|
||||
crypto: ^2.1.4
|
||||
flutter_local_notifications: ^1.4.4+1
|
||||
pull_to_refresh: ^1.6.0
|
||||
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
|
||||
60
test/api/tcyun_api_test.dart
Normal file
60
test/api/tcyun_api_test.dart
Normal file
@@ -0,0 +1,60 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_picgo/api/tcyun_api.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
main() {
|
||||
test('测试生成 KeyTime', () {
|
||||
print(TcyunApi.buildKeyTime());
|
||||
});
|
||||
|
||||
test('测试生成 SignKey', () {
|
||||
print(TcyunApi.buildSignKey('adsadaads', TcyunApi.buildKeyTime()));
|
||||
});
|
||||
|
||||
test('测试Api 签名', () async {
|
||||
try {
|
||||
await TcyunApi.deleteobject(
|
||||
'AKID70e28x4gazd17vKiywkITO1NSWHv6s75',
|
||||
'pN0MRGo5HCqDxTqpGPTFlCsHPKzUjhZD',
|
||||
'test-1253954259',
|
||||
'ap-nanjing',
|
||||
'wallhaven-2e9j79.jpg');
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
});
|
||||
|
||||
test('PostObjecy 提交', () async {
|
||||
try {
|
||||
String pathname = path.joinAll(
|
||||
[Directory.current.path, '..\\assets\\' 'images', 'logo.png']);
|
||||
print(pathname);
|
||||
String keyTime = TcyunApi.buildKeyTime();
|
||||
String policy = TcyunApi.buildPolicy('ap-nanjing', 'logo.png',
|
||||
'AKIDvb0B9rqfeOr44kt2ar46rO2cwzl6JwUk', keyTime);
|
||||
print(policy);
|
||||
// TcyunApi.postObject(
|
||||
// 'test-1253954259',
|
||||
// 'ap-nanjing',
|
||||
// 'png',
|
||||
// FormData.fromMap({
|
||||
// "key": "logo.png",
|
||||
// "file": await MultipartFile.fromFile(
|
||||
// 'C:\\Users\\Administrator\\Desktop\\flutter-picgo\\flutter-picgo\\assets\\images\\logo.png',
|
||||
// filename: 'logo.png'),
|
||||
// "policy": base64.encode(utf8.encode(policy)),
|
||||
// "q-sign-algorithm": "sha1",
|
||||
// "q-ak": "AKIDvb0B9rqfeOr44kt2ar46rO2cwzl6JwUk",
|
||||
// "q-key-time": keyTime,
|
||||
// "q-signature": TcyunApi.buildSignature(
|
||||
// 'KOEoR1LL5apX1lFRN4VgZB0nJgmdEbie', keyTime, policy)
|
||||
// }));
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user