Compare commits
69 Commits
93077980f0
..
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| d3dc71714d | |||
| 67246faace | |||
| c2387714f2 | |||
| e27671e247 | |||
| 6ebc05b376 | |||
| 0c87b714c4 | |||
| 49ec2a641e | |||
| f760e7239c | |||
| b01b2ed194 | |||
| def733f7c5 | |||
| 775e7c8d3a | |||
| 1225eb7d63 | |||
| 14d1f2efd8 | |||
| 7da5790eb2 | |||
| 8a0fc5e196 | |||
| f7dfffd7a3 | |||
| 42c654bb00 | |||
| b667823d27 | |||
| a6de246dd0 | |||
| bb9f00c89b | |||
| ec097dc16d | |||
| dd530fdf93 | |||
| c3ef400372 | |||
| 7ef959f9f7 | |||
| f3acdb0b8e | |||
| e169078b6c | |||
| 36f0d76552 | |||
| d74a0ad167 | |||
| 338c1c0b36 | |||
| cacb14cac4 | |||
| a2553e1419 | |||
| d2e07cd89d | |||
| 40eba0ddd7 | |||
| 049d9286f2 | |||
| a124f765cc | |||
| 44b5df3d24 | |||
| c567122d84 | |||
| a088f857e8 | |||
| 50d3da98a5 | |||
| faffbe1e00 | |||
| da28e25377 | |||
| 1109228933 | |||
| 6ac2e7a031 | |||
| b45fec2969 | |||
| d548cac4dc | |||
| 25675aae8d | |||
| 5a5c9c11b8 | |||
| 6a936e7364 | |||
| 827a3bbec8 | |||
| 52b5d9e259 | |||
| b2adb4bee3 | |||
| 489dc227de | |||
| 1a0d2f3acf | |||
| 32739b2dd9 | |||
| 251e9542ad | |||
| 44b230ed85 | |||
| d77b200d52 | |||
| 0f741d9ca8 | |||
| 1e740f230d | |||
| cac56c42cc | |||
| fb3df19bbe | |||
| 7531702f1b | |||
| 24ab0e593a | |||
| 1a4530ba55 | |||
| 58263f4548 | |||
| b7d0ec75eb | |||
| 85a7d36fda | |||
| 048c54e080 | |||
| 317aa006e3 |
@@ -1,2 +1,3 @@
|
|||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
db_data/
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
|
Copyright (c) 2022, dx8917312@gmail.com
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
@@ -1,2 +1,160 @@
|
|||||||
# DDE Gesture Manager
|
# DDE Gesture Manager
|
||||||
专为 DDE 桌面环境打造的触摸板手势管理工具
|
|
||||||
|

|
||||||
|
|
||||||
|
专为 [DDE](https://www.deepin.org/zh/dde/) 桌面环境打造的触摸板手势管理工具(缩写:dgm),客户端使用 [Flutter](https://flutter.dev/) 构建,后端技术栈为 [dart](https://dart.dev/) 的 [Angel3 框架](https://github.com/dukefirehawk/angel) + [PostgreSQL](https://www.postgresql.org/) + [Redis](https://redis.io/) + docker。
|
||||||
|
|
||||||
|
# web版
|
||||||
|
|
||||||
|
[DDE手势管理器-web版](http://www.debuggerx.com/dgm_web/#/)
|
||||||
|
|
||||||
|
# 功能
|
||||||
|
- 创建、编辑、删除本地手势配置方案
|
||||||
|
- 将选定手势方案应用到系统中
|
||||||
|
- 注册登陆后可以上传、分享自己创建的配置方案
|
||||||
|
- 可以下载、点赞他人分享的配置方案
|
||||||
|
- 贴合 DDE 的 UI 设计风格,支持系统主题切换和活动色
|
||||||
|
- 支持多语言
|
||||||
|
|
||||||
|
# 手册
|
||||||
|
[DDE手势管理器-说明手册](https://www.debuggerx.com/2022/01/21/dgm-manual/)
|
||||||
|
|
||||||
|
# 运行
|
||||||
|
|
||||||
|
## api
|
||||||
|
|
||||||
|
- 使用docker(推荐)
|
||||||
|
|
||||||
|
首先安装 docker 及 docker-compose,然后在`/api`目录下执行:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
bash start.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
- 手动运行
|
||||||
|
1. 首先配置 dart 环境(如果已经配置 flutter 开发环境则无需再配置):
|
||||||
|
[Dart SDK overview](https://dart.dev/tools/sdk)
|
||||||
|
|
||||||
|
2. 安装项目依赖,运行代码生成命令:
|
||||||
|
在`/api`目录下执行:
|
||||||
|
```shell
|
||||||
|
bash source_gen.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 安装 PostgreSQL 及 Redis
|
||||||
|
- [PostgreSQL Downloads](https://www.postgresql.org/download/)
|
||||||
|
- [Redis Downloads](https://redis.io/download)
|
||||||
|
|
||||||
|
然后在 `/config/development.yaml` 设置如下配置:
|
||||||
|
```yaml
|
||||||
|
# Development-only server configuration.
|
||||||
|
debug: true
|
||||||
|
postgres:
|
||||||
|
host: [db host]
|
||||||
|
port: 5432
|
||||||
|
database_name: gesture_manager
|
||||||
|
username: postgres
|
||||||
|
password: [db password]
|
||||||
|
use_ssl: false
|
||||||
|
time_zone: Asia/Shanghai
|
||||||
|
redis:
|
||||||
|
host: [redis host]
|
||||||
|
port: 6379
|
||||||
|
password: [redis password]
|
||||||
|
smtp:
|
||||||
|
username: [smtp account name]
|
||||||
|
password: [smtp account password]
|
||||||
|
host: [smtp server host]
|
||||||
|
```
|
||||||
|
|
||||||
|
4. 设置数据库
|
||||||
|
- 登录数据库,创建名为 `gesture_manager` 的数据库
|
||||||
|
|
||||||
|
```sql
|
||||||
|
create database gesture_manager;
|
||||||
|
```
|
||||||
|
|
||||||
|
- 运行 Migration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dart bin/migrate.dart
|
||||||
|
```
|
||||||
|
5. 运行 api
|
||||||
|
```bash
|
||||||
|
dart bin/dev.dart
|
||||||
|
```
|
||||||
|
|
||||||
|
## app
|
||||||
|
|
||||||
|
1. 配置 flutter 开发环境,并启用 Linux 支持:
|
||||||
|
- [Linux install](https://docs.flutter.dev/get-started/install/linux)
|
||||||
|
- [Additional Linux requirements](https://docs.flutter.dev/get-started/install/linux#additional-linux-requirements)
|
||||||
|
|
||||||
|
2. 修改服务器连接地址
|
||||||
|
在 `/api` 目录下修改 `lib/apis.dart`:
|
||||||
|
```dart
|
||||||
|
class Apis {
|
||||||
|
static const apiScheme = 'http';
|
||||||
|
static const apiHost = 'localhost'; // 设置为api的地址
|
||||||
|
static const apiPort = 3000; // 设置为api监听的端口
|
||||||
|
|
||||||
|
static const appNewVersionUrl = 'https://www.debuggerx.com';
|
||||||
|
|
||||||
|
……
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 安装项目依赖,运行代码生成命令:
|
||||||
|
在`/app`目录下执行:
|
||||||
|
```shell
|
||||||
|
bash source_gen.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
4. 运行app项目:
|
||||||
|
- Linux:
|
||||||
|
```shell
|
||||||
|
flutter run -d linux
|
||||||
|
```
|
||||||
|
|
||||||
|
- web:
|
||||||
|
```shell
|
||||||
|
flutter run -d chrome
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# RoadMap
|
||||||
|
|
||||||
|
|
||||||
|
- [x] 方案下载功能实现
|
||||||
|
- [x] 方案应用功能实现
|
||||||
|
- [ ] BugFix
|
||||||
|
- [x] MD 编辑器中的UI文本国际化
|
||||||
|
- [x] 编写帮助说明文档
|
||||||
|
- [ ] 浅色模式界面优化
|
||||||
|
- [ ] 打包上架 Deepin/UOS 应用商店
|
||||||
|
|
||||||
|
|
||||||
|
# FAQ
|
||||||
|
|
||||||
|
- Q:为什么要开发这个工具
|
||||||
|
|
||||||
|
A:本人是 [Deepin Linux](https://www.deepin.org/zh/) 的老粉了,日常学习工作和生活娱乐几乎完全在 Deepin/UOS 系统下进行。同时我还是个手势重度依赖者,除了鼠标手势,对笔记本的触摸板手势一样有很强的自定义需求。但是从 Deepin 系统增加手势功能到如今也有5年多了,官方一直没有在系统层面给出自定义触摸板手势的功能入口,我不得不经常通过手工修改系统手势配置文件的方式来实现自定义。但是长久以来,一方面是自己每次新装系统都需要重新设置,一方面是不断看到论坛和用户群有朋友反馈询问修改方法,遂决定动手写一个方便使用,并支持配置分享下载的GUI工具
|
||||||
|
|
||||||
|
- Q:为什么使用 flutter 开发而不是 Qt/DTK/GTK ……
|
||||||
|
|
||||||
|
A:因为本人对 flutter 比较熟悉,有4年多的研究积累,而且对于 flutter 的跨平台效果非常看好,而C/C++的经验相对缺乏,又恰逢2021下半年这个时间点,google官方的一大重点就是对桌面应用开发的支持,于是决定尝试通过使用 flutter 实现本工具。
|
||||||
|
|
||||||
|
- Q:为何还要兼容开发Web版本
|
||||||
|
|
||||||
|
A:得益于 flutter 的跨平台能力,在开发 Linux 桌面版应用的基础上,可以以很低的成本同步开发出 Web 版,于是一方面出于技术探索的目的,从一开始的功能规划我就将 Web 支持放在了基础需求中。另外,Web 版还有三个明显的好处:
|
||||||
|
|
||||||
|
1. 用户可以不必安装桌面应用,仅仅通过浏览器打开网页就能体验本工具的功能,方便了用户预览体验,也方便本项目的转发推广;
|
||||||
|
2. 由于 UOS 系统默认是锁 root 权限的,某些情况下的用户(比如机关单位的普通员工)可能不方便安装运行第三方软件,虽然我有将本工具上架 UOS 软件商店的打算,但是并不一定能够保证及时更新,所以此时可以通过使用 Web 版来实现和桌面版相同的功能和接近的体验;
|
||||||
|
3. 还有一部分用户可能使用的是国产CPU,可能并不是 flutter 的编译工具所支持的,或者虽然 flutter 支持,但是由于我没有对应的机器进项编译打包,所以可能暂时无法为这些用户提供二进制的程序使用,此时这些用户一样可以通过使用 Web 版来解决。
|
||||||
|
|
||||||
|
- Q:为何使用 dart 编写服务端,而不用其他更流行常见的语言和技术
|
||||||
|
|
||||||
|
A:作为全栈开发,虽然有多种其他语言和流行框架的后端开发经验,但是那些方案,有些是框架本身太重太吃资源,不适合这个小项目使用,有些是语言本身实在是写烦了,开发起来没有动力……在看到一些朋友和大佬分享使用 dart 开发后端的经验之后,我想,是不是可以让前后端项目使用相同的语言,以"同构"的方式开发,并将前后端的一些"弱关联"转变成由语法来保证正确性的"强依赖"呢?
|
||||||
|
|
||||||
|
所以在这个项目中,我让`api`直接作为`app`的依赖,`app`的网络请求处理中直接使用`api`侧导出的请求参数定义和结果模型,探索一种可以不用再通过文档进行前后端配合的开发模式——因为我相信,文档总是不可靠的,只有代码本身不会骗人 :joy:
|
||||||
|
|||||||
@@ -178,6 +178,7 @@ class RedisCacheProvider extends CacheProvider<List<int>> {
|
|||||||
|
|
||||||
// Force close the client
|
// Force close the client
|
||||||
scheduleMicrotask(() => client.close(force: true));
|
scheduleMicrotask(() => client.close(force: true));
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
+16
-12
@@ -1,14 +1,18 @@
|
|||||||
FROM google/dart:latest
|
FROM dart:stable AS build-env
|
||||||
|
LABEL stage=dart_builder
|
||||||
COPY ./ ./
|
ENV PUB_HOSTED_URL="https://pub.flutter-io.cn"
|
||||||
|
|
||||||
# Install dependencies, pre-build
|
|
||||||
RUN pub get
|
|
||||||
|
|
||||||
# Optionally build generaed sources.
|
|
||||||
# RUN pub run build_runner build
|
|
||||||
|
|
||||||
# Set environment, start server
|
|
||||||
ENV ANGEL_ENV=production
|
ENV ANGEL_ENV=production
|
||||||
|
COPY ./ ./
|
||||||
|
RUN pub get
|
||||||
|
RUN bash source_gen.sh && dart compile exe bin/prod.dart -o /server
|
||||||
|
ENTRYPOINT ["dart", "bin/migrate.dart", "up"]
|
||||||
|
|
||||||
|
FROM scratch
|
||||||
|
WORKDIR /app
|
||||||
|
ENV ANGEL_ENV=production
|
||||||
|
ADD ./views ./views
|
||||||
|
ADD ./config ./config
|
||||||
|
COPY --from=build-env /runtime/ /
|
||||||
|
COPY --from=build-env /server /app
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
CMD dart bin/prod.dart
|
ENTRYPOINT ["./server", "-a", "0.0.0.0", "--port", "3000"]
|
||||||
+3
-5
@@ -8,12 +8,11 @@ import 'package:logging/logging.dart';
|
|||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
// Watch the config/ and web/ directories for changes, and hot-reload the server.
|
// Watch the config/ and web/ directories for changes, and hot-reload the server.
|
||||||
hierarchicalLoggingEnabled = true;
|
|
||||||
|
|
||||||
var hot = HotReloader(() async {
|
var hot = HotReloader(() async {
|
||||||
var logger = Logger.detached('dde_gesture_manager_api')
|
Logger.root
|
||||||
..level = Level.ALL
|
..level = Level.ALL
|
||||||
..onRecord.listen(prettyLog);
|
..onRecord.listen(prettyLog);
|
||||||
|
var logger = Logger.detached('dde_gesture_manager_api');
|
||||||
var app = Angel(logger: logger, reflector: MirrorsReflector());
|
var app = Angel(logger: logger, reflector: MirrorsReflector());
|
||||||
await app.configure(configureServer);
|
await app.configure(configureServer);
|
||||||
return app;
|
return app;
|
||||||
@@ -23,6 +22,5 @@ void main() async {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
var server = await hot.startServer('127.0.0.1', 3000);
|
var server = await hot.startServer('127.0.0.1', 3000);
|
||||||
print(
|
print('dde_gesture_manager_api server listening at http://${server.address.address}:${server.port}');
|
||||||
'dde_gesture_manager_api server listening at http://${server.address.address}:${server.port}');
|
|
||||||
}
|
}
|
||||||
|
|||||||
+38
-4
@@ -1,10 +1,12 @@
|
|||||||
import 'package:angel3_migration/angel3_migration.dart';
|
|
||||||
import 'package:angel3_orm_postgres/angel3_orm_postgres.dart';
|
|
||||||
import 'package:dde_gesture_manager_api/src/config/plugins/orm.dart';
|
|
||||||
import 'package:dde_gesture_manager_api/models.dart';
|
|
||||||
import 'package:angel3_configuration/angel3_configuration.dart';
|
import 'package:angel3_configuration/angel3_configuration.dart';
|
||||||
|
import 'package:angel3_migration/angel3_migration.dart';
|
||||||
import 'package:angel3_migration_runner/angel3_migration_runner.dart';
|
import 'package:angel3_migration_runner/angel3_migration_runner.dart';
|
||||||
import 'package:angel3_migration_runner/postgres.dart';
|
import 'package:angel3_migration_runner/postgres.dart';
|
||||||
|
import 'package:angel3_orm_postgres/angel3_orm_postgres.dart';
|
||||||
|
import 'package:dde_gesture_manager_api/models.dart';
|
||||||
|
import 'package:dde_gesture_manager_api/src/config/plugins/orm.dart';
|
||||||
|
import 'package:dde_gesture_manager_api/src/models/download_history.dart';
|
||||||
|
import 'package:dde_gesture_manager_api/src/models/like_record.dart';
|
||||||
import 'package:file/local.dart';
|
import 'package:file/local.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
@@ -28,6 +30,11 @@ void main(List<String> args) async {
|
|||||||
var migrationRunner = PostgresMigrationRunner(connection, migrations: [
|
var migrationRunner = PostgresMigrationRunner(connection, migrations: [
|
||||||
UserMigration(),
|
UserMigration(),
|
||||||
UserSeed(),
|
UserSeed(),
|
||||||
|
SchemeMigration(),
|
||||||
|
DownloadHistoryMigration(),
|
||||||
|
LikeRecordMigration(),
|
||||||
|
AppVersionMigration(),
|
||||||
|
AppVersionSeed(),
|
||||||
]);
|
]);
|
||||||
await runMigrations(migrationRunner, args);
|
await runMigrations(migrationRunner, args);
|
||||||
}
|
}
|
||||||
@@ -59,3 +66,30 @@ Future doUserSeed() async {
|
|||||||
}
|
}
|
||||||
return connection.close();
|
return connection.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AppVersionSeed extends Migration {
|
||||||
|
@override
|
||||||
|
void up(Schema schema) async {
|
||||||
|
await doAppVersionSeed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void down(Schema schema) async {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future doAppVersionSeed() async {
|
||||||
|
var connection = await connectToPostgres(configuration);
|
||||||
|
await connection.open();
|
||||||
|
var executor = PostgreSqlExecutor(connection);
|
||||||
|
var appVersionQuery = AppVersionQuery();
|
||||||
|
var one = await appVersionQuery.getOne(executor);
|
||||||
|
if (one.isEmpty) {
|
||||||
|
appVersionQuery = AppVersionQuery();
|
||||||
|
appVersionQuery.values.copyFrom(AppVersion(
|
||||||
|
versionCode: 1,
|
||||||
|
versionName: '1.0.0',
|
||||||
|
));
|
||||||
|
return appVersionQuery.insert(executor).then((value) => connection.close());
|
||||||
|
}
|
||||||
|
return connection.close();
|
||||||
|
}
|
||||||
|
|||||||
+12
-7
@@ -2,12 +2,17 @@
|
|||||||
host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
port: 3000
|
port: 3000
|
||||||
postgres:
|
postgres:
|
||||||
host: localhost
|
host: db
|
||||||
port: 5432
|
port: 5432
|
||||||
database_name: appdb
|
database_name: gesture_manager
|
||||||
username: appuser
|
username: postgres
|
||||||
password: App1970#
|
password: gesture_manager_secret
|
||||||
useSSL: false
|
use_ssl: false
|
||||||
time_zone: UTC
|
time_zone: Asia/Shanghai
|
||||||
|
|
||||||
|
redis:
|
||||||
|
host: kv
|
||||||
|
port: 6379
|
||||||
|
|
||||||
jwt_secret: "OvA9SBLnncot8gFHvt8Gh1qkQ1ptGIQW"
|
jwt_secret: "OvA9SBLnncot8gFHvt8Gh1qkQ1ptGIQW"
|
||||||
password_salt: "test"
|
password_salt: "Z5b84rrgsKmfNFNRExAC4BCJe5aZPdJq"
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
version: "2.4"
|
||||||
|
|
||||||
|
services:
|
||||||
|
kv:
|
||||||
|
image: redis:alpine
|
||||||
|
restart: always
|
||||||
|
container_name: dgm_redis
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
networks:
|
||||||
|
- dgm_api_default
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: postgres:alpine
|
||||||
|
restart: always
|
||||||
|
container_name: dgm_postgres
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: gesture_manager
|
||||||
|
POSTGRES_PASSWORD: gesture_manager_secret
|
||||||
|
volumes:
|
||||||
|
- ../db_data:/var/lib/postgresql/data
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
networks:
|
||||||
|
- dgm_api_default
|
||||||
|
|
||||||
|
api:
|
||||||
|
build: .
|
||||||
|
image: dgm_api_image
|
||||||
|
container_name: dgm_api
|
||||||
|
ports:
|
||||||
|
- 3000:3000
|
||||||
|
restart: always
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
- kv
|
||||||
|
links:
|
||||||
|
- db:db
|
||||||
|
- kv:kv
|
||||||
|
networks:
|
||||||
|
- dgm_api_default
|
||||||
|
|
||||||
|
networks:
|
||||||
|
dgm_api_default:
|
||||||
|
name: dgm_api_default
|
||||||
+50
-4
@@ -1,20 +1,29 @@
|
|||||||
class Apis {
|
class Apis {
|
||||||
static const apiScheme = 'http';
|
static const apiScheme = 'http';
|
||||||
static const apiHost = '127.0.0.1';
|
static const apiHost = 'dgm_api.debuggerx.com';
|
||||||
static const apiPort = 3000;
|
static const apiPort = 3000;
|
||||||
|
|
||||||
static const appNewVersionUrl = 'https://www.debuggerx.com';
|
static const appNewVersionUrl = 'https://www.debuggerx.com/2022/01/21/dgm-changelog?from=app';
|
||||||
|
|
||||||
|
static appBulletinUrl(bool isWeb) =>
|
||||||
|
'https://www.debuggerx.com/dgm_web/bulletin.json?from=app_${isWeb ? 'web' : 'linux'}';
|
||||||
|
|
||||||
|
static appManualUrl(bool isWeb) =>
|
||||||
|
'https://www.debuggerx.com/2022/01/21/dgm-manual?from=app_${isWeb ? 'web' : 'linux'}';
|
||||||
|
|
||||||
static final system = SystemApis();
|
static final system = SystemApis();
|
||||||
static final auth = AuthApis();
|
static final auth = AuthApis();
|
||||||
|
static final scheme = SchemeApis();
|
||||||
}
|
}
|
||||||
|
|
||||||
class AuthApis {
|
class AuthApis {
|
||||||
static final String path = '/auth';
|
static final String path = '/auth';
|
||||||
|
|
||||||
String get loginOrSignup => [path, 'login_or_signup'].joinPath();
|
String get loginOrSignup => [path, 'login-or-signup'].joinPath();
|
||||||
|
|
||||||
String confirmSignup({required StringParam accessKey}) => [path, 'confirm_sign_up', accessKey].joinPath();
|
String confirmSignup({required StringParam accessKey}) => [path, 'confirm-sign-up', accessKey].joinPath();
|
||||||
|
|
||||||
|
String get status => [path, 'status'].joinPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
class SystemApis {
|
class SystemApis {
|
||||||
@@ -23,7 +32,28 @@ class SystemApis {
|
|||||||
String get appVersion => [path, 'app-version'].joinPath();
|
String get appVersion => [path, 'app-version'].joinPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SchemeApis {
|
||||||
|
static final String path = '/scheme';
|
||||||
|
|
||||||
|
String get upload => [path, 'upload'].joinPath();
|
||||||
|
|
||||||
|
String markAsShared({required StringParam schemeId}) => [path, 'mark-as-shared', schemeId].joinPath();
|
||||||
|
|
||||||
|
String user({required StringParam type}) => [path, 'user', type].joinPath();
|
||||||
|
|
||||||
|
String market({required StringParam type, required IntParam page, required IntParam pageSize}) =>
|
||||||
|
[path, 'market', type, page, pageSize].joinPath();
|
||||||
|
|
||||||
|
String download({required StringParam schemeId}) => [path, 'download', schemeId].joinPath();
|
||||||
|
|
||||||
|
String like({required StringParam schemeId, required StringParam isLike}) =>
|
||||||
|
[path, 'like', schemeId, isLike].joinPath();
|
||||||
|
|
||||||
|
String get userLikes => [path, 'user-likes'].joinPath();
|
||||||
|
}
|
||||||
|
|
||||||
final _paramsMap = {
|
final _paramsMap = {
|
||||||
|
'BoolParam': BoolParam.nameOnRoute,
|
||||||
'IntParam': IntParam.nameOnRoute,
|
'IntParam': IntParam.nameOnRoute,
|
||||||
'DoubleParam': DoubleParam.nameOnRoute,
|
'DoubleParam': DoubleParam.nameOnRoute,
|
||||||
'StringParam': StringParam.nameOnRoute,
|
'StringParam': StringParam.nameOnRoute,
|
||||||
@@ -47,6 +77,18 @@ extension RouteUrl on Function {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class BoolParam {
|
||||||
|
final bool val;
|
||||||
|
String? name;
|
||||||
|
|
||||||
|
BoolParam(this.val);
|
||||||
|
|
||||||
|
BoolParam.nameOnRoute(this.name) : val = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => name == null ? val.toString() : 'bool:$name';
|
||||||
|
}
|
||||||
|
|
||||||
class IntParam {
|
class IntParam {
|
||||||
final int val;
|
final int val;
|
||||||
String? name;
|
String? name;
|
||||||
@@ -83,6 +125,10 @@ class StringParam {
|
|||||||
String toString() => name == null ? val.toString() : ':$name';
|
String toString() => name == null ? val.toString() : ':$name';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension BoolParamExt on bool {
|
||||||
|
BoolParam get param => BoolParam(this);
|
||||||
|
}
|
||||||
|
|
||||||
extension IntParamExt on int {
|
extension IntParamExt on int {
|
||||||
IntParam get param => IntParam(this);
|
IntParam get param => IntParam(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
export 'src/models/user.dart';
|
export 'src/models/user.dart';
|
||||||
export 'src/models/app_version.dart';
|
export 'src/models/app_version.dart';
|
||||||
export 'src/models/login_success.dart';
|
export 'src/models/login_success.dart';
|
||||||
|
export 'src/models/scheme.dart';
|
||||||
@@ -9,7 +9,7 @@ Future<void> configureServer(Angel app) async {
|
|||||||
allowCookie: false,
|
allowCookie: false,
|
||||||
deserializer: (p) async => (UserQuery()..where!.id.equals(int.parse(p)))
|
deserializer: (p) async => (UserQuery()..where!.id.equals(int.parse(p)))
|
||||||
.getOne(app.container!.make<orm.QueryExecutor>())
|
.getOne(app.container!.make<orm.QueryExecutor>())
|
||||||
.then((value) => value.value),
|
.then((value) => value.isNotEmpty ? value.value : User(email: '')),
|
||||||
serializer: (p) => p.id ?? '',
|
serializer: (p) => p.id ?? '',
|
||||||
);
|
);
|
||||||
await auth.configureServer(app);
|
await auth.configureServer(app);
|
||||||
|
|||||||
@@ -3,11 +3,32 @@ import 'dart:io';
|
|||||||
import 'package:angel3_framework/angel3_framework.dart';
|
import 'package:angel3_framework/angel3_framework.dart';
|
||||||
import 'package:angel3_orm/angel3_orm.dart';
|
import 'package:angel3_orm/angel3_orm.dart';
|
||||||
import 'package:angel3_orm_postgres/angel3_orm_postgres.dart';
|
import 'package:angel3_orm_postgres/angel3_orm_postgres.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
import 'package:postgres/postgres.dart';
|
import 'package:postgres/postgres.dart';
|
||||||
|
|
||||||
|
const times = ['', 'st', 'nd', 'rd'];
|
||||||
|
|
||||||
|
const retryTimeOut = Duration(seconds: 30);
|
||||||
|
|
||||||
Future<void> configureServer(Angel app) async {
|
Future<void> configureServer(Angel app) async {
|
||||||
var connection = await connectToPostgres(app.configuration);
|
final _log = Logger('OrmPlugin');
|
||||||
|
late PostgreSQLConnection connection;
|
||||||
|
var _startTime = DateTime.now();
|
||||||
|
var _retry = 1;
|
||||||
|
while (_startTime.difference(DateTime.now()).abs() <= retryTimeOut) {
|
||||||
|
try {
|
||||||
|
connection = await connectToPostgres(app.configuration);
|
||||||
await connection.open();
|
await connection.open();
|
||||||
|
break;
|
||||||
|
} catch (e, st) {
|
||||||
|
await connection.close();
|
||||||
|
_log.severe(
|
||||||
|
'Failed to connect, the $_retry${_retry <= 3 ? times[_retry] : 'th'} retry will do in a second.', e, st);
|
||||||
|
sleep(Duration(seconds: 1));
|
||||||
|
_retry++;
|
||||||
|
if (_startTime.difference(DateTime.now()).abs() > retryTimeOut) rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var logger = app.environment.isProduction ? null : app.logger;
|
var logger = app.environment.isProduction ? null : app.logger;
|
||||||
var executor = PostgreSqlExecutor(connection, logger: logger);
|
var executor = PostgreSqlExecutor(connection, logger: logger);
|
||||||
|
|||||||
@@ -1,8 +1,25 @@
|
|||||||
|
import 'package:angel3_orm/angel3_orm.dart';
|
||||||
import 'package:angel3_serialize/angel3_serialize.dart';
|
import 'package:angel3_serialize/angel3_serialize.dart';
|
||||||
|
import 'package:dde_gesture_manager_api/src/models/base_model.dart';
|
||||||
|
import 'package:angel3_migration/angel3_migration.dart';
|
||||||
|
import 'package:optional/optional.dart';
|
||||||
part 'app_version.g.dart';
|
part 'app_version.g.dart';
|
||||||
|
|
||||||
@serializable
|
@serializable
|
||||||
abstract class _AppVersion {
|
@orm
|
||||||
|
abstract class _AppVersion extends BaseModel {
|
||||||
|
@SerializableField(isNullable: false)
|
||||||
|
@Column(isNullable: false)
|
||||||
|
String? get versionName;
|
||||||
|
|
||||||
|
@SerializableField(isNullable: false)
|
||||||
|
@Column(isNullable: false)
|
||||||
|
int? get versionCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@serializable
|
||||||
|
@Orm(tableName: 'app_versions', generateMigrations: false)
|
||||||
|
abstract class _AppVersionResp {
|
||||||
@SerializableField(isNullable: false)
|
@SerializableField(isNullable: false)
|
||||||
String? get versionName;
|
String? get versionName;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import 'package:angel3_orm/angel3_orm.dart';
|
||||||
|
import 'package:angel3_serialize/angel3_serialize.dart';
|
||||||
|
import 'package:dde_gesture_manager_api/src/models/base_model.dart';
|
||||||
|
import 'package:angel3_migration/angel3_migration.dart';
|
||||||
|
import 'package:optional/optional.dart';
|
||||||
|
|
||||||
|
part 'download_history.g.dart';
|
||||||
|
|
||||||
|
@serializable
|
||||||
|
@orm
|
||||||
|
abstract class _DownloadHistory extends BaseModel {
|
||||||
|
@Column(isNullable: false, indexType: IndexType.standardIndex)
|
||||||
|
@SerializableField(isNullable: false)
|
||||||
|
int? get uid;
|
||||||
|
|
||||||
|
@Column(isNullable: false, indexType: IndexType.standardIndex)
|
||||||
|
@SerializableField(isNullable: false)
|
||||||
|
int? get schemeId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import 'package:angel3_orm/angel3_orm.dart';
|
||||||
|
import 'package:angel3_serialize/angel3_serialize.dart';
|
||||||
|
import 'package:dde_gesture_manager_api/src/models/base_model.dart';
|
||||||
|
import 'package:angel3_migration/angel3_migration.dart';
|
||||||
|
import 'package:optional/optional.dart';
|
||||||
|
|
||||||
|
part 'like_record.g.dart';
|
||||||
|
|
||||||
|
@serializable
|
||||||
|
@orm
|
||||||
|
abstract class _LikeRecord extends BaseModel {
|
||||||
|
@Column(isNullable: false, indexType: IndexType.standardIndex)
|
||||||
|
@SerializableField(isNullable: false)
|
||||||
|
int? get uid;
|
||||||
|
|
||||||
|
@Column(isNullable: false, indexType: IndexType.standardIndex)
|
||||||
|
@SerializableField(isNullable: false)
|
||||||
|
int? get schemeId;
|
||||||
|
|
||||||
|
@Column(isNullable: false, indexType: IndexType.standardIndex)
|
||||||
|
@SerializableField(isNullable: false)
|
||||||
|
bool? get liked;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@serializable
|
||||||
|
@Orm(tableName: 'like_records', generateMigrations: false)
|
||||||
|
abstract class _UserLikes {
|
||||||
|
@Column(isNullable: false)
|
||||||
|
@SerializableField(isNullable: false)
|
||||||
|
int? id;
|
||||||
|
|
||||||
|
@Column(isNullable: false)
|
||||||
|
@SerializableField(exclude: true)
|
||||||
|
int? get uid;
|
||||||
|
|
||||||
|
@Column(isNullable: false)
|
||||||
|
@SerializableField(exclude: true)
|
||||||
|
bool? get liked;
|
||||||
|
}
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
import 'package:angel3_orm/angel3_orm.dart';
|
||||||
|
import 'package:angel3_serialize/angel3_serialize.dart';
|
||||||
|
import 'package:dde_gesture_manager_api/src/models/base_model.dart';
|
||||||
|
import 'package:angel3_migration/angel3_migration.dart';
|
||||||
|
import 'package:optional/optional.dart';
|
||||||
|
|
||||||
|
part 'scheme.g.dart';
|
||||||
|
|
||||||
|
@serializable
|
||||||
|
@orm
|
||||||
|
abstract class _Scheme extends BaseModel {
|
||||||
|
@Column(isNullable: false, indexType: IndexType.unique)
|
||||||
|
@SerializableField(isNullable: false)
|
||||||
|
String? get uuid;
|
||||||
|
|
||||||
|
@Column(isNullable: false)
|
||||||
|
@SerializableField(isNullable: false)
|
||||||
|
String? get name;
|
||||||
|
|
||||||
|
@Column(isNullable: false, indexType: IndexType.standardIndex)
|
||||||
|
@SerializableField(isNullable: true, exclude: true)
|
||||||
|
int? uid;
|
||||||
|
|
||||||
|
@Column(type: ColumnType.text)
|
||||||
|
String? description;
|
||||||
|
|
||||||
|
@Column(isNullable: false, indexType: IndexType.standardIndex)
|
||||||
|
@SerializableField(defaultValue: false, isNullable: false)
|
||||||
|
bool? get shared;
|
||||||
|
|
||||||
|
@Column(type: ColumnType.jsonb)
|
||||||
|
@SerializableField()
|
||||||
|
@DefaultsTo([])
|
||||||
|
List? get gestures;
|
||||||
|
}
|
||||||
|
|
||||||
|
@serializable
|
||||||
|
@Orm(tableName: 'schemes', generateMigrations: false)
|
||||||
|
abstract class _SimpleScheme {
|
||||||
|
@Column()
|
||||||
|
int? id;
|
||||||
|
|
||||||
|
@Column(isNullable: false, indexType: IndexType.unique)
|
||||||
|
@SerializableField(isNullable: false)
|
||||||
|
String? get uuid;
|
||||||
|
|
||||||
|
@Column(isNullable: false)
|
||||||
|
@SerializableField(isNullable: false)
|
||||||
|
String? get name;
|
||||||
|
|
||||||
|
@Column(isNullable: false, indexType: IndexType.standardIndex)
|
||||||
|
@SerializableField(isNullable: true, exclude: true)
|
||||||
|
int? uid;
|
||||||
|
|
||||||
|
@Column(type: ColumnType.text)
|
||||||
|
String? description;
|
||||||
|
|
||||||
|
@Column(isNullable: false, indexType: IndexType.standardIndex)
|
||||||
|
@SerializableField(defaultValue: false, isNullable: false)
|
||||||
|
bool? get shared;
|
||||||
|
|
||||||
|
@SerializableField(isNullable: true)
|
||||||
|
@Column(type: ColumnType.json)
|
||||||
|
Map<String, dynamic>? get metadata;
|
||||||
|
|
||||||
|
@SerializableField(isNullable: true)
|
||||||
|
@Column(expression: 'lr.liked')
|
||||||
|
bool? get liked;
|
||||||
|
}
|
||||||
|
|
||||||
|
@serializable
|
||||||
|
abstract class _SimpleSchemeTransMetaData {
|
||||||
|
@SerializableField(isNullable: false)
|
||||||
|
int? id;
|
||||||
|
|
||||||
|
@SerializableField(isNullable: false)
|
||||||
|
String? get uuid;
|
||||||
|
|
||||||
|
@SerializableField(isNullable: false)
|
||||||
|
String? get name;
|
||||||
|
|
||||||
|
@SerializableField(isNullable: false)
|
||||||
|
String? description;
|
||||||
|
|
||||||
|
@SerializableField(defaultValue: false, isNullable: false)
|
||||||
|
bool? get shared;
|
||||||
|
|
||||||
|
int? get downloads;
|
||||||
|
|
||||||
|
int? get likes;
|
||||||
|
|
||||||
|
bool get liked;
|
||||||
|
}
|
||||||
|
|
||||||
|
SimpleSchemeTransMetaData transSimpleSchemeMetaData(SimpleScheme scheme) => SimpleSchemeTransMetaData(
|
||||||
|
id: scheme.id,
|
||||||
|
description: scheme.description,
|
||||||
|
uuid: scheme.uuid,
|
||||||
|
name: scheme.name,
|
||||||
|
shared: scheme.shared,
|
||||||
|
liked: scheme.liked ?? false,
|
||||||
|
likes: scheme.metadata?['likes'] ?? 0,
|
||||||
|
downloads: scheme.metadata?['downloads'] ?? 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
@serializable
|
||||||
|
abstract class _SchemeForDownload {
|
||||||
|
@SerializableField(isNullable: false)
|
||||||
|
String? get uuid;
|
||||||
|
|
||||||
|
@SerializableField(isNullable: false)
|
||||||
|
String? get name;
|
||||||
|
|
||||||
|
@SerializableField(defaultValue: false, isNullable: false)
|
||||||
|
bool? get shared;
|
||||||
|
|
||||||
|
@Column(type: ColumnType.text)
|
||||||
|
String? description;
|
||||||
|
|
||||||
|
@SerializableField()
|
||||||
|
@DefaultsTo([])
|
||||||
|
List? get gestures;
|
||||||
|
}
|
||||||
|
|
||||||
|
SchemeForDownload transSchemeForDownload(Scheme scheme) => SchemeForDownload(
|
||||||
|
uuid: scheme.uuid,
|
||||||
|
name: scheme.name,
|
||||||
|
description: scheme.description,
|
||||||
|
gestures: scheme.gestures,
|
||||||
|
shared: scheme.shared,
|
||||||
|
);
|
||||||
|
|
||||||
|
@serializable
|
||||||
|
@Orm(tableName: 'schemes', generateMigrations: false)
|
||||||
|
abstract class _MarketScheme {
|
||||||
|
@Column()
|
||||||
|
int? id;
|
||||||
|
|
||||||
|
@Column(isNullable: false, indexType: IndexType.unique)
|
||||||
|
@SerializableField(isNullable: false)
|
||||||
|
String? get uuid;
|
||||||
|
|
||||||
|
@Column(isNullable: false)
|
||||||
|
@SerializableField(isNullable: false)
|
||||||
|
String? get name;
|
||||||
|
|
||||||
|
@Column(type: ColumnType.text)
|
||||||
|
String? description;
|
||||||
|
|
||||||
|
@Column(isNullable: false)
|
||||||
|
@SerializableField(exclude: true)
|
||||||
|
bool? get shared;
|
||||||
|
|
||||||
|
@SerializableField(isNullable: true)
|
||||||
|
@Column(type: ColumnType.json)
|
||||||
|
Map<String, dynamic>? get metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
@serializable
|
||||||
|
abstract class _MarketSchemeTransMetaData {
|
||||||
|
@SerializableField(isNullable: false)
|
||||||
|
int? id;
|
||||||
|
|
||||||
|
@SerializableField(isNullable: false)
|
||||||
|
String? get uuid;
|
||||||
|
|
||||||
|
@SerializableField(isNullable: false)
|
||||||
|
String? get name;
|
||||||
|
|
||||||
|
@SerializableField(isNullable: false)
|
||||||
|
String? description;
|
||||||
|
|
||||||
|
int? get downloads;
|
||||||
|
|
||||||
|
int? get likes;
|
||||||
|
}
|
||||||
|
|
||||||
|
MarketSchemeTransMetaData transMarketSchemeMetaData(MarketScheme scheme) => MarketSchemeTransMetaData(
|
||||||
|
id: scheme.id,
|
||||||
|
description: scheme.description,
|
||||||
|
uuid: scheme.uuid,
|
||||||
|
name: scheme.name,
|
||||||
|
likes: scheme.metadata?['likes'] ?? 0,
|
||||||
|
downloads: scheme.metadata?['downloads'] ?? 0,
|
||||||
|
);
|
||||||
@@ -16,9 +16,13 @@ abstract class _User extends BaseModel {
|
|||||||
@SerializableField(isNullable: false)
|
@SerializableField(isNullable: false)
|
||||||
String? get email;
|
String? get email;
|
||||||
|
|
||||||
@Column(isNullable: false, length: 32)
|
@Column(isNullable: false, length: 64)
|
||||||
@SerializableField(isNullable: true, exclude: true)
|
@SerializableField(isNullable: true, exclude: true)
|
||||||
String? get password;
|
String? get password;
|
||||||
|
|
||||||
|
@Column(isNullable: false)
|
||||||
|
@SerializableField(defaultValue: false)
|
||||||
|
bool? get blocked;
|
||||||
|
|
||||||
String secret(String salt) => base64.encode(Hmac(sha256, salt.codeUnits).convert((password ?? '').codeUnits).bytes);
|
String secret(String salt) => base64.encode(Hmac(sha256, salt.codeUnits).convert((password ?? '').codeUnits).bytes);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import 'package:angel3_auth/angel3_auth.dart';
|
|||||||
import 'package:angel3_framework/angel3_framework.dart';
|
import 'package:angel3_framework/angel3_framework.dart';
|
||||||
import 'package:dde_gesture_manager_api/apis.dart';
|
import 'package:dde_gesture_manager_api/apis.dart';
|
||||||
import 'package:dde_gesture_manager_api/models.dart';
|
import 'package:dde_gesture_manager_api/models.dart';
|
||||||
|
import 'package:dde_gesture_manager_api/src/routes/controllers/middlewares.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
import 'package:mailer/mailer.dart';
|
import 'package:mailer/mailer.dart';
|
||||||
import 'package:mailer/smtp_server.dart';
|
import 'package:mailer/smtp_server.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
@@ -44,10 +46,14 @@ Future configureServer(Angel app) async {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
send(message, smtpServer);
|
SendReport report = await send(message, smtpServer);
|
||||||
|
Logger('auth_controller').info(report);
|
||||||
|
|
||||||
return res.notFound();
|
return res.notFound();
|
||||||
} else if (user.value.password != userParams.password) {
|
} else if (user.value.password != userParams.password) {
|
||||||
return res.unauthorized();
|
return res.unauthorized();
|
||||||
|
} else if (user.value.blocked == true) {
|
||||||
|
return res.forbidden();
|
||||||
} else {
|
} else {
|
||||||
var angelAuth = req.container!.make<AngelAuth>();
|
var angelAuth = req.container!.make<AngelAuth>();
|
||||||
await angelAuth.loginById(user.value.id!, req, res);
|
await angelAuth.loginById(user.value.id!, req, res);
|
||||||
@@ -75,4 +81,14 @@ Future configureServer(Angel app) async {
|
|||||||
}
|
}
|
||||||
return res.render('sign_up_result.html', {'success': false});
|
return res.render('sign_up_result.html', {'success': false});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get(
|
||||||
|
Apis.auth.status,
|
||||||
|
chain(
|
||||||
|
[
|
||||||
|
jwtMiddleware(),
|
||||||
|
(req, res) => req.user!.blocked == false ? res.noContent() : res.forbidden(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:angel3_framework/angel3_framework.dart';
|
import 'package:angel3_framework/angel3_framework.dart';
|
||||||
import 'package:angel3_orm/angel3_orm.dart' as orm;
|
import 'package:angel3_orm/angel3_orm.dart' as orm;
|
||||||
|
import 'package:dde_gesture_manager_api/models.dart';
|
||||||
import 'package:dde_gesture_manager_api/src/config/plugins/redis_cache.dart';
|
import 'package:dde_gesture_manager_api/src/config/plugins/redis_cache.dart';
|
||||||
import 'package:neat_cache/neat_cache.dart';
|
import 'package:neat_cache/neat_cache.dart';
|
||||||
|
|
||||||
@@ -20,6 +21,16 @@ extension ResponseNoContent on ResponseContext {
|
|||||||
statusCode = HttpStatus.unauthorized;
|
statusCode = HttpStatus.unauthorized;
|
||||||
return close();
|
return close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
forbidden() {
|
||||||
|
statusCode = HttpStatus.forbidden;
|
||||||
|
return close();
|
||||||
|
}
|
||||||
|
|
||||||
|
unProcessableEntity() {
|
||||||
|
statusCode = HttpStatus.unprocessableEntity;
|
||||||
|
return close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension QueryWhereId on orm.Query {
|
extension QueryWhereId on orm.Query {
|
||||||
@@ -32,6 +43,16 @@ extension QueryExecutor on RequestContext {
|
|||||||
orm.QueryExecutor get queryExecutor => container!.make<orm.QueryExecutor>();
|
orm.QueryExecutor get queryExecutor => container!.make<orm.QueryExecutor>();
|
||||||
}
|
}
|
||||||
|
|
||||||
extension RedisExecutor on RequestContext {
|
extension RedisClient on RequestContext {
|
||||||
Cache get cache => container!.make<RedisCache>().cache;
|
Cache get cache => container!.make<RedisCache>().cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension JWTUserInstance on RequestContext {
|
||||||
|
User? get user {
|
||||||
|
try {
|
||||||
|
return container!.make<User>();
|
||||||
|
} catch (_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,16 +2,16 @@ import 'package:angel3_auth/angel3_auth.dart';
|
|||||||
import 'package:angel3_framework/angel3_framework.dart';
|
import 'package:angel3_framework/angel3_framework.dart';
|
||||||
|
|
||||||
import 'package:dde_gesture_manager_api/models.dart';
|
import 'package:dde_gesture_manager_api/models.dart';
|
||||||
|
import '../controllers/controller_extensions.dart';
|
||||||
|
|
||||||
RequestHandler jwtMiddleware() {
|
RequestHandler jwtMiddleware({ignoreError = false}) {
|
||||||
return (RequestContext req, ResponseContext res, {bool throwError = true}) async {
|
return (RequestContext req, ResponseContext res, {bool throwError = true}) async {
|
||||||
bool _reject(ResponseContext res) {
|
bool _reject(ResponseContext res, [ignoreError = false]) {
|
||||||
|
if (ignoreError) return true;
|
||||||
if (throwError) {
|
if (throwError) {
|
||||||
res.statusCode = 403;
|
res.forbidden();
|
||||||
throw AngelHttpException.forbidden();
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.container != null) {
|
if (req.container != null) {
|
||||||
@@ -19,17 +19,22 @@ RequestHandler jwtMiddleware() {
|
|||||||
if (reqContainer.has<User>() || req.method == 'OPTIONS') {
|
if (reqContainer.has<User>() || req.method == 'OPTIONS') {
|
||||||
return true;
|
return true;
|
||||||
} else if (reqContainer.has<Future<User>>()) {
|
} else if (reqContainer.has<Future<User>>()) {
|
||||||
|
try {
|
||||||
User user = await reqContainer.makeAsync<User>();
|
User user = await reqContainer.makeAsync<User>();
|
||||||
var authToken = req.container!.make<AuthToken>();
|
var authToken = req.container!.make<AuthToken>();
|
||||||
if (user.secret(req.app!.configuration['password_salt']) != authToken.payload[UserFields.password]) {
|
if (user.secret(req.app!.configuration['password_salt']) != authToken.payload[UserFields.password]) {
|
||||||
return _reject(res);
|
return _reject(res, ignoreError);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (ignoreError) return true;
|
||||||
|
rethrow;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return _reject(res);
|
return _reject(res, ignoreError);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return _reject(res);
|
return _reject(res, ignoreError);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,256 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:angel3_framework/angel3_framework.dart';
|
||||||
|
import 'package:dde_gesture_manager_api/apis.dart';
|
||||||
|
import 'package:dde_gesture_manager_api/src/models/download_history.dart';
|
||||||
|
import 'package:dde_gesture_manager_api/src/models/like_record.dart';
|
||||||
|
import 'package:dde_gesture_manager_api/src/models/scheme.dart';
|
||||||
|
import 'package:dde_gesture_manager_api/src/routes/controllers/middlewares.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'controller_extensions.dart';
|
||||||
|
|
||||||
|
Future configureServer(Angel app) async {
|
||||||
|
final _log = Logger('scheme_controller');
|
||||||
|
|
||||||
|
app.post(
|
||||||
|
Apis.scheme.upload,
|
||||||
|
chain(
|
||||||
|
[
|
||||||
|
jwtMiddleware(),
|
||||||
|
(req, res) async {
|
||||||
|
try {
|
||||||
|
var scheme = SchemeSerializer.fromMap(req.bodyAsMap);
|
||||||
|
var schemeQuery = SchemeQuery();
|
||||||
|
schemeQuery.where!.uuid.notEquals(scheme.uuid!);
|
||||||
|
schemeQuery.where!.name.equals(scheme.name!);
|
||||||
|
if ((await schemeQuery.getOne(req.queryExecutor)).isNotEmpty) {
|
||||||
|
res.statusCode = HttpStatus.locked;
|
||||||
|
return res.close();
|
||||||
|
}
|
||||||
|
req.queryExecutor.transaction((tx) async {
|
||||||
|
schemeQuery = SchemeQuery();
|
||||||
|
schemeQuery.where!.uuid.equals(scheme.uuid!);
|
||||||
|
var one = await schemeQuery.getOne(tx);
|
||||||
|
schemeQuery = SchemeQuery();
|
||||||
|
schemeQuery.values.copyFrom(scheme);
|
||||||
|
schemeQuery.values.uid = req.user!.idAsInt;
|
||||||
|
if (one.isEmpty) {
|
||||||
|
schemeQuery.values.metadata = {'author': req.user!.email};
|
||||||
|
return await schemeQuery.insert(tx);
|
||||||
|
} else {
|
||||||
|
schemeQuery.whereId = one.value.idAsInt;
|
||||||
|
return await schemeQuery.updateOne(tx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
_log.severe(e);
|
||||||
|
return res.unProcessableEntity();
|
||||||
|
}
|
||||||
|
return res.noContent();
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
app.post(
|
||||||
|
Apis.scheme.markAsShared.route,
|
||||||
|
chain(
|
||||||
|
[
|
||||||
|
jwtMiddleware(),
|
||||||
|
(req, res) async {
|
||||||
|
var schemeId = req.params['schemeId'];
|
||||||
|
var schemeQuery = SchemeQuery();
|
||||||
|
schemeQuery.where!.uuid.equals(schemeId);
|
||||||
|
schemeQuery.values.shared = true;
|
||||||
|
await schemeQuery.updateOne(req.queryExecutor);
|
||||||
|
return res.noContent();
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
app.get(
|
||||||
|
Apis.scheme.download.route,
|
||||||
|
chain(
|
||||||
|
[
|
||||||
|
jwtMiddleware(ignoreError: true),
|
||||||
|
(req, res) async {
|
||||||
|
var schemeQuery = SchemeQuery();
|
||||||
|
schemeQuery.where?.uuid.equals(req.params['schemeId']);
|
||||||
|
var optionalScheme = await schemeQuery.getOne(req.queryExecutor);
|
||||||
|
if (optionalScheme.isNotEmpty) {
|
||||||
|
var scheme = optionalScheme.value;
|
||||||
|
if (req.user != null) {
|
||||||
|
/// 增加用户下载记录
|
||||||
|
var downloadHistoryQuery = DownloadHistoryQuery();
|
||||||
|
downloadHistoryQuery.where?.uid.equals(req.user!.idAsInt);
|
||||||
|
downloadHistoryQuery.where?.schemeId.equals(scheme.idAsInt);
|
||||||
|
var notExist = (await downloadHistoryQuery.getOne(req.queryExecutor)).isEmpty;
|
||||||
|
if (notExist) {
|
||||||
|
downloadHistoryQuery = DownloadHistoryQuery();
|
||||||
|
downloadHistoryQuery.values.copyFrom(DownloadHistory(uid: req.user!.idAsInt, schemeId: scheme.idAsInt));
|
||||||
|
await downloadHistoryQuery.insert(req.queryExecutor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 增加方案的下载数量
|
||||||
|
schemeQuery = SchemeQuery();
|
||||||
|
schemeQuery.whereId = scheme.idAsInt;
|
||||||
|
Map<String, dynamic> metadata = Map.from(scheme.metadata!);
|
||||||
|
metadata.update('downloads', (value) => ++value, ifAbsent: () => 1);
|
||||||
|
schemeQuery.values.metadata = metadata;
|
||||||
|
schemeQuery.updateOne(req.queryExecutor);
|
||||||
|
|
||||||
|
return res.json(transSchemeForDownload(scheme));
|
||||||
|
}
|
||||||
|
return res.notFound();
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
app.get(
|
||||||
|
Apis.scheme.like.route,
|
||||||
|
chain(
|
||||||
|
[
|
||||||
|
jwtMiddleware(),
|
||||||
|
(req, res) async {
|
||||||
|
bool isLike = req.params['isLike'] == 'like';
|
||||||
|
bool needUpdate = true;
|
||||||
|
var schemeQuery = SchemeQuery();
|
||||||
|
schemeQuery.where?.uuid.equals(req.params['schemeId']);
|
||||||
|
var optionalScheme = await schemeQuery.getOne(req.queryExecutor);
|
||||||
|
if (optionalScheme.isNotEmpty) {
|
||||||
|
var scheme = optionalScheme.value;
|
||||||
|
if (req.user != null) {
|
||||||
|
/// 增加用户点赞记录
|
||||||
|
var likeRecordQuery = LikeRecordQuery();
|
||||||
|
likeRecordQuery.where?.uid.equals(req.user!.idAsInt);
|
||||||
|
likeRecordQuery.where?.schemeId.equals(scheme.idAsInt);
|
||||||
|
var likeRecordCheck = await likeRecordQuery.getOne(req.queryExecutor);
|
||||||
|
likeRecordQuery = LikeRecordQuery();
|
||||||
|
likeRecordQuery.values
|
||||||
|
.copyFrom(LikeRecord(uid: req.user!.idAsInt, schemeId: scheme.idAsInt, liked: isLike));
|
||||||
|
if (likeRecordCheck.isEmpty) {
|
||||||
|
likeRecordQuery.insert(req.queryExecutor);
|
||||||
|
} else if (likeRecordCheck.value.liked != isLike) {
|
||||||
|
likeRecordQuery.whereId = likeRecordCheck.value.idAsInt;
|
||||||
|
likeRecordQuery.updateOne(req.queryExecutor);
|
||||||
|
} else {
|
||||||
|
needUpdate = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needUpdate) {
|
||||||
|
/// 增加/减少方案的点赞数量
|
||||||
|
schemeQuery = SchemeQuery();
|
||||||
|
schemeQuery.whereId = scheme.idAsInt;
|
||||||
|
Map<String, dynamic> metadata = Map.from(scheme.metadata!);
|
||||||
|
metadata.update('likes', (value) => isLike ? ++value : --value, ifAbsent: () => 1);
|
||||||
|
schemeQuery.values.metadata = metadata;
|
||||||
|
schemeQuery.updateOne(req.queryExecutor);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.noContent();
|
||||||
|
}
|
||||||
|
return res.notFound();
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
app.get(
|
||||||
|
Apis.scheme.user.route,
|
||||||
|
chain(
|
||||||
|
[
|
||||||
|
jwtMiddleware(),
|
||||||
|
(req, res) async {
|
||||||
|
var schemeQuery = SimpleSchemeQuery();
|
||||||
|
var type = req.params['type'];
|
||||||
|
var likeRecordTableName = LikeRecordQuery().tableName;
|
||||||
|
schemeQuery.leftJoin(likeRecordTableName, SchemeFields.id, LikeRecordFields.schemeId, alias: 'lr');
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'uploaded':
|
||||||
|
schemeQuery.where!.uid.equals(req.user!.idAsInt);
|
||||||
|
break;
|
||||||
|
case 'downloaded':
|
||||||
|
var downloadHistoryTableName = DownloadHistoryQuery().tableName;
|
||||||
|
schemeQuery.leftJoin(downloadHistoryTableName, SchemeFields.id, DownloadHistoryFields.schemeId,
|
||||||
|
alias: 'dh');
|
||||||
|
schemeQuery.where!.raw('dh.${DownloadHistoryFields.uid} = ${req.user!.idAsInt}');
|
||||||
|
break;
|
||||||
|
case 'liked':
|
||||||
|
schemeQuery.where!.raw('lr.${LikeRecordFields.uid} = ${req.user!.idAsInt}');
|
||||||
|
schemeQuery.where!.raw('lr.${LikeRecordFields.liked} = true');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return res.unProcessableEntity();
|
||||||
|
}
|
||||||
|
schemeQuery.orderBy('${schemeQuery.tableName}.${SchemeFields.updatedAt}', descending: true);
|
||||||
|
return schemeQuery.get(req.queryExecutor).then((value) => value.map(transSimpleSchemeMetaData).toList());
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const recommend = "(metadata->'recommends') is null ,(metadata->'recommends')::int";
|
||||||
|
const updated = "updated_at";
|
||||||
|
const likes = "(metadata->'likes') is null ,(metadata->'likes')::int";
|
||||||
|
const downloads = "(metadata->'downloads') is null ,(metadata->'downloads')::int";
|
||||||
|
|
||||||
|
app.get(Apis.scheme.market.route, (req, res) async {
|
||||||
|
var schemeQuery = MarketSchemeQuery();
|
||||||
|
var type = req.params['type'];
|
||||||
|
var page = req.params['page'] as int;
|
||||||
|
var pageSize = req.params['pageSize'] as int;
|
||||||
|
schemeQuery.where?.shared.equals(true);
|
||||||
|
late List<String> orders;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'recommend':
|
||||||
|
orders = [recommend, likes, downloads, SchemeFields.id];
|
||||||
|
break;
|
||||||
|
case 'updated':
|
||||||
|
orders = [updated];
|
||||||
|
break;
|
||||||
|
case 'likes':
|
||||||
|
orders = [likes, recommend, downloads, SchemeFields.id];
|
||||||
|
break;
|
||||||
|
case 'downloads':
|
||||||
|
orders = [downloads, recommend, likes, SchemeFields.id];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return res.unProcessableEntity();
|
||||||
|
}
|
||||||
|
for (var order in orders) {
|
||||||
|
schemeQuery.orderBy(order, descending: true);
|
||||||
|
}
|
||||||
|
schemeQuery
|
||||||
|
..offset(page * pageSize)
|
||||||
|
..limit(pageSize + 1);
|
||||||
|
return schemeQuery.get(req.queryExecutor).then((value) {
|
||||||
|
var hasMore = value.length > pageSize;
|
||||||
|
if (hasMore) value.removeLast();
|
||||||
|
return {
|
||||||
|
'hasMore': hasMore,
|
||||||
|
'items': value.map(transMarketSchemeMetaData).toList(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get(
|
||||||
|
Apis.scheme.userLikes,
|
||||||
|
chain(
|
||||||
|
[
|
||||||
|
jwtMiddleware(),
|
||||||
|
(req, res) async => (UserLikesQuery()
|
||||||
|
..where?.uid.equals(req.user!.idAsInt)
|
||||||
|
..where?.liked.equals(true))
|
||||||
|
.get(req.queryExecutor)
|
||||||
|
.then((value) => value.map((e) => e.id).toList()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,24 +3,18 @@ import 'dart:async';
|
|||||||
import 'package:angel3_framework/angel3_framework.dart';
|
import 'package:angel3_framework/angel3_framework.dart';
|
||||||
import 'package:dde_gesture_manager_api/apis.dart';
|
import 'package:dde_gesture_manager_api/apis.dart';
|
||||||
import 'package:dde_gesture_manager_api/src/models/app_version.dart';
|
import 'package:dde_gesture_manager_api/src/models/app_version.dart';
|
||||||
import 'package:file/file.dart';
|
import 'controller_extensions.dart';
|
||||||
import 'package:yaml/yaml.dart';
|
|
||||||
|
|
||||||
late FileSystem fs;
|
|
||||||
|
|
||||||
Future configureServer(Angel app) async {
|
Future configureServer(Angel app) async {
|
||||||
app.get(
|
app.get(
|
||||||
Apis.system.appVersion,
|
Apis.system.appVersion,
|
||||||
(req, res) async {
|
(req, res) async {
|
||||||
var pubspec = fs.currentDirectory.parent.childDirectory('app').childFile('pubspec.yaml').readAsStringSync();
|
var appVersionQuery = AppVersionQuery();
|
||||||
var version = loadYaml(pubspec)['version'] as String;
|
appVersionQuery.orderBy(AppVersionFields.versionCode, descending: true);
|
||||||
var versions = version.split('+');
|
return appVersionQuery.getOne(req.queryExecutor).then((value) => AppVersionResp(
|
||||||
return res.json(AppVersion(versionName: versions.first, versionCode: int.parse(versions.last)));
|
versionName: value.value.versionName,
|
||||||
|
versionCode: value.value.versionCode,
|
||||||
|
));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
configureServerWithFileSystem(FileSystem fileSystem) {
|
|
||||||
fs = fileSystem;
|
|
||||||
return configureServer;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import 'package:angel3_framework/angel3_framework.dart';
|
import 'package:angel3_framework/angel3_framework.dart';
|
||||||
|
import 'package:angel3_cors/angel3_cors.dart';
|
||||||
|
import 'package:angel3_static/angel3_static.dart';
|
||||||
import 'package:file/file.dart';
|
import 'package:file/file.dart';
|
||||||
import 'controllers/auth_controllers.dart' as auth_controllers;
|
import 'controllers/auth_controllers.dart' as auth_controllers;
|
||||||
import 'controllers/system_controllers.dart' as system_controllers;
|
import 'controllers/system_controllers.dart' as system_controllers;
|
||||||
|
import 'controllers/scheme_controllers.dart' as scheme_controllers;
|
||||||
|
|
||||||
/// Put your app routes here!
|
/// Put your app routes here!
|
||||||
///
|
///
|
||||||
@@ -19,9 +22,15 @@ AngelConfigurer configureServer(FileSystem fileSystem) {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.fallback(cors());
|
||||||
|
|
||||||
// Typically, you want to mount controllers first, after any global middleware.
|
// Typically, you want to mount controllers first, after any global middleware.
|
||||||
await app.configure(system_controllers.configureServerWithFileSystem(fileSystem));
|
await app.configure(system_controllers.configureServer);
|
||||||
await app.configure(auth_controllers.configureServer);
|
await app.configure(auth_controllers.configureServer);
|
||||||
|
await app.configure(scheme_controllers.configureServer);
|
||||||
|
|
||||||
|
var vDir = VirtualDirectory(app, fileSystem, source: fileSystem.directory('web'));
|
||||||
|
app.fallback(vDir.handleRequest);
|
||||||
|
|
||||||
// Throw a 404 if no route matched the request.
|
// Throw a 404 if no route matched the request.
|
||||||
app.fallback((req, res) => throw AngelHttpException.notFound());
|
app.fallback((req, res) => throw AngelHttpException.notFound());
|
||||||
|
|||||||
+9
-13
@@ -8,32 +8,28 @@ dependencies:
|
|||||||
angel3_auth: ^4.0.0
|
angel3_auth: ^4.0.0
|
||||||
angel3_configuration: ^4.1.0
|
angel3_configuration: ^4.1.0
|
||||||
angel3_framework: ^4.2.0
|
angel3_framework: ^4.2.0
|
||||||
angel3_migration: ^4.0.0
|
angel3_migration: ^4.0.3
|
||||||
angel3_orm: ^4.0.0
|
angel3_orm: ^4.0.6
|
||||||
angel3_orm_postgres: ^3.0.0
|
angel3_orm_postgres: ^3.3.0
|
||||||
angel3_serialize: ^4.1.0
|
angel3_serialize: ^4.1.0
|
||||||
angel3_production: ^3.1.0
|
|
||||||
angel3_static: ^4.1.0
|
angel3_static: ^4.1.0
|
||||||
angel3_validate: ^4.0.0
|
angel3_production: ^3.1.2
|
||||||
belatuk_pretty_logging: ^4.0.0
|
belatuk_pretty_logging: ^4.0.0
|
||||||
optional: ^6.0.0
|
optional: ^6.0.0
|
||||||
logging: ^1.0.0
|
logging: ^1.0.0
|
||||||
yaml: ^3.1.0
|
|
||||||
mailer: ^5.0.2
|
mailer: ^5.0.2
|
||||||
uuid: ^3.0.5
|
uuid: ^3.0.5
|
||||||
|
angel3_cors: ^4.1.0
|
||||||
neat_cache:
|
neat_cache:
|
||||||
path: 3rd_party/neat_cache
|
path: 3rd_party/neat_cache
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
angel3_hot: ^4.2.0
|
angel3_hot: ^4.3.0
|
||||||
angel3_jinja: ^2.0.1
|
angel3_jinja: ^2.0.1
|
||||||
angel3_migration_runner: ^4.0.0
|
angel3_migration_runner: ^4.1.1
|
||||||
angel3_orm_generator: ^4.1.0
|
angel3_orm_generator: ^4.3.0
|
||||||
angel3_serialize_generator: ^4.2.0
|
angel3_serialize_generator: ^4.3.0
|
||||||
angel3_test: ^4.0.0
|
angel3_test: ^4.0.0
|
||||||
build_runner: ^2.0.3
|
build_runner: ^2.0.3
|
||||||
io: ^1.0.0
|
io: ^1.0.0
|
||||||
test: ^1.17.5
|
test: ^1.17.5
|
||||||
lints: ^1.0.0
|
lints: ^1.0.0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
docker-compose build
|
||||||
|
docker-compose up -d
|
||||||
|
MIGRATION_IMAGE="$(docker image ls --filter label=stage=dart_builder -q)"
|
||||||
|
docker run --name=dgm_api_migrate --network dgm_api_default "$MIGRATION_IMAGE"
|
||||||
|
docker rm "$(docker ps -a --filter name=dgm_api_migrate -q)"
|
||||||
|
docker image prune -f --filter label=stage=dart_builder
|
||||||
|
docker image prune -f
|
||||||
@@ -3,6 +3,9 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<title>{% block title %}{% endblock %}</title>
|
<title>{% block title %}{% endblock %}</title>
|
||||||
|
<link rel="shortcut icon" href="/images/favicon.png"/>
|
||||||
|
<link rel="bookmark" href="/images/favicon.png"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/site.css"/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{% block body %}{% endblock %}
|
{% block body %}{% endblock %}
|
||||||
|
|||||||
+3
-21
@@ -1,27 +1,9 @@
|
|||||||
html, body {
|
html, body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: .5em;
|
||||||
padding: 0;
|
|
||||||
width: 100%;
|
|
||||||
display: table;
|
|
||||||
font-weight: 100;
|
|
||||||
font-family: 'Lato', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
text-align: center;
|
|
||||||
display: table-cell;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
text-align: center;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 96px;
|
|
||||||
}
|
}
|
||||||
@@ -45,3 +45,6 @@ app.*.map.json
|
|||||||
/android/app/debug
|
/android/app/debug
|
||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/android/app/release
|
||||||
|
|
||||||
|
/deb_builder/
|
||||||
|
*.deb
|
||||||
|
|||||||
+10
-2
@@ -288,6 +288,8 @@ class _CherryToastState extends State<CherryToast> with TickerProviderStateMixin
|
|||||||
late AnimationController slideController;
|
late AnimationController slideController;
|
||||||
late BoxDecoration toastDecoration;
|
late BoxDecoration toastDecoration;
|
||||||
|
|
||||||
|
bool _dismissed = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -311,7 +313,10 @@ class _CherryToastState extends State<CherryToast> with TickerProviderStateMixin
|
|||||||
Timer(this.widget.toastDuration, () {
|
Timer(this.widget.toastDuration, () {
|
||||||
slideController.reverse();
|
slideController.reverse();
|
||||||
Timer(this.widget.animationDuration, () {
|
Timer(this.widget.animationDuration, () {
|
||||||
if (mounted) Navigator.pop(context);
|
if (mounted && !_dismissed) {
|
||||||
|
_dismissed = true;
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -463,7 +468,10 @@ class _CherryToastState extends State<CherryToast> with TickerProviderStateMixin
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
slideController.reverse();
|
slideController.reverse();
|
||||||
Timer(this.widget.animationDuration, () {
|
Timer(this.widget.animationDuration, () {
|
||||||
if (mounted) Navigator.pop(context);
|
if (mounted && !_dismissed) {
|
||||||
|
_dismissed = true;
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: Icon(Icons.close, color: Colors.grey[500], size: CLOSE_BUTTON_SIZE),
|
child: Icon(Icons.close, color: Colors.grey[500], size: CLOSE_BUTTON_SIZE),
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ class MarkdownBuilder implements md.NodeVisitor {
|
|||||||
this.defaultTextStyle, {
|
this.defaultTextStyle, {
|
||||||
this.tagTextStyle = defaultTagTextStyle,
|
this.tagTextStyle = defaultTagTextStyle,
|
||||||
required this.onCodeCopied,
|
required this.onCodeCopied,
|
||||||
|
this.richTap,
|
||||||
});
|
});
|
||||||
|
|
||||||
final _widgets = <Widget>[];
|
final _widgets = <Widget>[];
|
||||||
@@ -30,6 +31,7 @@ class MarkdownBuilder implements md.NodeVisitor {
|
|||||||
|
|
||||||
final BuildContext context;
|
final BuildContext context;
|
||||||
final LinkTap linkTap;
|
final LinkTap linkTap;
|
||||||
|
final VoidCallback? richTap;
|
||||||
final WidgetImage widgetImage;
|
final WidgetImage widgetImage;
|
||||||
final double maxWidth;
|
final double maxWidth;
|
||||||
final Function onCodeCopied;
|
final Function onCodeCopied;
|
||||||
@@ -125,6 +127,7 @@ class MarkdownBuilder implements md.NodeVisitor {
|
|||||||
children: last.textSpans,
|
children: last.textSpans,
|
||||||
style: last.textStyle,
|
style: last.textStyle,
|
||||||
),
|
),
|
||||||
|
onTap: richTap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ class Markdown extends StatefulWidget {
|
|||||||
required this.onCodeCopied,
|
required this.onCodeCopied,
|
||||||
this.maxWidth,
|
this.maxWidth,
|
||||||
this.textStyle,
|
this.textStyle,
|
||||||
|
this.richTap,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final String data;
|
final String data;
|
||||||
@@ -28,6 +29,8 @@ class Markdown extends StatefulWidget {
|
|||||||
|
|
||||||
final Function onCodeCopied;
|
final Function onCodeCopied;
|
||||||
|
|
||||||
|
final VoidCallback? richTap;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MarkdownState createState() => MarkdownState();
|
MarkdownState createState() => MarkdownState();
|
||||||
}
|
}
|
||||||
@@ -62,6 +65,7 @@ class MarkdownState extends State<Markdown> {
|
|||||||
widget.maxWidth ?? MediaQuery.of(context).size.width,
|
widget.maxWidth ?? MediaQuery.of(context).size.width,
|
||||||
widget.textStyle ?? defaultTextStyle(context),
|
widget.textStyle ?? defaultTextStyle(context),
|
||||||
onCodeCopied: widget.onCodeCopied,
|
onCodeCopied: widget.onCodeCopied,
|
||||||
|
richTap: widget.richTap,
|
||||||
).build(nodes);
|
).build(nodes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ library markdown_editor;
|
|||||||
|
|
||||||
export 'package:markdown_editor_ot/src/editor.dart';
|
export 'package:markdown_editor_ot/src/editor.dart';
|
||||||
export 'package:markdown_editor_ot/src/preview.dart';
|
export 'package:markdown_editor_ot/src/preview.dart';
|
||||||
|
export 'package:markdown_editor_ot/src/action.dart';
|
||||||
|
|||||||
+4
-2
@@ -11,6 +11,7 @@ class ActionImage extends StatefulWidget {
|
|||||||
this.imageSelect,
|
this.imageSelect,
|
||||||
required this.color,
|
required this.color,
|
||||||
this.getCursorPosition,
|
this.getCursorPosition,
|
||||||
|
required this.message,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final ActionType type;
|
final ActionType type;
|
||||||
@@ -20,6 +21,8 @@ class ActionImage extends StatefulWidget {
|
|||||||
|
|
||||||
final Color? color;
|
final Color? color;
|
||||||
|
|
||||||
|
final String? message;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ActionImageState createState() => ActionImageState();
|
ActionImageState createState() => ActionImageState();
|
||||||
}
|
}
|
||||||
@@ -60,7 +63,7 @@ class ActionImageState extends State<ActionImage> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Tooltip(
|
return Tooltip(
|
||||||
preferBelow: false,
|
preferBelow: false,
|
||||||
message: _defaultImageAttributes
|
message: widget.message ?? _defaultImageAttributes
|
||||||
.firstWhere((img) => img.type == widget.type)
|
.firstWhere((img) => img.type == widget.type)
|
||||||
.tip,
|
.tip,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
@@ -243,7 +246,6 @@ enum ActionType {
|
|||||||
fontBold,
|
fontBold,
|
||||||
fontItalic,
|
fontItalic,
|
||||||
fontStrikethrough,
|
fontStrikethrough,
|
||||||
fontDeleteLine,
|
|
||||||
textQuote,
|
textQuote,
|
||||||
list,
|
list,
|
||||||
h1,
|
h1,
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ class MdEditor extends StatefulWidget {
|
|||||||
this.appendBottomWidget,
|
this.appendBottomWidget,
|
||||||
this.splitWidget,
|
this.splitWidget,
|
||||||
this.textFocusNode,
|
this.textFocusNode,
|
||||||
|
required this.actionMessages,
|
||||||
required this.onComplete,
|
required this.onComplete,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@@ -51,6 +52,8 @@ class MdEditor extends StatefulWidget {
|
|||||||
|
|
||||||
final OnComplete onComplete;
|
final OnComplete onComplete;
|
||||||
|
|
||||||
|
final Map<ActionType, String> actionMessages;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() => MdEditorState();
|
State<StatefulWidget> createState() => MdEditorState();
|
||||||
}
|
}
|
||||||
@@ -186,6 +189,7 @@ class MdEditorState extends State<MdEditor> with AutomaticKeepAliveClientMixin {
|
|||||||
|
|
||||||
widgets.add(ActionImage(
|
widgets.add(ActionImage(
|
||||||
type: ActionType.done,
|
type: ActionType.done,
|
||||||
|
message: widget.actionMessages[ActionType.done],
|
||||||
color: widget.actionIconColor,
|
color: widget.actionIconColor,
|
||||||
tap: (t, s, i, [p]) {
|
tap: (t, s, i, [p]) {
|
||||||
widget.onComplete(getText());
|
widget.onComplete(getText());
|
||||||
@@ -194,6 +198,7 @@ class MdEditorState extends State<MdEditor> with AutomaticKeepAliveClientMixin {
|
|||||||
|
|
||||||
widgets.add(ActionImage(
|
widgets.add(ActionImage(
|
||||||
type: ActionType.undo,
|
type: ActionType.undo,
|
||||||
|
message: widget.actionMessages[ActionType.undo],
|
||||||
color: widget.actionIconColor,
|
color: widget.actionIconColor,
|
||||||
tap: (t, s, i, [p]) {
|
tap: (t, s, i, [p]) {
|
||||||
_editPerform.undo();
|
_editPerform.undo();
|
||||||
@@ -201,6 +206,7 @@ class MdEditorState extends State<MdEditor> with AutomaticKeepAliveClientMixin {
|
|||||||
));
|
));
|
||||||
widgets.add(ActionImage(
|
widgets.add(ActionImage(
|
||||||
type: ActionType.redo,
|
type: ActionType.redo,
|
||||||
|
message: widget.actionMessages[ActionType.redo],
|
||||||
color: widget.actionIconColor,
|
color: widget.actionIconColor,
|
||||||
tap: (t, s, i, [p]) {
|
tap: (t, s, i, [p]) {
|
||||||
_editPerform.redo();
|
_editPerform.redo();
|
||||||
@@ -237,6 +243,7 @@ class MdEditorState extends State<MdEditor> with AutomaticKeepAliveClientMixin {
|
|||||||
sortValue: getSortValue(ActionType.image),
|
sortValue: getSortValue(ActionType.image),
|
||||||
widget: ActionImage(
|
widget: ActionImage(
|
||||||
type: ActionType.image,
|
type: ActionType.image,
|
||||||
|
message: widget.actionMessages[ActionType.image],
|
||||||
color: widget.actionIconColor,
|
color: widget.actionIconColor,
|
||||||
tap: _disposeText,
|
tap: _disposeText,
|
||||||
imageSelect: widget.imageSelect,
|
imageSelect: widget.imageSelect,
|
||||||
@@ -247,6 +254,7 @@ class MdEditorState extends State<MdEditor> with AutomaticKeepAliveClientMixin {
|
|||||||
sortValue: getSortValue(ActionType.link),
|
sortValue: getSortValue(ActionType.link),
|
||||||
widget: ActionImage(
|
widget: ActionImage(
|
||||||
type: ActionType.link,
|
type: ActionType.link,
|
||||||
|
message: widget.actionMessages[ActionType.link],
|
||||||
color: widget.actionIconColor,
|
color: widget.actionIconColor,
|
||||||
tap: _disposeText,
|
tap: _disposeText,
|
||||||
),
|
),
|
||||||
@@ -255,6 +263,7 @@ class MdEditorState extends State<MdEditor> with AutomaticKeepAliveClientMixin {
|
|||||||
sortValue: getSortValue(ActionType.fontBold),
|
sortValue: getSortValue(ActionType.fontBold),
|
||||||
widget: ActionImage(
|
widget: ActionImage(
|
||||||
type: ActionType.fontBold,
|
type: ActionType.fontBold,
|
||||||
|
message: widget.actionMessages[ActionType.fontBold],
|
||||||
color: widget.actionIconColor,
|
color: widget.actionIconColor,
|
||||||
tap: _disposeText,
|
tap: _disposeText,
|
||||||
),
|
),
|
||||||
@@ -263,6 +272,7 @@ class MdEditorState extends State<MdEditor> with AutomaticKeepAliveClientMixin {
|
|||||||
sortValue: getSortValue(ActionType.fontItalic),
|
sortValue: getSortValue(ActionType.fontItalic),
|
||||||
widget: ActionImage(
|
widget: ActionImage(
|
||||||
type: ActionType.fontItalic,
|
type: ActionType.fontItalic,
|
||||||
|
message: widget.actionMessages[ActionType.fontItalic],
|
||||||
color: widget.actionIconColor,
|
color: widget.actionIconColor,
|
||||||
tap: _disposeText,
|
tap: _disposeText,
|
||||||
),
|
),
|
||||||
@@ -271,6 +281,7 @@ class MdEditorState extends State<MdEditor> with AutomaticKeepAliveClientMixin {
|
|||||||
sortValue: getSortValue(ActionType.fontStrikethrough),
|
sortValue: getSortValue(ActionType.fontStrikethrough),
|
||||||
widget: ActionImage(
|
widget: ActionImage(
|
||||||
type: ActionType.fontStrikethrough,
|
type: ActionType.fontStrikethrough,
|
||||||
|
message: widget.actionMessages[ActionType.fontStrikethrough],
|
||||||
color: widget.actionIconColor,
|
color: widget.actionIconColor,
|
||||||
tap: _disposeText,
|
tap: _disposeText,
|
||||||
),
|
),
|
||||||
@@ -279,6 +290,7 @@ class MdEditorState extends State<MdEditor> with AutomaticKeepAliveClientMixin {
|
|||||||
sortValue: getSortValue(ActionType.textQuote),
|
sortValue: getSortValue(ActionType.textQuote),
|
||||||
widget: ActionImage(
|
widget: ActionImage(
|
||||||
type: ActionType.textQuote,
|
type: ActionType.textQuote,
|
||||||
|
message: widget.actionMessages[ActionType.textQuote],
|
||||||
color: widget.actionIconColor,
|
color: widget.actionIconColor,
|
||||||
tap: _disposeText,
|
tap: _disposeText,
|
||||||
),
|
),
|
||||||
@@ -287,6 +299,7 @@ class MdEditorState extends State<MdEditor> with AutomaticKeepAliveClientMixin {
|
|||||||
sortValue: getSortValue(ActionType.list),
|
sortValue: getSortValue(ActionType.list),
|
||||||
widget: ActionImage(
|
widget: ActionImage(
|
||||||
type: ActionType.list,
|
type: ActionType.list,
|
||||||
|
message: widget.actionMessages[ActionType.list],
|
||||||
color: widget.actionIconColor,
|
color: widget.actionIconColor,
|
||||||
tap: _disposeText,
|
tap: _disposeText,
|
||||||
),
|
),
|
||||||
@@ -295,6 +308,7 @@ class MdEditorState extends State<MdEditor> with AutomaticKeepAliveClientMixin {
|
|||||||
sortValue: getSortValue(ActionType.h4),
|
sortValue: getSortValue(ActionType.h4),
|
||||||
widget: ActionImage(
|
widget: ActionImage(
|
||||||
type: ActionType.h4,
|
type: ActionType.h4,
|
||||||
|
message: widget.actionMessages[ActionType.h4],
|
||||||
color: widget.actionIconColor,
|
color: widget.actionIconColor,
|
||||||
tap: _disposeText,
|
tap: _disposeText,
|
||||||
),
|
),
|
||||||
@@ -303,6 +317,7 @@ class MdEditorState extends State<MdEditor> with AutomaticKeepAliveClientMixin {
|
|||||||
sortValue: getSortValue(ActionType.h5),
|
sortValue: getSortValue(ActionType.h5),
|
||||||
widget: ActionImage(
|
widget: ActionImage(
|
||||||
type: ActionType.h5,
|
type: ActionType.h5,
|
||||||
|
message: widget.actionMessages[ActionType.h5],
|
||||||
color: widget.actionIconColor,
|
color: widget.actionIconColor,
|
||||||
tap: _disposeText,
|
tap: _disposeText,
|
||||||
),
|
),
|
||||||
@@ -311,6 +326,7 @@ class MdEditorState extends State<MdEditor> with AutomaticKeepAliveClientMixin {
|
|||||||
sortValue: getSortValue(ActionType.h1),
|
sortValue: getSortValue(ActionType.h1),
|
||||||
widget: ActionImage(
|
widget: ActionImage(
|
||||||
type: ActionType.h1,
|
type: ActionType.h1,
|
||||||
|
message: widget.actionMessages[ActionType.h1],
|
||||||
color: widget.actionIconColor,
|
color: widget.actionIconColor,
|
||||||
tap: _disposeText,
|
tap: _disposeText,
|
||||||
),
|
),
|
||||||
@@ -319,6 +335,7 @@ class MdEditorState extends State<MdEditor> with AutomaticKeepAliveClientMixin {
|
|||||||
sortValue: getSortValue(ActionType.h2),
|
sortValue: getSortValue(ActionType.h2),
|
||||||
widget: ActionImage(
|
widget: ActionImage(
|
||||||
type: ActionType.h2,
|
type: ActionType.h2,
|
||||||
|
message: widget.actionMessages[ActionType.h2],
|
||||||
color: widget.actionIconColor,
|
color: widget.actionIconColor,
|
||||||
tap: _disposeText,
|
tap: _disposeText,
|
||||||
),
|
),
|
||||||
@@ -327,6 +344,7 @@ class MdEditorState extends State<MdEditor> with AutomaticKeepAliveClientMixin {
|
|||||||
sortValue: getSortValue(ActionType.h3),
|
sortValue: getSortValue(ActionType.h3),
|
||||||
widget: ActionImage(
|
widget: ActionImage(
|
||||||
type: ActionType.h3,
|
type: ActionType.h3,
|
||||||
|
message: widget.actionMessages[ActionType.h3],
|
||||||
color: widget.actionIconColor,
|
color: widget.actionIconColor,
|
||||||
tap: _disposeText,
|
tap: _disposeText,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ class MdPreview extends StatefulWidget {
|
|||||||
required this.widgetImage,
|
required this.widgetImage,
|
||||||
required this.onCodeCopied,
|
required this.onCodeCopied,
|
||||||
this.textStyle,
|
this.textStyle,
|
||||||
|
this.richTap,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final String text;
|
final String text;
|
||||||
@@ -24,6 +25,8 @@ class MdPreview extends StatefulWidget {
|
|||||||
/// If [onTapLink] is null,it will open the link with your default browser.
|
/// If [onTapLink] is null,it will open the link with your default browser.
|
||||||
final TapLinkCallback? onTapLink;
|
final TapLinkCallback? onTapLink;
|
||||||
|
|
||||||
|
final VoidCallback? richTap;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() => MdPreviewState();
|
State<StatefulWidget> createState() => MdPreviewState();
|
||||||
}
|
}
|
||||||
@@ -50,6 +53,7 @@ class MdPreviewState extends State<MdPreview>
|
|||||||
image: widget.widgetImage,
|
image: widget.widgetImage,
|
||||||
textStyle: widget.textStyle,
|
textStyle: widget.textStyle,
|
||||||
onCodeCopied: widget.onCodeCopied,
|
onCodeCopied: widget.onCodeCopied,
|
||||||
|
richTap: widget.richTap,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
Executable
+90
@@ -0,0 +1,90 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
cd ../api || exit
|
||||||
|
bash source_gen.sh
|
||||||
|
|
||||||
|
cd ../app || exit
|
||||||
|
bash source_gen.sh
|
||||||
|
|
||||||
|
|
||||||
|
VERSION=$(dart version.dart)
|
||||||
|
|
||||||
|
if [ -e pubspec.yaml.bak ]; then
|
||||||
|
mv pubspec.yaml.bak pubspec.yaml
|
||||||
|
fi
|
||||||
|
|
||||||
|
flutter clean
|
||||||
|
|
||||||
|
cp pubspec.yaml pubspec.yaml.bak
|
||||||
|
ln -s /usr/share/fonts/opentype/noto/ noto_fonts
|
||||||
|
|
||||||
|
cat >> pubspec.yaml << EOF
|
||||||
|
fonts:
|
||||||
|
- family: NotoSansSC
|
||||||
|
fonts:
|
||||||
|
- asset: noto_fonts/NotoSansCJK-Regular.ttc
|
||||||
|
weight: 400
|
||||||
|
- asset: noto_fonts/NotoSansCJK-Bold.ttc
|
||||||
|
weight: 700
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
flutter build linux
|
||||||
|
|
||||||
|
rm pubspec.yaml
|
||||||
|
rm noto_fonts
|
||||||
|
mv pubspec.yaml.bak pubspec.yaml
|
||||||
|
|
||||||
|
if [ -e deb_builder ]; then
|
||||||
|
rm -rf deb_builder
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
mkdir "deb_builder"
|
||||||
|
|
||||||
|
cp -r debian deb_builder/DEBIAN
|
||||||
|
chmod -R 755 deb_builder/DEBIAN
|
||||||
|
|
||||||
|
cp ../LICENSE deb_builder/DEBIAN/copyright
|
||||||
|
|
||||||
|
echo "设置版本号为: $VERSION"
|
||||||
|
|
||||||
|
echo Version: "$VERSION" >> deb_builder/DEBIAN/control
|
||||||
|
|
||||||
|
mkdir -p deb_builder/opt/apps/com.debuggerx.dde-gesture-manager/
|
||||||
|
|
||||||
|
cp -r dde_package_info/* deb_builder/opt/apps/com.debuggerx.dde-gesture-manager/
|
||||||
|
|
||||||
|
ARCH="x64"
|
||||||
|
|
||||||
|
if [[ $(uname -m) == aarch64 ]]; then
|
||||||
|
ARCH="arm64"
|
||||||
|
sed -i "s/amd64/$ARCH/g" deb_builder/opt/apps/com.debuggerx.dde-gesture-manager/info
|
||||||
|
sed -i "s/amd64/$ARCH/g" deb_builder/DEBIAN/control
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp -r build/linux/"$ARCH"/release/bundle deb_builder/opt/apps/com.debuggerx.dde-gesture-manager/files
|
||||||
|
|
||||||
|
rm -rf deb_builder/opt/apps/com.debuggerx.dde-gesture-manager/files/data/flutter_assets/noto_fonts/
|
||||||
|
|
||||||
|
ln -s /usr/share/fonts/opentype/noto/ deb_builder/opt/apps/com.debuggerx.dde-gesture-manager/files/data/flutter_assets/noto_fonts
|
||||||
|
|
||||||
|
mkdir -p deb_builder/opt/apps/com.debuggerx.dde-gesture-manager/entries/icons/hicolor/scalable/apps/
|
||||||
|
|
||||||
|
cp web/icons/Icon-512.png deb_builder/opt/apps/com.debuggerx.dde-gesture-manager/entries/icons/hicolor/scalable/apps/dgm.png
|
||||||
|
|
||||||
|
sed -i "s/VERSION/$VERSION/g" deb_builder/opt/apps/com.debuggerx.dde-gesture-manager/info
|
||||||
|
|
||||||
|
sed -i "s/VERSION/$VERSION/g" deb_builder/opt/apps/com.debuggerx.dde-gesture-manager/entries/applications/com.debuggerx.dde-gesture-manager.desktop
|
||||||
|
|
||||||
|
echo "开始打包 $ARCH deb"
|
||||||
|
|
||||||
|
fakeroot dpkg-deb -b deb_builder
|
||||||
|
|
||||||
|
if [[ $ARCH == "x64" ]]; then
|
||||||
|
ARCH="amd64"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mv deb_builder.deb com.debuggerx.dde-gesture-manager_"$VERSION"_"$ARCH".deb
|
||||||
|
|
||||||
|
echo "打包完成!"
|
||||||
+4
-1
@@ -8,7 +8,10 @@ echo "Downloading WASM from $wasmLocation"
|
|||||||
curl -o build/web/canvaskit.js "$wasmLocation/canvaskit.js"
|
curl -o build/web/canvaskit.js "$wasmLocation/canvaskit.js"
|
||||||
curl -o build/web/canvaskit.wasm "$wasmLocation/canvaskit.wasm"
|
curl -o build/web/canvaskit.wasm "$wasmLocation/canvaskit.wasm"
|
||||||
sed -i -e "s!$wasmLocation!.!" \
|
sed -i -e "s!$wasmLocation!.!" \
|
||||||
-e "s!https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Me5WZLCzYlKw.ttf!./assets/packages/amos_mobile_widgets/assets/google_fonts/Roboto-Regular.ttf!" \
|
-e "s!https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Me5WZLCzYlKw.ttf!./google_fonts/Roboto-Regular.ttf!" \
|
||||||
-e "s!https://fonts.googleapis.com/css2?family=Noto+Sans+Symbols!./assets/assets/css/Noto-Sans-Symbols.css!" \
|
-e "s!https://fonts.googleapis.com/css2?family=Noto+Sans+Symbols!./assets/assets/css/Noto-Sans-Symbols.css!" \
|
||||||
-e "s!https://fonts.googleapis.com/css2?family=Noto+Color+Emoji+Compat!./assets/assets/css/Noto-Color-Emoji-Compat.css!" \
|
-e "s!https://fonts.googleapis.com/css2?family=Noto+Color+Emoji+Compat!./assets/assets/css/Noto-Color-Emoji-Compat.css!" \
|
||||||
build/web/main.dart.js
|
build/web/main.dart.js
|
||||||
|
|
||||||
|
|
||||||
|
# git init && git add . && git commit -m 'update' && git remote add origin git@github.com:debuggerx01/dgm_web.git && git push --set-upstream origin master -f
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Categories=Utility;
|
||||||
|
Comment=专为 DDE 桌面环境打造的触摸板手势管理工具(缩写:dgm),使用 Flutter 构建。
|
||||||
|
Exec=/opt/apps/com.debuggerx.dde-gesture-manager/files/dde-gesture-manager
|
||||||
|
Icon=dgm
|
||||||
|
Name=DDE Gesture Manager
|
||||||
|
Name[zh_CN]=DDE手势管理器
|
||||||
|
Type=Application
|
||||||
|
Version=VERSION
|
||||||
|
X-Deepin-Vendor=user-custom
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"appid": "com.debuggerx.dde-gesture-manager",
|
||||||
|
"name": "dde-gesture-manager",
|
||||||
|
"version": "VERSION",
|
||||||
|
"arch": ["amd64"],
|
||||||
|
"permissions": {
|
||||||
|
"autostart": true,
|
||||||
|
"notification": true,
|
||||||
|
"trayicon": false,
|
||||||
|
"clipboard": true,
|
||||||
|
"account": false,
|
||||||
|
"bluetooth": false,
|
||||||
|
"camera": false,
|
||||||
|
"audio_record": false,
|
||||||
|
"installed_apps": false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
Source: dde-gesture-manager
|
||||||
|
Section: utils
|
||||||
|
Priority: optional
|
||||||
|
Maintainer: DebuggerX <dx8913712@163.com>
|
||||||
|
Build-Depends:
|
||||||
|
clang,
|
||||||
|
cmake,
|
||||||
|
libgtk-3-dev,
|
||||||
|
ninja-build,
|
||||||
|
Homepage: https://github.com/debuggerx01/dde_gesture_manager
|
||||||
|
Package: com.debuggerx.dde-gesture-manager
|
||||||
|
Architecture: amd64
|
||||||
|
Depends: fonts-noto-cjk
|
||||||
|
Description: 专为 DDE 桌面环境打造的触摸板手势管理工具(缩写:dgm),使用 Flutter 构建。
|
||||||
@@ -3,7 +3,7 @@ import 'package:flutter/cupertino.dart';
|
|||||||
/// [UOS设计指南](https://docs.uniontech.com/zh/content/t_dbG3kBK9iDf9B963ok)
|
/// [UOS设计指南](https://docs.uniontech.com/zh/content/t_dbG3kBK9iDf9B963ok)
|
||||||
const double localManagerPanelWidth = 260;
|
const double localManagerPanelWidth = 260;
|
||||||
|
|
||||||
const double marketOrMePanelWidth = 300;
|
const double marketOrMePanelWidth = 400;
|
||||||
|
|
||||||
const shortDuration = const Duration(milliseconds: 100);
|
const shortDuration = const Duration(milliseconds: 100);
|
||||||
|
|
||||||
@@ -19,6 +19,18 @@ const double defaultBorderRadius = 8;
|
|||||||
|
|
||||||
const double defaultButtonHeight = 36;
|
const double defaultButtonHeight = 36;
|
||||||
|
|
||||||
|
const userGestureConfigFilePath = 'deepin/dde-daemon/gesture.json';
|
||||||
|
|
||||||
|
const defaultFontFamily = 'NotoSansSC';
|
||||||
|
|
||||||
|
const deepinLogoutCommands = [
|
||||||
|
'dbus-send',
|
||||||
|
'--type=method_call',
|
||||||
|
'--dest=com.deepin.SessionManager',
|
||||||
|
'/com/deepin/SessionManager',
|
||||||
|
'com.deepin.SessionManager.RequestLogout'
|
||||||
|
];
|
||||||
|
|
||||||
const List<String> builtInCommands = [
|
const List<String> builtInCommands = [
|
||||||
'ShowWorkspace',
|
'ShowWorkspace',
|
||||||
'Handle4Or5FingersSwipeUp',
|
'Handle4Or5FingersSwipeUp',
|
||||||
@@ -40,3 +52,9 @@ enum PanelType {
|
|||||||
local_manager,
|
local_manager,
|
||||||
market_or_me,
|
market_or_me,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum UploadRespStatus {
|
||||||
|
done,
|
||||||
|
name_occupied,
|
||||||
|
error,
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ class SPKeys {
|
|||||||
static final String accessToken = 'USER_ACCESS_TOKEN';
|
static final String accessToken = 'USER_ACCESS_TOKEN';
|
||||||
static final String loginEmail = 'USER_LOGIN_EMAIL';
|
static final String loginEmail = 'USER_LOGIN_EMAIL';
|
||||||
static final String ignoredUpdateVersion = 'IGNORED_UPDATE_VERSION';
|
static final String ignoredUpdateVersion = 'IGNORED_UPDATE_VERSION';
|
||||||
|
static final String readBulletinId = 'READ_BULLETIN_ID';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
|
import 'package:dde_gesture_manager/models/configs.provider.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:dde_gesture_manager/extensions.dart';
|
||||||
|
|
||||||
extension ContextExtension on BuildContext {
|
extension ContextExtension on BuildContext {
|
||||||
ThemeData get t => Theme.of(this);
|
ThemeData get t => Theme.of(this);
|
||||||
|
|
||||||
NavigatorState get n => Navigator.of(this);
|
NavigatorState get n => Navigator.of(this);
|
||||||
|
|
||||||
|
bool get hasToken => this.read<ConfigsProvider>().accessToken.notNull;
|
||||||
|
|
||||||
|
bool get watchHasToken => this.watch<ConfigsProvider>().accessToken.notNull;
|
||||||
}
|
}
|
||||||
|
|||||||
+120
-16
@@ -1,10 +1,14 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:dde_gesture_manager/constants/constants.dart';
|
||||||
import 'package:dde_gesture_manager/constants/sp_keys.dart';
|
import 'package:dde_gesture_manager/constants/sp_keys.dart';
|
||||||
import 'package:dde_gesture_manager/extensions.dart';
|
import 'package:dde_gesture_manager/extensions.dart';
|
||||||
|
import 'package:dde_gesture_manager/models/scheme.dart' as AppScheme;
|
||||||
import 'package:dde_gesture_manager/utils/helper.dart';
|
import 'package:dde_gesture_manager/utils/helper.dart';
|
||||||
import 'package:dde_gesture_manager/utils/notificator.dart';
|
import 'package:dde_gesture_manager/utils/notificator.dart';
|
||||||
|
import 'package:dde_gesture_manager/widgets/market.dart';
|
||||||
|
import 'package:dde_gesture_manager/widgets/me.dart';
|
||||||
import 'package:dde_gesture_manager_api/apis.dart';
|
import 'package:dde_gesture_manager_api/apis.dart';
|
||||||
import 'package:dde_gesture_manager_api/models.dart';
|
import 'package:dde_gesture_manager_api/models.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
@@ -13,7 +17,12 @@ typedef T BeanBuilder<T>(Map res);
|
|||||||
|
|
||||||
typedef T HandleRespBuild<T>(http.Response resp);
|
typedef T HandleRespBuild<T>(http.Response resp);
|
||||||
|
|
||||||
getStatusCodeFunc<int>(Map resp) => resp["statusCode"];
|
typedef int GetStatusCodeFunc(Map resp);
|
||||||
|
|
||||||
|
int getStatusCodeFunc(Map resp) => resp["statusCode"] as int;
|
||||||
|
|
||||||
|
BeanBuilder<List<T>> listRespBuilderWrap<T>(BeanBuilder<T> builder) =>
|
||||||
|
(Map resp) => (resp['list'] as List).map<T>((e) => builder(e)).toList();
|
||||||
|
|
||||||
class HttpErrorCode extends Error {
|
class HttpErrorCode extends Error {
|
||||||
int statusCode;
|
int statusCode;
|
||||||
@@ -39,18 +48,24 @@ class Api {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static HandleRespBuild<T> _handleRespBuild<T>(BeanBuilder<T> builder) => (http.Response resp) {
|
static HandleRespBuild<T?> _handleRespBuild<T>(BeanBuilder<T> builder) => (http.Response resp) {
|
||||||
if (builder == getStatusCodeFunc) return builder({"statusCode": resp.statusCode});
|
if (builder is GetStatusCodeFunc) return builder({"statusCode": resp.statusCode});
|
||||||
T res;
|
T? res;
|
||||||
try {
|
try {
|
||||||
res = builder(json.decode(resp.body));
|
if (resp.statusCode != HttpStatus.ok && resp.bodyBytes.length == 0)
|
||||||
|
throw HttpErrorCode(resp.statusCode, message: 'No resp body from ${resp.request!.url.path}');
|
||||||
|
var decodeBody = json.decode(utf8.decode(resp.bodyBytes));
|
||||||
|
res = decodeBody is Map ? builder(decodeBody) : builder({'list': decodeBody});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
e.sout();
|
||||||
throw HttpErrorCode(resp.statusCode, message: resp.body);
|
throw HttpErrorCode(resp.statusCode, message: resp.body);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
static Future<T> _get<T>(
|
static final _fullPathRegExp = RegExp('http(s?)://');
|
||||||
|
|
||||||
|
static Future<T?> _get<T>(
|
||||||
String path,
|
String path,
|
||||||
BeanBuilder<T> builder, {
|
BeanBuilder<T> builder, {
|
||||||
Map<String, dynamic>? queryParams,
|
Map<String, dynamic>? queryParams,
|
||||||
@@ -59,7 +74,9 @@ class Api {
|
|||||||
}) =>
|
}) =>
|
||||||
http
|
http
|
||||||
.get(
|
.get(
|
||||||
Uri(
|
path.startsWith(_fullPathRegExp)
|
||||||
|
? Uri.parse(path)
|
||||||
|
: Uri(
|
||||||
scheme: Apis.apiScheme,
|
scheme: Apis.apiScheme,
|
||||||
host: Apis.apiHost,
|
host: Apis.apiHost,
|
||||||
port: Apis.apiPort,
|
port: Apis.apiPort,
|
||||||
@@ -67,21 +84,21 @@ class Api {
|
|||||||
path: path,
|
path: path,
|
||||||
),
|
),
|
||||||
headers: <String, String>{
|
headers: <String, String>{
|
||||||
HttpHeaders.contentTypeHeader: ContentType.json.value,
|
HttpHeaders.contentTypeHeader: ContentType.json.toString(),
|
||||||
}..addAll(
|
}..addAll(
|
||||||
ignoreToken ? {} : {HttpHeaders.authorizationHeader: 'Bearer ${H().sp.getString(SPKeys.accessToken)}'}),
|
ignoreToken ? {} : {HttpHeaders.authorizationHeader: 'Bearer ${H().sp.getString(SPKeys.accessToken)}'}),
|
||||||
)
|
)
|
||||||
.then(
|
.then<T?>(
|
||||||
_handleRespBuild<T>(builder),
|
_handleRespBuild<T>(builder),
|
||||||
onError: (e) {
|
onError: (e) {
|
||||||
if (ignoreErrorHandle)
|
if (ignoreErrorHandle)
|
||||||
throw e;
|
throw e;
|
||||||
else
|
else
|
||||||
return _handleHttpError(e);
|
_handleHttpError(e);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
static Future<T> _post<T>(
|
static Future<T?> _post<T>(
|
||||||
String path,
|
String path,
|
||||||
BeanBuilder<T> builder, {
|
BeanBuilder<T> builder, {
|
||||||
Map<String, dynamic>? body,
|
Map<String, dynamic>? body,
|
||||||
@@ -90,7 +107,9 @@ class Api {
|
|||||||
}) =>
|
}) =>
|
||||||
http
|
http
|
||||||
.post(
|
.post(
|
||||||
Uri(
|
path.startsWith(_fullPathRegExp)
|
||||||
|
? Uri.parse(path)
|
||||||
|
: Uri(
|
||||||
scheme: Apis.apiScheme,
|
scheme: Apis.apiScheme,
|
||||||
host: Apis.apiHost,
|
host: Apis.apiHost,
|
||||||
port: Apis.apiPort,
|
port: Apis.apiPort,
|
||||||
@@ -98,17 +117,17 @@ class Api {
|
|||||||
),
|
),
|
||||||
body: jsonEncode(body),
|
body: jsonEncode(body),
|
||||||
headers: <String, String>{
|
headers: <String, String>{
|
||||||
HttpHeaders.contentTypeHeader: ContentType.json.value,
|
HttpHeaders.contentTypeHeader: ContentType.json.toString(),
|
||||||
}..addAll(
|
}..addAll(
|
||||||
ignoreToken ? {} : {HttpHeaders.authorizationHeader: 'Bearer ${H().sp.getString(SPKeys.accessToken)}'}),
|
ignoreToken ? {} : {HttpHeaders.authorizationHeader: 'Bearer ${H().sp.getString(SPKeys.accessToken)}'}),
|
||||||
)
|
)
|
||||||
.then(
|
.then<T?>(
|
||||||
_handleRespBuild<T>(builder),
|
_handleRespBuild<T>(builder),
|
||||||
onError: (e) {
|
onError: (e) {
|
||||||
if (ignoreErrorHandle)
|
if (ignoreErrorHandle)
|
||||||
throw e;
|
throw e;
|
||||||
else
|
else
|
||||||
return _handleHttpError(e);
|
_handleHttpError(e);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -121,7 +140,7 @@ class Api {
|
|||||||
LoginSuccessSerializer.fromMap,
|
LoginSuccessSerializer.fromMap,
|
||||||
body: {
|
body: {
|
||||||
UserFields.email: email,
|
UserFields.email: email,
|
||||||
UserFields.password: password,
|
UserFields.password: User(email: email, password: password).secret('dgm_password'),
|
||||||
},
|
},
|
||||||
ignoreToken: true,
|
ignoreToken: true,
|
||||||
);
|
);
|
||||||
@@ -132,4 +151,89 @@ class Api {
|
|||||||
ignoreToken: true,
|
ignoreToken: true,
|
||||||
ignoreErrorHandle: ignoreErrorHandle,
|
ignoreErrorHandle: ignoreErrorHandle,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
static Future<bool> checkAuthStatus() => _get<int>(Apis.auth.status, getStatusCodeFunc, ignoreErrorHandle: true)
|
||||||
|
.then((value) => value == HttpStatus.noContent);
|
||||||
|
|
||||||
|
static Future<UploadRespStatus> uploadScheme({required AppScheme.Scheme scheme, required bool share}) => _post(
|
||||||
|
Apis.scheme.upload,
|
||||||
|
getStatusCodeFunc,
|
||||||
|
body: SchemeSerializer.toMap(
|
||||||
|
Scheme(
|
||||||
|
name: scheme.name,
|
||||||
|
uuid: scheme.id,
|
||||||
|
description: scheme.description,
|
||||||
|
gestures: scheme.gestures,
|
||||||
|
shared: share,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).then((value) {
|
||||||
|
switch (value) {
|
||||||
|
case HttpStatus.noContent:
|
||||||
|
return UploadRespStatus.done;
|
||||||
|
case HttpStatus.locked:
|
||||||
|
return UploadRespStatus.name_occupied;
|
||||||
|
case HttpStatus.unprocessableEntity:
|
||||||
|
default:
|
||||||
|
return UploadRespStatus.error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
static Future<List<SimpleSchemeTransMetaData>?> userSchemes({required SchemeListType type}) =>
|
||||||
|
_get(Apis.scheme.user(type: type.name.param), listRespBuilderWrap(SimpleSchemeTransMetaDataSerializer.fromMap));
|
||||||
|
|
||||||
|
static Future<bool> likeScheme({required String schemeId, required bool isLike}) => _get(
|
||||||
|
Apis.scheme.like(schemeId: schemeId.param, isLike: StringParam(isLike ? 'like' : 'unlike')),
|
||||||
|
getStatusCodeFunc)
|
||||||
|
.then((value) {
|
||||||
|
return value == HttpStatus.noContent;
|
||||||
|
});
|
||||||
|
|
||||||
|
static Future<SchemeForDownload?> downloadScheme({required String schemeId}) => _get(
|
||||||
|
Apis.scheme.download(schemeId: schemeId.param),
|
||||||
|
SchemeForDownloadSerializer.fromMap,
|
||||||
|
);
|
||||||
|
|
||||||
|
static Future<MarketSchemeTransMetaDataResp?> marketSchemes(
|
||||||
|
{required MarketSortType type, required int page, int pageSize = 30}) =>
|
||||||
|
_get(
|
||||||
|
Apis.scheme.market(type: type.name.param, page: page.param, pageSize: pageSize.param),
|
||||||
|
MarketSchemeTransMetaDataResp.fromMap,
|
||||||
|
);
|
||||||
|
|
||||||
|
static Future<List<int>?> userLikes() => _get(
|
||||||
|
Apis.scheme.userLikes,
|
||||||
|
(e) => (e['list'] as List).cast<int>(),
|
||||||
|
);
|
||||||
|
|
||||||
|
static Future<AppBulletinResp?> checkBulletin(bool isWeb) => _get(
|
||||||
|
Apis.appBulletinUrl(isWeb),
|
||||||
|
AppBulletinResp.fromMap,
|
||||||
|
ignoreErrorHandle: true,
|
||||||
|
ignoreToken: true,
|
||||||
|
).catchError((_) {});
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppBulletinResp {
|
||||||
|
int? id;
|
||||||
|
bool? once;
|
||||||
|
String? title;
|
||||||
|
String? content;
|
||||||
|
|
||||||
|
AppBulletinResp.fromMap(Map map)
|
||||||
|
: id = map['id'],
|
||||||
|
once = map['once'],
|
||||||
|
title = map['title'],
|
||||||
|
content = map['content'];
|
||||||
|
}
|
||||||
|
|
||||||
|
class MarketSchemeTransMetaDataResp {
|
||||||
|
bool hasMore;
|
||||||
|
List<MarketSchemeTransMetaData> items;
|
||||||
|
|
||||||
|
MarketSchemeTransMetaDataResp.fromMap(Map map)
|
||||||
|
: hasMore = map['hasMore'],
|
||||||
|
items = (map['items'] as List)
|
||||||
|
.map<MarketSchemeTransMetaData>((e) => MarketSchemeTransMetaDataSerializer.fromMap(e))
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
|
|||||||
+49
-3
@@ -3,6 +3,7 @@ import 'package:dde_gesture_manager/constants/sp_keys.dart';
|
|||||||
import 'package:dde_gesture_manager/constants/supported_locales.dart';
|
import 'package:dde_gesture_manager/constants/supported_locales.dart';
|
||||||
import 'package:dde_gesture_manager/extensions.dart';
|
import 'package:dde_gesture_manager/extensions.dart';
|
||||||
import 'package:dde_gesture_manager/generated/codegen_loader.g.dart';
|
import 'package:dde_gesture_manager/generated/codegen_loader.g.dart';
|
||||||
|
import 'package:dde_gesture_manager/http/api.dart';
|
||||||
import 'package:dde_gesture_manager/models/configs.dart';
|
import 'package:dde_gesture_manager/models/configs.dart';
|
||||||
import 'package:dde_gesture_manager/models/configs.provider.dart';
|
import 'package:dde_gesture_manager/models/configs.provider.dart';
|
||||||
import 'package:dde_gesture_manager/models/settings.provider.dart';
|
import 'package:dde_gesture_manager/models/settings.provider.dart';
|
||||||
@@ -10,7 +11,11 @@ import 'package:dde_gesture_manager/themes/dark.dart';
|
|||||||
import 'package:dde_gesture_manager/themes/light.dart';
|
import 'package:dde_gesture_manager/themes/light.dart';
|
||||||
import 'package:dde_gesture_manager/utils/helper.dart';
|
import 'package:dde_gesture_manager/utils/helper.dart';
|
||||||
import 'package:dde_gesture_manager/utils/init.dart';
|
import 'package:dde_gesture_manager/utils/init.dart';
|
||||||
|
import 'package:dde_gesture_manager/utils/notificator.dart';
|
||||||
|
import 'package:dde_gesture_manager/utils/simple_throttle.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||||
|
|
||||||
import 'pages/home.dart';
|
import 'pages/home.dart';
|
||||||
|
|
||||||
@@ -19,13 +24,23 @@ Future<void> main() async {
|
|||||||
EasyLocalization.logger.enableLevels = [];
|
EasyLocalization.logger.enableLevels = [];
|
||||||
await EasyLocalization.ensureInitialized();
|
await EasyLocalization.ensureInitialized();
|
||||||
await initConfigs();
|
await initConfigs();
|
||||||
runApp(EasyLocalization(
|
await SentryFlutter.init(
|
||||||
|
(options) {
|
||||||
|
options.dsn = 'https://febbfdeac6874a01b5fee56b2ba9515c@o644838.ingest.sentry.io/6216990';
|
||||||
|
// Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
|
||||||
|
// We recommend adjusting this value in production.
|
||||||
|
options.tracesSampleRate = kReleaseMode ? 0.1 : 1.0;
|
||||||
|
options.reportPackages = false;
|
||||||
|
options.maxDeduplicationItems = 3;
|
||||||
|
},
|
||||||
|
appRunner: () => runApp(EasyLocalization(
|
||||||
supportedLocales: supportedLocales,
|
supportedLocales: supportedLocales,
|
||||||
fallbackLocale: zh_CN,
|
fallbackLocale: zh_CN,
|
||||||
path: 'resources/langs',
|
path: 'resources/langs',
|
||||||
assetLoader: CodegenLoader(),
|
assetLoader: CodegenLoader(),
|
||||||
child: MyApp(),
|
child: MyApp(),
|
||||||
));
|
)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
@@ -68,7 +83,15 @@ class MyApp extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
firstChild: Builder(builder: (context) {
|
firstChild: Builder(builder: (context) {
|
||||||
Future.microtask(() => initEvents(context));
|
Future.microtask(() {
|
||||||
|
initEvents(context);
|
||||||
|
SimpleThrottle.throttledFunc(_checkAuthStatus, timeout: const Duration(minutes: 5))?.call(context);
|
||||||
|
SimpleThrottle.throttledFunc(
|
||||||
|
Sentry.captureMessage,
|
||||||
|
timeout: const Duration(days: 1),
|
||||||
|
)?.call('App launched');
|
||||||
|
SimpleThrottle.throttledFunc(_checkBulletin, timeout: const Duration(days: 1))?.call(context);
|
||||||
|
});
|
||||||
return Container();
|
return Container();
|
||||||
}),
|
}),
|
||||||
secondChild: HomePage(),
|
secondChild: HomePage(),
|
||||||
@@ -79,3 +102,26 @@ class MyApp extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _checkAuthStatus(BuildContext context) {
|
||||||
|
if (H().lastCheckAuthStatusTime != null &&
|
||||||
|
H().lastCheckAuthStatusTime!.difference(DateTime.now()) < Duration(minutes: 10)) return;
|
||||||
|
if (context.hasToken) {
|
||||||
|
Api.checkAuthStatus().then((value) {
|
||||||
|
if (!value) context.read<ConfigsProvider>().setProps(email: '', accessToken: '');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
H().lastCheckAuthStatusTime = DateTime.now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _checkBulletin(BuildContext context) {
|
||||||
|
Api.checkBulletin(kIsWeb).then((value) {
|
||||||
|
if (value != null && value.id != null) {
|
||||||
|
if (value.once == false || (H().sp.getInt(SPKeys.readBulletinId) ?? 0) < value.id!) {
|
||||||
|
Notificator.showAlert(title: value.title ?? '', description: value.content ?? '');
|
||||||
|
}
|
||||||
|
H().sp.setInt(SPKeys.readBulletinId, value.id!);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:dde_gesture_manager/builder/provider_annotation.dart';
|
|||||||
import 'package:dde_gesture_manager/constants/sp_keys.dart';
|
import 'package:dde_gesture_manager/constants/sp_keys.dart';
|
||||||
import 'package:dde_gesture_manager/extensions.dart';
|
import 'package:dde_gesture_manager/extensions.dart';
|
||||||
import 'package:dde_gesture_manager/utils/helper.dart';
|
import 'package:dde_gesture_manager/utils/helper.dart';
|
||||||
|
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||||
|
|
||||||
enum BrightnessMode {
|
enum BrightnessMode {
|
||||||
system,
|
system,
|
||||||
@@ -45,9 +46,12 @@ class Configs {
|
|||||||
|
|
||||||
set email(String? emailAddress) {
|
set email(String? emailAddress) {
|
||||||
_email = emailAddress;
|
_email = emailAddress;
|
||||||
if (emailAddress.notNull)
|
if (emailAddress.notNull) {
|
||||||
H().sp.updateString(SPKeys.loginEmail, emailAddress!);
|
H().sp.updateString(SPKeys.loginEmail, emailAddress!);
|
||||||
else
|
Sentry.configureScope(
|
||||||
|
(scope) => scope.user = SentryUser(email: emailAddress),
|
||||||
|
);
|
||||||
|
} else
|
||||||
H().sp.remove(SPKeys.loginEmail);
|
H().sp.remove(SPKeys.loginEmail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import 'package:dde_gesture_manager/builder/provider_annotation.dart';
|
import 'package:dde_gesture_manager/builder/provider_annotation.dart';
|
||||||
|
import 'package:dde_gesture_manager/constants/sp_keys.dart';
|
||||||
|
import 'package:dde_gesture_manager/utils/helper.dart';
|
||||||
|
import 'package:dde_gesture_manager/extensions.dart';
|
||||||
|
|
||||||
@ProviderModel()
|
@ProviderModel()
|
||||||
class ContentLayout {
|
class ContentLayout {
|
||||||
@@ -9,7 +12,7 @@ class ContentLayout {
|
|||||||
bool? marketOrMeOpened;
|
bool? marketOrMeOpened;
|
||||||
|
|
||||||
@ProviderModelProp()
|
@ProviderModelProp()
|
||||||
bool? currentIsMarket = true;
|
bool? currentIsMarket = H().sp.getString(SPKeys.accessToken).isNull;
|
||||||
|
|
||||||
bool get isMarket => currentIsMarket ?? true;
|
bool get isMarket => currentIsMarket ?? true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,27 +19,26 @@ class LocalSchemes implements LocalSchemesInterface<LocalSchemeEntryWeb> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<LocalSchemeEntryWeb>> get schemeEntries async {
|
Future<List<LocalSchemeEntryWeb>> get schemeEntries async {
|
||||||
return window.localStorage.keys
|
List<LocalSchemeEntryWeb> _localeSchemes = [];
|
||||||
.map<LocalSchemeEntryWeb?>((key) {
|
for (var key in window.localStorage.keys) {
|
||||||
if (key.startsWith('schemes.')) {
|
if (key.startsWith('schemes.')) {
|
||||||
LocalSchemeEntryWeb? entry;
|
|
||||||
try {
|
|
||||||
var content = window.localStorage[key] ?? '';
|
var content = window.localStorage[key] ?? '';
|
||||||
var schemeJson = json.decode(content);
|
var schemeJson;
|
||||||
entry = LocalSchemeEntryWeb(
|
try {
|
||||||
path: key,
|
schemeJson = json.decode(content);
|
||||||
scheme: Scheme.parse(schemeJson),
|
|
||||||
lastModifyTime: DateTime.parse(schemeJson['modified_at']),
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
e.sout();
|
e.sout();
|
||||||
}
|
}
|
||||||
return entry;
|
if (schemeJson != null) {
|
||||||
|
_localeSchemes.add(LocalSchemeEntryWeb(
|
||||||
|
path: key,
|
||||||
|
scheme: Scheme.parse(schemeJson),
|
||||||
|
lastModifyTime: DateTime.parse(schemeJson['modified_at']),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
.where((e) => e != null)
|
}
|
||||||
.cast<LocalSchemeEntryWeb>()
|
return Future.value(_localeSchemes);
|
||||||
.toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ProviderModelProp()
|
@ProviderModelProp()
|
||||||
@@ -48,7 +47,7 @@ class LocalSchemes implements LocalSchemesInterface<LocalSchemeEntryWeb> {
|
|||||||
@override
|
@override
|
||||||
Future<LocalSchemeEntry> create() => Future.value(
|
Future<LocalSchemeEntry> create() => Future.value(
|
||||||
LocalSchemeEntryWeb(
|
LocalSchemeEntryWeb(
|
||||||
path: Uuid().v1(),
|
path: 'schemes.${Uuid().v1()}',
|
||||||
scheme: Scheme.create(),
|
scheme: Scheme.create(),
|
||||||
lastModifyTime: DateTime.now(),
|
lastModifyTime: DateTime.now(),
|
||||||
),
|
),
|
||||||
@@ -83,7 +82,9 @@ class LocalSchemeEntryWeb implements LocalSchemeEntry {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
save(LocalSchemesProvider provider) {
|
save(LocalSchemesProvider provider) {
|
||||||
window.localStorage[path] = JsonEncoder.withIndent(' ' * 4).convert(scheme);
|
var schemeMap = scheme.toJson();
|
||||||
|
schemeMap['modified_at'] = DateTime.now().toIso8601String();
|
||||||
|
window.localStorage[path] = JsonEncoder.withIndent(' ' * 4).convert(schemeMap);
|
||||||
provider.schemes!.firstWhere((ele) => ele.scheme.id == scheme.id).lastModifyTime = DateTime.now();
|
provider.schemes!.firstWhere((ele) => ele.scheme.id == scheme.id).lastModifyTime = DateTime.now();
|
||||||
provider.setProps(schemes: [...provider.schemes!]..sort());
|
provider.setProps(schemes: [...provider.schemes!]..sort());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ class Scheme {
|
|||||||
@ProviderModelProp()
|
@ProviderModelProp()
|
||||||
List<GestureProp>? gestures;
|
List<GestureProp>? gestures;
|
||||||
|
|
||||||
bool get readOnly => uploaded == true || fromMarket == true || id == Uuid.NAMESPACE_NIL;
|
bool get readOnly => fromMarket == true || id == Uuid.NAMESPACE_NIL;
|
||||||
|
|
||||||
Scheme.parse(scheme) {
|
Scheme.parse(scheme) {
|
||||||
if (scheme is String) scheme = json.decode(scheme);
|
if (scheme is String) scheme = json.decode(scheme);
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import 'package:dde_gesture_manager/builder/provider_annotation.dart';
|
||||||
|
|
||||||
|
@ProviderModel()
|
||||||
|
class SchemeListRefreshKey {
|
||||||
|
@ProviderModelProp(nullable: false)
|
||||||
|
int refreshKey;
|
||||||
|
|
||||||
|
SchemeListRefreshKey() : refreshKey = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:dde_gesture_manager/extensions.dart';
|
import 'package:dde_gesture_manager/extensions.dart';
|
||||||
import 'package:dde_gesture_manager/models/content_layout.provider.dart';
|
import 'package:dde_gesture_manager/models/content_layout.provider.dart';
|
||||||
import 'package:dde_gesture_manager/models/scheme.provider.dart';
|
import 'package:dde_gesture_manager/models/scheme.provider.dart';
|
||||||
|
import 'package:dde_gesture_manager/models/scheme_list_refresh_key.provider.dart';
|
||||||
import 'package:dde_gesture_manager/pages/gesture_editor.dart';
|
import 'package:dde_gesture_manager/pages/gesture_editor.dart';
|
||||||
import 'package:dde_gesture_manager/pages/local_manager.dart';
|
import 'package:dde_gesture_manager/pages/local_manager.dart';
|
||||||
import 'package:dde_gesture_manager/pages/market_or_me.dart';
|
import 'package:dde_gesture_manager/pages/market_or_me.dart';
|
||||||
@@ -36,6 +37,9 @@ class _ContentState extends State<Content> {
|
|||||||
ChangeNotifierProvider(
|
ChangeNotifierProvider(
|
||||||
create: (context) => CopiedGesturePropProvider.empty(),
|
create: (context) => CopiedGesturePropProvider.empty(),
|
||||||
),
|
),
|
||||||
|
ChangeNotifierProvider(
|
||||||
|
create: (context) => SchemeListRefreshKeyProvider(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
if (widthChanged && mounted) {
|
if (widthChanged && mounted) {
|
||||||
@@ -50,7 +54,7 @@ class _ContentState extends State<Content> {
|
|||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
LocalManager(),
|
LocalManager(key: H.localManagerKey),
|
||||||
GestureEditor(),
|
GestureEditor(),
|
||||||
MarketOrMe(),
|
MarketOrMe(),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import 'package:adaptive_scrollbar/adaptive_scrollbar.dart';
|
import 'package:adaptive_scrollbar/adaptive_scrollbar.dart';
|
||||||
import 'package:dde_gesture_manager/constants/constants.dart';
|
import 'package:dde_gesture_manager/constants/constants.dart';
|
||||||
import 'package:dde_gesture_manager/extensions.dart';
|
import 'package:dde_gesture_manager/extensions.dart';
|
||||||
|
import 'package:dde_gesture_manager/http/api.dart';
|
||||||
import 'package:dde_gesture_manager/models/content_layout.provider.dart';
|
import 'package:dde_gesture_manager/models/content_layout.provider.dart';
|
||||||
import 'package:dde_gesture_manager/models/local_schemes_provider.dart';
|
import 'package:dde_gesture_manager/models/local_schemes_provider.dart';
|
||||||
import 'package:dde_gesture_manager/models/scheme.dart';
|
import 'package:dde_gesture_manager/models/scheme.dart';
|
||||||
import 'package:dde_gesture_manager/models/scheme.provider.dart';
|
import 'package:dde_gesture_manager/models/scheme.provider.dart';
|
||||||
|
import 'package:dde_gesture_manager/models/scheme_list_refresh_key.provider.dart';
|
||||||
import 'package:dde_gesture_manager/models/settings.provider.dart';
|
import 'package:dde_gesture_manager/models/settings.provider.dart';
|
||||||
import 'package:dde_gesture_manager/pages/content.dart';
|
import 'package:dde_gesture_manager/pages/content.dart';
|
||||||
import 'package:dde_gesture_manager/utils/helper.dart';
|
import 'package:dde_gesture_manager/utils/helper.dart';
|
||||||
@@ -18,6 +20,7 @@ import 'package:dde_gesture_manager/widgets/table_cell_shortcut_listener.dart';
|
|||||||
import 'package:dde_gesture_manager/widgets/table_cell_text_field.dart';
|
import 'package:dde_gesture_manager/widgets/table_cell_text_field.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_platform_alert/flutter_platform_alert.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
const double _headingRowHeight = 56;
|
const double _headingRowHeight = 56;
|
||||||
@@ -292,6 +295,65 @@ class GestureEditor extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 10),
|
||||||
|
child: DButton.upload(
|
||||||
|
enabled: schemeProvider.readOnly == false,
|
||||||
|
onTap: () async {
|
||||||
|
if (schemeProvider.description.isNull) {
|
||||||
|
Notificator.error(context, title: LocaleKeys.info_upload_pls_add_description.tr());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!context.hasToken) {
|
||||||
|
return Notificator.showAlert(
|
||||||
|
title: LocaleKeys.info_login_for_upload_title.tr(),
|
||||||
|
description: LocaleKeys.info_login_for_upload_description.tr(),
|
||||||
|
).then((value) {
|
||||||
|
if (value == CustomButton.positiveButton) {
|
||||||
|
context
|
||||||
|
.read<ContentLayoutProvider>()
|
||||||
|
.setProps(marketOrMeOpened: true, currentIsMarket: false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Notificator.showConfirm(
|
||||||
|
title: LocaleKeys.info_upload_and_share_title.tr(),
|
||||||
|
description: LocaleKeys.info_upload_and_share_description.tr(),
|
||||||
|
positiveButtonTitle: LocaleKeys.str_share.tr(),
|
||||||
|
negativeButtonTitle: LocaleKeys.str_cancel.tr(),
|
||||||
|
).then((value) {
|
||||||
|
bool? _share;
|
||||||
|
if (value == CustomButton.positiveButton)
|
||||||
|
_share = true;
|
||||||
|
else if (value == CustomButton.negativeButton) _share = false;
|
||||||
|
|
||||||
|
if (_share != null) {
|
||||||
|
Api.uploadScheme(scheme: schemeProvider, share: _share).then((value) {
|
||||||
|
if (value == UploadRespStatus.done) {
|
||||||
|
Notificator.success(context, title: LocaleKeys.info_upload_success.tr());
|
||||||
|
var localSchemesProvider = context.read<LocalSchemesProvider>();
|
||||||
|
var localSchemeEntry = localSchemesProvider.schemes!
|
||||||
|
.firstWhere((ele) => ele.scheme.id == schemeProvider.id);
|
||||||
|
localSchemeEntry.scheme.uploaded = true;
|
||||||
|
localSchemeEntry.save(localSchemesProvider);
|
||||||
|
context
|
||||||
|
.read<SchemeListRefreshKeyProvider>()
|
||||||
|
.setProps(refreshKey: DateTime.now().millisecondsSinceEpoch);
|
||||||
|
} else if (value == UploadRespStatus.name_occupied) {
|
||||||
|
Notificator.error(
|
||||||
|
context,
|
||||||
|
title: LocaleKeys.info_upload_name_occupied.tr(),
|
||||||
|
description: LocaleKeys.info_upload_pls_rename.tr(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Notificator.error(context, title: LocaleKeys.info_upload_failed.tr());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Divider(),
|
Divider(),
|
||||||
@@ -542,13 +604,11 @@ Widget _buildCommandCellsEditing(BuildContext context) {
|
|||||||
('${LocaleKeys.built_in_commands}.$e').tr(),
|
('${LocaleKeys.built_in_commands}.$e').tr(),
|
||||||
textScaleFactor: .8,
|
textScaleFactor: .8,
|
||||||
),
|
),
|
||||||
value: ('${LocaleKeys.built_in_commands}.$e').tr(),
|
value: e,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
value:
|
value: (builtInCommands.contains(gesture.command) ? gesture.command : builtInCommands.first)!,
|
||||||
('${LocaleKeys.built_in_commands}.${(builtInCommands.contains(gesture.command) ? gesture.command : builtInCommands.first)!}')
|
|
||||||
.tr(),
|
|
||||||
onChanged: (value) => context.read<GesturePropProvider>().setProps(
|
onChanged: (value) => context.read<GesturePropProvider>().setProps(
|
||||||
command: value,
|
command: value,
|
||||||
editMode: true,
|
editMode: true,
|
||||||
|
|||||||
@@ -8,9 +8,12 @@ import 'package:dde_gesture_manager/models/local_schemes_provider.dart';
|
|||||||
import 'package:dde_gesture_manager/models/scheme.dart';
|
import 'package:dde_gesture_manager/models/scheme.dart';
|
||||||
import 'package:dde_gesture_manager/models/scheme.provider.dart';
|
import 'package:dde_gesture_manager/models/scheme.provider.dart';
|
||||||
import 'package:dde_gesture_manager/models/settings.provider.dart';
|
import 'package:dde_gesture_manager/models/settings.provider.dart';
|
||||||
|
import 'package:dde_gesture_manager/utils/apply_scheme_interface.dart';
|
||||||
import 'package:dde_gesture_manager/widgets/dde_button.dart';
|
import 'package:dde_gesture_manager/widgets/dde_button.dart';
|
||||||
|
import 'package:dde_gesture_manager_api/models.dart' show SchemeForDownload;
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
class LocalManager extends StatefulWidget {
|
class LocalManager extends StatefulWidget {
|
||||||
@@ -19,10 +22,10 @@ class LocalManager extends StatefulWidget {
|
|||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<LocalManager> createState() => _LocalManagerState();
|
State<LocalManager> createState() => LocalManagerState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LocalManagerState extends State<LocalManager> {
|
class LocalManagerState extends State<LocalManager> {
|
||||||
late ScrollController _scrollController;
|
late ScrollController _scrollController;
|
||||||
String? _hoveringItemPath;
|
String? _hoveringItemPath;
|
||||||
late String _selectedItemPath;
|
late String _selectedItemPath;
|
||||||
@@ -69,6 +72,28 @@ class _LocalManagerState extends State<LocalManager> {
|
|||||||
context.read<GesturePropProvider>().copyFrom(GestureProp.empty());
|
context.read<GesturePropProvider>().copyFrom(GestureProp.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future addLocalScheme(BuildContext context, [SchemeForDownload? downloadedScheme = null]) async {
|
||||||
|
var localSchemesProvider = context.read<LocalSchemesProvider>();
|
||||||
|
var newSchemes = [...?localSchemesProvider.schemes];
|
||||||
|
var newEntry = await localSchemesProvider.create();
|
||||||
|
if (downloadedScheme != null) {
|
||||||
|
newEntry.scheme
|
||||||
|
..id = downloadedScheme.uuid
|
||||||
|
..name = downloadedScheme.name
|
||||||
|
..description = downloadedScheme.description
|
||||||
|
..uploaded = true
|
||||||
|
..fromMarket = downloadedScheme.shared == true
|
||||||
|
..gestures = (downloadedScheme.gestures ?? []).map(GestureProp.parse).toList();
|
||||||
|
}
|
||||||
|
newSchemes.add(newEntry);
|
||||||
|
localSchemesProvider.setProps(schemes: newSchemes..sort());
|
||||||
|
newEntry.save(localSchemesProvider);
|
||||||
|
setState(() {
|
||||||
|
_selectedItemPath = newEntry.path;
|
||||||
|
});
|
||||||
|
_handleItemClick(context, newEntry);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var isOpen = context.watch<ContentLayoutProvider>().localManagerOpened == true;
|
var isOpen = context.watch<ContentLayoutProvider>().localManagerOpened == true;
|
||||||
@@ -184,23 +209,15 @@ class _LocalManagerState extends State<LocalManager> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(height: 5),
|
Padding(
|
||||||
Container(
|
padding: const EdgeInsets.only(top: 5),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
DButton.add(
|
DButton.add(
|
||||||
enabled: true,
|
enabled: true,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
var localSchemesProvider = context.read<LocalSchemesProvider>();
|
await addLocalScheme(context);
|
||||||
var newSchemes = [...?localSchemesProvider.schemes];
|
|
||||||
var newEntry = await localSchemesProvider.create();
|
|
||||||
newSchemes.add(newEntry);
|
|
||||||
localSchemesProvider.setProps(schemes: newSchemes..sort());
|
|
||||||
setState(() {
|
|
||||||
_selectedItemPath = newEntry.path;
|
|
||||||
});
|
|
||||||
_handleItemClick(context, newEntry);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
DButton.delete(
|
DButton.delete(
|
||||||
@@ -242,10 +259,11 @@ class _LocalManagerState extends State<LocalManager> {
|
|||||||
DButton.apply(
|
DButton.apply(
|
||||||
enabled: true,
|
enabled: true,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
var appliedId =
|
var appliedScheme =
|
||||||
localSchemes.firstWhere((ele) => ele.path == _selectedItemPath).scheme.id!;
|
localSchemes.firstWhere((ele) => ele.path == _selectedItemPath).scheme;
|
||||||
appliedId.sout();
|
context.read<ConfigsProvider>().setProps(appliedSchemeId: appliedScheme.id);
|
||||||
context.read<ConfigsProvider>().setProps(appliedSchemeId: appliedId);
|
SchemeApplyUtil().apply(context, appliedScheme);
|
||||||
|
Sentry.captureMessage('Scheme applied: [${appliedScheme.name}](${appliedScheme.id})');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import 'package:auto_size_text/auto_size_text.dart';
|
|
||||||
import 'package:dde_gesture_manager/constants/constants.dart';
|
import 'package:dde_gesture_manager/constants/constants.dart';
|
||||||
import 'package:dde_gesture_manager/extensions.dart';
|
import 'package:dde_gesture_manager/extensions.dart';
|
||||||
import 'package:dde_gesture_manager/models/configs.provider.dart';
|
|
||||||
import 'package:dde_gesture_manager/models/content_layout.provider.dart';
|
import 'package:dde_gesture_manager/models/content_layout.provider.dart';
|
||||||
import 'package:dde_gesture_manager/widgets/dde_button.dart';
|
import 'package:dde_gesture_manager/widgets/dde_button.dart';
|
||||||
import 'package:dde_gesture_manager/widgets/login.dart';
|
import 'package:dde_gesture_manager/widgets/login.dart';
|
||||||
|
import 'package:dde_gesture_manager/widgets/market.dart';
|
||||||
|
import 'package:dde_gesture_manager/widgets/me.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
@@ -18,15 +18,14 @@ class MarketOrMe extends StatelessWidget {
|
|||||||
var layoutProvider = context.watch<ContentLayoutProvider>();
|
var layoutProvider = context.watch<ContentLayoutProvider>();
|
||||||
bool isOpen = layoutProvider.marketOrMeOpened == true;
|
bool isOpen = layoutProvider.marketOrMeOpened == true;
|
||||||
bool isMarket = layoutProvider.isMarket;
|
bool isMarket = layoutProvider.isMarket;
|
||||||
bool showLogin = context.watch<ConfigsProvider>().accessToken.isNull && !isMarket;
|
|
||||||
return AnimatedContainer(
|
return AnimatedContainer(
|
||||||
duration: mediumDuration,
|
duration: mediumDuration,
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
width: isOpen ? marketOrMePanelWidth * (showLogin ? 1.5 : 1) : 0,
|
width: isOpen ? marketOrMePanelWidth * 1 : 0,
|
||||||
child: OverflowBox(
|
child: OverflowBox(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
maxWidth: marketOrMePanelWidth * (showLogin ? 1.5 : 1),
|
maxWidth: marketOrMePanelWidth,
|
||||||
minWidth: marketOrMePanelWidth * (showLogin ? 1.5 : 1),
|
minWidth: marketOrMePanelWidth,
|
||||||
child: Material(
|
child: Material(
|
||||||
color: context.t.backgroundColor,
|
color: context.t.backgroundColor,
|
||||||
elevation: isOpen ? 10 : 0,
|
elevation: isOpen ? 10 : 0,
|
||||||
@@ -79,39 +78,11 @@ class MarketOrMe extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildMeContent(BuildContext context) {
|
Widget buildMeContent(BuildContext context) => context.watchHasToken
|
||||||
var accessToken = context.watch<ConfigsProvider>().accessToken;
|
? Expanded(
|
||||||
if (accessToken.isNull) return LoginWidget();
|
child: MeWidget(),
|
||||||
|
)
|
||||||
|
: LoginWidget();
|
||||||
|
|
||||||
return Padding(
|
Widget buildMarketContent(BuildContext context) => Expanded(child: MarketWidget());
|
||||||
padding: EdgeInsets.symmetric(vertical: 10),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Icon(Icons.person, size: defaultButtonHeight),
|
|
||||||
Flexible(
|
|
||||||
child: AutoSizeText(
|
|
||||||
context.watch<ConfigsProvider>().email ?? '',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
),
|
|
||||||
maxLines: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
DButton.logout(
|
|
||||||
enabled: true,
|
|
||||||
onTap: () => context.read<ConfigsProvider>().setProps(accessToken: '', email: ''),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildMarketContent(BuildContext context) {
|
|
||||||
return Container();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:dde_gesture_manager/constants/constants.dart';
|
import 'package:dde_gesture_manager/constants/constants.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
var darkTheme = ThemeData.dark().copyWith(
|
final _darkTheme = ThemeData(
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
fontFamily: kIsWeb ? null : defaultFontFamily,
|
||||||
|
);
|
||||||
|
|
||||||
|
var darkTheme = _darkTheme.copyWith(
|
||||||
primaryColor: Colors.grey,
|
primaryColor: Colors.grey,
|
||||||
scaffoldBackgroundColor: Color(0xff252525),
|
scaffoldBackgroundColor: Color(0xff252525),
|
||||||
backgroundColor: Color(0xff282828),
|
backgroundColor: Color(0xff282828),
|
||||||
@@ -9,23 +15,26 @@ var darkTheme = ThemeData.dark().copyWith(
|
|||||||
color: Color(0xffc0c6d4),
|
color: Color(0xffc0c6d4),
|
||||||
),
|
),
|
||||||
dividerColor: Color(0xfff3f3f3),
|
dividerColor: Color(0xfff3f3f3),
|
||||||
textTheme: ThemeData.dark().textTheme.copyWith(
|
textTheme: _darkTheme.textTheme.copyWith(
|
||||||
headline1: TextStyle(
|
headline1: TextStyle(
|
||||||
color: Color(0xffc0c6d4),
|
color: Color(0xffc0c6d4),
|
||||||
|
fontFamily: kIsWeb ? null : defaultFontFamily,
|
||||||
),
|
),
|
||||||
bodyText2: TextStyle(
|
bodyText2: TextStyle(
|
||||||
color: Color(0xffc0c6d4),
|
color: Color(0xffc0c6d4),
|
||||||
|
fontFamily: kIsWeb ? null : defaultFontFamily,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
popupMenuTheme: ThemeData.dark().popupMenuTheme.copyWith(
|
popupMenuTheme: _darkTheme.popupMenuTheme.copyWith(
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(defaultBorderRadius),
|
borderRadius: BorderRadius.circular(defaultBorderRadius),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
dialogBackgroundColor: Color(0xff202020),
|
dialogBackgroundColor: Color(0xff202020),
|
||||||
tooltipTheme: ThemeData.dark().tooltipTheme.copyWith(
|
tooltipTheme: _darkTheme.tooltipTheme.copyWith(
|
||||||
textStyle: TextStyle(
|
textStyle: TextStyle(
|
||||||
color: Colors.grey,
|
color: Colors.grey,
|
||||||
|
fontFamily: kIsWeb ? null : defaultFontFamily,
|
||||||
),
|
),
|
||||||
padding: EdgeInsets.symmetric(horizontal: 3, vertical: 2),
|
padding: EdgeInsets.symmetric(horizontal: 3, vertical: 2),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
|||||||
@@ -1,31 +1,40 @@
|
|||||||
import 'package:dde_gesture_manager/constants/constants.dart';
|
import 'package:dde_gesture_manager/constants/constants.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
var lightTheme = ThemeData.light().copyWith(
|
final _lightTheme = ThemeData(
|
||||||
|
brightness: Brightness.light,
|
||||||
|
fontFamily: kIsWeb ? null : defaultFontFamily,
|
||||||
|
);
|
||||||
|
|
||||||
|
var lightTheme = _lightTheme.copyWith(
|
||||||
primaryColor: Colors.blue,
|
primaryColor: Colors.blue,
|
||||||
scaffoldBackgroundColor: Color(0xfff8f8f8),
|
scaffoldBackgroundColor: Color(0xfff8f8f8),
|
||||||
backgroundColor: Color(0xffffffff),
|
backgroundColor: Color(0xffffffff),
|
||||||
iconTheme: IconThemeData(
|
iconTheme: IconThemeData(
|
||||||
color: Color(0xff414d68),
|
color: Color(0xff414d68),
|
||||||
),
|
),
|
||||||
dividerColor: Color(0xfff3f3f3),
|
dividerColor: Colors.grey.shade600,
|
||||||
textTheme: ThemeData.light().textTheme.copyWith(
|
textTheme: _lightTheme.textTheme.copyWith(
|
||||||
headline1: TextStyle(
|
headline1: TextStyle(
|
||||||
color: Color(0xff414d68),
|
color: Color(0xff414d68),
|
||||||
|
fontFamily: kIsWeb ? null : defaultFontFamily,
|
||||||
),
|
),
|
||||||
bodyText2: TextStyle(
|
bodyText2: TextStyle(
|
||||||
color: Color(0xff414d68),
|
color: Color(0xff414d68),
|
||||||
|
fontFamily: kIsWeb ? null : defaultFontFamily,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
popupMenuTheme: ThemeData.dark().popupMenuTheme.copyWith(
|
popupMenuTheme: _lightTheme.popupMenuTheme.copyWith(
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(defaultBorderRadius),
|
borderRadius: BorderRadius.circular(defaultBorderRadius),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
dialogBackgroundColor: Color(0xfffefefe),
|
dialogBackgroundColor: Color(0xfffefefe),
|
||||||
tooltipTheme: ThemeData.dark().tooltipTheme.copyWith(
|
tooltipTheme: _lightTheme.tooltipTheme.copyWith(
|
||||||
textStyle: TextStyle(
|
textStyle: TextStyle(
|
||||||
color: Colors.grey.shade600,
|
color: Colors.grey.shade600,
|
||||||
|
fontFamily: kIsWeb ? null : defaultFontFamily,
|
||||||
),
|
),
|
||||||
padding: EdgeInsets.symmetric(horizontal: 3, vertical: 2),
|
padding: EdgeInsets.symmetric(horizontal: 3, vertical: 2),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import 'package:dde_gesture_manager/models/scheme.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
export 'apply_scheme_web.dart' if (dart.library.io) 'apply_scheme_linux.dart';
|
||||||
|
|
||||||
|
abstract class SchemeApplyUtilStub {
|
||||||
|
void apply(BuildContext context, Scheme scheme);
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:dde_gesture_manager/constants/constants.dart';
|
||||||
|
import 'package:dde_gesture_manager/extensions.dart';
|
||||||
|
import 'package:dde_gesture_manager/models/scheme.dart';
|
||||||
|
import 'package:dde_gesture_manager/utils/helper.dart';
|
||||||
|
import 'package:dde_gesture_manager/utils/notificator.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_platform_alert/flutter_platform_alert.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
import 'apply_scheme_interface.dart';
|
||||||
|
import 'package:xdg_directories/xdg_directories.dart' as xdg;
|
||||||
|
import 'package:path/path.dart' show join;
|
||||||
|
|
||||||
|
class SchemeApplyUtil implements SchemeApplyUtilStub {
|
||||||
|
void apply(BuildContext context, Scheme scheme) {
|
||||||
|
var configFilePath = join(xdg.configHome.path, userGestureConfigFilePath);
|
||||||
|
configFilePath.sout();
|
||||||
|
var file = File(configFilePath);
|
||||||
|
if (scheme.id == Uuid.NAMESPACE_NIL) {
|
||||||
|
if (file.existsSync()) file.deleteSync();
|
||||||
|
} else {
|
||||||
|
if (!file.existsSync()) file.createSync(recursive: true);
|
||||||
|
file.writeAsStringSync(
|
||||||
|
H.transGesturePropsToConfig(scheme.gestures ?? []),
|
||||||
|
flush: true,
|
||||||
|
);
|
||||||
|
Notificator.showConfirm(
|
||||||
|
title: LocaleKeys.info_apply_scheme_success.tr(),
|
||||||
|
description: LocaleKeys.info_apply_scheme_description.tr(),
|
||||||
|
positiveButtonTitle: LocaleKeys.info_apply_scheme_logout_immediately.tr(),
|
||||||
|
negativeButtonTitle: LocaleKeys.str_cancel.tr(),
|
||||||
|
).then((value) {
|
||||||
|
if (value == CustomButton.positiveButton) {
|
||||||
|
Process.run(deepinLogoutCommands.first, deepinLogoutCommands.skip(1).toList());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import 'package:dde_gesture_manager/constants/constants.dart';
|
||||||
|
import 'package:dde_gesture_manager/extensions.dart';
|
||||||
|
import 'package:dde_gesture_manager/models/scheme.dart';
|
||||||
|
import 'package:dde_gesture_manager/utils/helper.dart';
|
||||||
|
import 'package:dde_gesture_manager/utils/notificator.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
import 'apply_scheme_interface.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
class SchemeApplyUtil implements SchemeApplyUtilStub {
|
||||||
|
void apply(BuildContext context, Scheme scheme) {
|
||||||
|
var cmd = scheme.id == Uuid.NAMESPACE_NIL
|
||||||
|
? [
|
||||||
|
'rm \$XDG_CONFIG_HOME/${userGestureConfigFilePath}',
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
'cat > \$XDG_CONFIG_HOME/${userGestureConfigFilePath} << EOF',
|
||||||
|
'${H.transGesturePropsToConfig(scheme.gestures ?? [])}',
|
||||||
|
'EOF',
|
||||||
|
];
|
||||||
|
cmd.add('dialog'
|
||||||
|
' --title "${LocaleKeys.info_apply_scheme_success.tr()}"'
|
||||||
|
' --yes-label "${LocaleKeys.info_apply_scheme_logout_immediately.tr()}"'
|
||||||
|
' --no-label "${LocaleKeys.str_cancel.tr()}"'
|
||||||
|
' --yesno "${LocaleKeys.info_apply_scheme_description.tr()}"'
|
||||||
|
' 8 30'
|
||||||
|
' && ${deepinLogoutCommands.join(' ')}');
|
||||||
|
Clipboard.setData(ClipboardData(
|
||||||
|
text: cmd.join('\n'),
|
||||||
|
));
|
||||||
|
Notificator.success(
|
||||||
|
context,
|
||||||
|
title: LocaleKeys.info_apply_scheme_commands_copied_title.tr(),
|
||||||
|
description: LocaleKeys.info_apply_scheme_commands_copied_description.tr(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:dde_gesture_manager/constants/constants.dart';
|
import 'package:dde_gesture_manager/constants/constants.dart';
|
||||||
import 'package:dde_gesture_manager/extensions.dart';
|
import 'package:dde_gesture_manager/extensions.dart';
|
||||||
import 'package:dde_gesture_manager/models/content_layout.provider.dart';
|
import 'package:dde_gesture_manager/models/content_layout.provider.dart';
|
||||||
import 'package:dde_gesture_manager/models/scheme.dart';
|
import 'package:dde_gesture_manager/models/scheme.dart';
|
||||||
|
import 'package:dde_gesture_manager/pages/local_manager.dart';
|
||||||
|
import 'package:dde_gesture_manager_api/src/models/scheme.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
extension EnumByName<T extends Enum> on Iterable<T> {
|
extension EnumByName<T extends Enum> on Iterable<T> {
|
||||||
@@ -34,6 +39,10 @@ class H {
|
|||||||
|
|
||||||
BuildContext get topContext => _topContext;
|
BuildContext get topContext => _topContext;
|
||||||
|
|
||||||
|
DateTime? lastCheckAuthStatusTime;
|
||||||
|
|
||||||
|
static final localManagerKey = GlobalKey<LocalManagerState>();
|
||||||
|
|
||||||
initTopContext(BuildContext context) {
|
initTopContext(BuildContext context) {
|
||||||
_topContext = context;
|
_topContext = context;
|
||||||
}
|
}
|
||||||
@@ -112,6 +121,34 @@ class H {
|
|||||||
gestureProp.direction = tree.availableNode.availableNode.availableNode.direction;
|
gestureProp.direction = tree.availableNode.availableNode.availableNode.direction;
|
||||||
return gestureProp;
|
return gestureProp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void launchURL(String url) async {
|
||||||
|
if (await canLaunch(url)) {
|
||||||
|
await launch(url);
|
||||||
|
} else {
|
||||||
|
throw 'Could not launch $url';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handleDownloadScheme(BuildContext context, SchemeForDownload value) =>
|
||||||
|
localManagerKey.currentState?.addLocalScheme(context, value);
|
||||||
|
|
||||||
|
static String transGesturePropsToConfig(List<GestureProp> gestures) =>
|
||||||
|
JsonEncoder.withIndent(' ' * 4).convert(gestures
|
||||||
|
.map(
|
||||||
|
(gesture) => {
|
||||||
|
"Event": {
|
||||||
|
"Name": gesture.gesture!.name,
|
||||||
|
"Direction": H.getGestureDirectionName(gesture.direction),
|
||||||
|
"Fingers": gesture.fingers,
|
||||||
|
},
|
||||||
|
"Action": {
|
||||||
|
"Type": gesture.type!.name,
|
||||||
|
"Action": gesture.command,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
class PreferredPanelsStatus {
|
class PreferredPanelsStatus {
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ Future<void> initEvents(BuildContext context) async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_updateChecked)
|
if (!_updateChecked) {
|
||||||
Api.checkAppVersion(ignoreErrorHandle: true).then((value) async {
|
|
||||||
_updateChecked = true;
|
_updateChecked = true;
|
||||||
|
Api.checkAppVersion(ignoreErrorHandle: true).then((value) async {
|
||||||
var info = await PackageInfo.fromPlatform();
|
var info = await PackageInfo.fromPlatform();
|
||||||
var _buildNumber = int.parse(info.buildNumber);
|
var _buildNumber = int.parse(info.buildNumber);
|
||||||
var _newVersionCode = value?.versionCode ?? 0;
|
var _newVersionCode = value?.versionCode ?? 0;
|
||||||
@@ -75,6 +75,7 @@ Future<void> initEvents(BuildContext context) async {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> initConfigs() async {
|
Future<void> initConfigs() async {
|
||||||
await H().initSharedPreference();
|
await H().initSharedPreference();
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class Notificator {
|
|||||||
return AlertImpl().showAlert(
|
return AlertImpl().showAlert(
|
||||||
windowTitle: title,
|
windowTitle: title,
|
||||||
text: description,
|
text: description,
|
||||||
positiveButtonTitle: positiveButtonTitle,
|
positiveButtonTitle: positiveButtonTitle ?? LocaleKeys.str_ok.tr(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import 'dart:collection';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
|
class _SimpleThrottleNode {
|
||||||
|
int funcHashCode;
|
||||||
|
int timestamp;
|
||||||
|
|
||||||
|
_SimpleThrottleNode(this.funcHashCode, this.timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef void _VoidFunc();
|
||||||
|
|
||||||
|
final _simpleThrottleQueue = Queue();
|
||||||
|
|
||||||
|
/// Usage: If you have a function : test(int n) => n;
|
||||||
|
/// you can use SimpleThrottle.throttledFunc(test)?.call(1) to make it throttled
|
||||||
|
/// this will return function's return value if last call time over the timeout
|
||||||
|
/// otherwise this will return null.
|
||||||
|
/// If your function is a 'void function()', you can use SimpleThrottle.invoke(func) to call it throttled,
|
||||||
|
/// and you can get a throttled function by SimpleThrottle.bind(func) if you do not call it immediately.
|
||||||
|
class SimpleThrottle {
|
||||||
|
static T? throttledFunc<T extends Function>(T func,
|
||||||
|
{String? funcKey, Duration timeout = const Duration(seconds: 1)}) {
|
||||||
|
var node = _simpleThrottleQueue.firstWhereOrNull((element) => element.funcHashCode == (funcKey ?? func).hashCode);
|
||||||
|
if (node != null) {
|
||||||
|
if (DateTime.now().millisecondsSinceEpoch - node.timestamp < timeout.inMilliseconds)
|
||||||
|
return null;
|
||||||
|
else
|
||||||
|
node.timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
} else {
|
||||||
|
_simpleThrottleQueue.add(_SimpleThrottleNode((funcKey ?? func).hashCode, DateTime.now().millisecondsSinceEpoch));
|
||||||
|
while (_simpleThrottleQueue.length > 16) {
|
||||||
|
_simpleThrottleQueue.removeFirst();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return func;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void invoke(_VoidFunc func, {String? funcKey, Duration timeout = const Duration(seconds: 1)}) =>
|
||||||
|
throttledFunc(func, timeout: timeout, funcKey: funcKey)?.call();
|
||||||
|
|
||||||
|
static _VoidFunc bind(_VoidFunc func, {String? funcKey, Duration timeout = const Duration(seconds: 1)}) =>
|
||||||
|
() => invoke(func, timeout: timeout, funcKey: funcKey);
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@ class DButton extends StatefulWidget {
|
|||||||
|
|
||||||
factory DButton.add({
|
factory DButton.add({
|
||||||
Key? key,
|
Key? key,
|
||||||
required enabled,
|
required bool enabled,
|
||||||
GestureTapCallback? onTap,
|
GestureTapCallback? onTap,
|
||||||
height = defaultButtonHeight * .7,
|
height = defaultButtonHeight * .7,
|
||||||
width = defaultButtonHeight * .7,
|
width = defaultButtonHeight * .7,
|
||||||
@@ -41,7 +41,7 @@ class DButton extends StatefulWidget {
|
|||||||
|
|
||||||
factory DButton.delete({
|
factory DButton.delete({
|
||||||
Key? key,
|
Key? key,
|
||||||
required enabled,
|
required bool enabled,
|
||||||
GestureTapCallback? onTap,
|
GestureTapCallback? onTap,
|
||||||
height = defaultButtonHeight * .7,
|
height = defaultButtonHeight * .7,
|
||||||
width = defaultButtonHeight * .7,
|
width = defaultButtonHeight * .7,
|
||||||
@@ -58,7 +58,7 @@ class DButton extends StatefulWidget {
|
|||||||
|
|
||||||
factory DButton.apply({
|
factory DButton.apply({
|
||||||
Key? key,
|
Key? key,
|
||||||
required enabled,
|
required bool enabled,
|
||||||
GestureTapCallback? onTap,
|
GestureTapCallback? onTap,
|
||||||
height = defaultButtonHeight * .7,
|
height = defaultButtonHeight * .7,
|
||||||
width = defaultButtonHeight * .7,
|
width = defaultButtonHeight * .7,
|
||||||
@@ -75,7 +75,7 @@ class DButton extends StatefulWidget {
|
|||||||
|
|
||||||
factory DButton.duplicate({
|
factory DButton.duplicate({
|
||||||
Key? key,
|
Key? key,
|
||||||
required enabled,
|
required bool enabled,
|
||||||
GestureTapCallback? onTap,
|
GestureTapCallback? onTap,
|
||||||
height = defaultButtonHeight * .7,
|
height = defaultButtonHeight * .7,
|
||||||
width = defaultButtonHeight * .7,
|
width = defaultButtonHeight * .7,
|
||||||
@@ -92,7 +92,7 @@ class DButton extends StatefulWidget {
|
|||||||
|
|
||||||
factory DButton.paste({
|
factory DButton.paste({
|
||||||
Key? key,
|
Key? key,
|
||||||
required enabled,
|
required bool enabled,
|
||||||
GestureTapCallback? onTap,
|
GestureTapCallback? onTap,
|
||||||
height = defaultButtonHeight * .7,
|
height = defaultButtonHeight * .7,
|
||||||
width = defaultButtonHeight * .7,
|
width = defaultButtonHeight * .7,
|
||||||
@@ -109,7 +109,7 @@ class DButton extends StatefulWidget {
|
|||||||
|
|
||||||
factory DButton.logout({
|
factory DButton.logout({
|
||||||
Key? key,
|
Key? key,
|
||||||
required enabled,
|
required bool enabled,
|
||||||
GestureTapCallback? onTap,
|
GestureTapCallback? onTap,
|
||||||
height = defaultButtonHeight,
|
height = defaultButtonHeight,
|
||||||
width = defaultButtonHeight,
|
width = defaultButtonHeight,
|
||||||
@@ -124,6 +124,74 @@ class DButton extends StatefulWidget {
|
|||||||
message: LocaleKeys.operation_logout.tr(),
|
message: LocaleKeys.operation_logout.tr(),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
factory DButton.upload({
|
||||||
|
Key? key,
|
||||||
|
required bool enabled,
|
||||||
|
GestureTapCallback? onTap,
|
||||||
|
height = defaultButtonHeight,
|
||||||
|
width = defaultButtonHeight,
|
||||||
|
}) =>
|
||||||
|
DButton(
|
||||||
|
key: key,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
onTap: enabled ? onTap : null,
|
||||||
|
child: Tooltip(
|
||||||
|
child: Opacity(opacity: enabled ? 1 : 0.4, child: const Icon(Icons.cloud_upload, size: 20)),
|
||||||
|
message: LocaleKeys.operation_upload.tr(),
|
||||||
|
));
|
||||||
|
|
||||||
|
factory DButton.download({
|
||||||
|
Key? key,
|
||||||
|
required bool enabled,
|
||||||
|
GestureTapCallback? onTap,
|
||||||
|
height = defaultButtonHeight * .7,
|
||||||
|
width = defaultButtonHeight * .7,
|
||||||
|
}) =>
|
||||||
|
DButton(
|
||||||
|
key: key,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
onTap: enabled ? onTap : null,
|
||||||
|
child: Tooltip(
|
||||||
|
child: Opacity(opacity: enabled ? 1 : 0.4, child: const Icon(Icons.file_download, size: 18)),
|
||||||
|
message: LocaleKeys.operation_download.tr(),
|
||||||
|
));
|
||||||
|
|
||||||
|
factory DButton.share({
|
||||||
|
Key? key,
|
||||||
|
required bool enabled,
|
||||||
|
GestureTapCallback? onTap,
|
||||||
|
height = defaultButtonHeight * .7,
|
||||||
|
width = defaultButtonHeight * .7,
|
||||||
|
}) =>
|
||||||
|
DButton(
|
||||||
|
key: key,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
onTap: enabled ? onTap : null,
|
||||||
|
child: Tooltip(
|
||||||
|
child: Opacity(opacity: enabled ? 1 : 0.4, child: const Icon(Icons.share, size: 18)),
|
||||||
|
message: LocaleKeys.operation_share.tr(),
|
||||||
|
));
|
||||||
|
|
||||||
|
factory DButton.like({
|
||||||
|
Key? key,
|
||||||
|
required bool enabled,
|
||||||
|
GestureTapCallback? onTap,
|
||||||
|
height = defaultButtonHeight * .7,
|
||||||
|
width = defaultButtonHeight * .7,
|
||||||
|
}) =>
|
||||||
|
DButton(
|
||||||
|
key: key,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
onTap: enabled ? onTap : null,
|
||||||
|
child: Tooltip(
|
||||||
|
child: Opacity(opacity: enabled ? 1 : 0.4, child: const Icon(Icons.thumb_up, size: 16)),
|
||||||
|
message: LocaleKeys.operation_like.tr(),
|
||||||
|
));
|
||||||
|
|
||||||
factory DButton.dropdown({
|
factory DButton.dropdown({
|
||||||
Key? key,
|
Key? key,
|
||||||
width = 60.0,
|
width = 60.0,
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import 'package:cached_network_image/cached_network_image.dart';
|
|||||||
import 'package:dde_gesture_manager/constants/constants.dart';
|
import 'package:dde_gesture_manager/constants/constants.dart';
|
||||||
import 'package:dde_gesture_manager/extensions.dart';
|
import 'package:dde_gesture_manager/extensions.dart';
|
||||||
import 'package:dde_gesture_manager/models/settings.provider.dart';
|
import 'package:dde_gesture_manager/models/settings.provider.dart';
|
||||||
|
import 'package:dde_gesture_manager/utils/helper.dart';
|
||||||
import 'package:dde_gesture_manager/utils/notificator.dart';
|
import 'package:dde_gesture_manager/utils/notificator.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:markdown_editor_ot/markdown_editor.dart';
|
import 'package:markdown_editor_ot/markdown_editor.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
|
||||||
|
|
||||||
class DMarkdownField extends StatefulWidget {
|
class DMarkdownField extends StatefulWidget {
|
||||||
const DMarkdownField({
|
const DMarkdownField({
|
||||||
@@ -36,14 +36,6 @@ class _DMarkdownFieldState extends State<DMarkdownField> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
_launchURL(String url) async {
|
|
||||||
if (await canLaunch(url)) {
|
|
||||||
await launch(url);
|
|
||||||
} else {
|
|
||||||
throw 'Could not launch $url';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(covariant DMarkdownField oldWidget) {
|
void didUpdateWidget(covariant DMarkdownField oldWidget) {
|
||||||
if (oldWidget.initText != widget.initText) {
|
if (oldWidget.initText != widget.initText) {
|
||||||
@@ -54,6 +46,14 @@ class _DMarkdownFieldState extends State<DMarkdownField> {
|
|||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VoidCallback? get _onMdPreviewTap => widget.readOnly
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
setState(() {
|
||||||
|
_previewText = null;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Focus(
|
return Focus(
|
||||||
@@ -70,19 +70,15 @@ class _DMarkdownFieldState extends State<DMarkdownField> {
|
|||||||
),
|
),
|
||||||
child: isPreview
|
child: isPreview
|
||||||
? GestureDetector(
|
? GestureDetector(
|
||||||
onTap: widget.readOnly
|
onTap: _onMdPreviewTap,
|
||||||
? null
|
|
||||||
: () {
|
|
||||||
setState(() {
|
|
||||||
_previewText = null;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: MouseRegion(
|
child: MouseRegion(
|
||||||
cursor: widget.readOnly ? SystemMouseCursors.basic : SystemMouseCursors.text,
|
cursor: widget.readOnly ? SystemMouseCursors.basic : SystemMouseCursors.text,
|
||||||
child: MdPreview(
|
child: MdPreview(
|
||||||
text: _previewText ?? '',
|
text: _previewText ?? '',
|
||||||
padding: EdgeInsets.only(left: 15),
|
padding: EdgeInsets.only(left: 15),
|
||||||
onTapLink: _launchURL,
|
onTapLink: H.launchURL,
|
||||||
|
richTap: _onMdPreviewTap,
|
||||||
|
textStyle: context.t.textTheme.bodyText2,
|
||||||
onCodeCopied: () {
|
onCodeCopied: () {
|
||||||
Notificator.success(
|
Notificator.success(
|
||||||
context,
|
context,
|
||||||
@@ -104,6 +100,7 @@ class _DMarkdownFieldState extends State<DMarkdownField> {
|
|||||||
)
|
)
|
||||||
: MdEditor(
|
: MdEditor(
|
||||||
initText: widget.initText,
|
initText: widget.initText,
|
||||||
|
hintText: LocaleKeys.md_editor_init_text.tr(),
|
||||||
textFocusNode: _focusNode,
|
textFocusNode: _focusNode,
|
||||||
padding: EdgeInsets.symmetric(horizontal: 5),
|
padding: EdgeInsets.symmetric(horizontal: 5),
|
||||||
onComplete: (content) {
|
onComplete: (content) {
|
||||||
@@ -114,6 +111,23 @@ class _DMarkdownFieldState extends State<DMarkdownField> {
|
|||||||
else
|
else
|
||||||
widget.onComplete(content);
|
widget.onComplete(content);
|
||||||
},
|
},
|
||||||
|
actionMessages: {
|
||||||
|
ActionType.done: LocaleKeys.md_editor_done.tr(),
|
||||||
|
ActionType.undo: LocaleKeys.md_editor_undo.tr(),
|
||||||
|
ActionType.redo: LocaleKeys.md_editor_redo.tr(),
|
||||||
|
ActionType.image: LocaleKeys.md_editor_image.tr(),
|
||||||
|
ActionType.link: LocaleKeys.md_editor_link.tr(),
|
||||||
|
ActionType.fontBold: LocaleKeys.md_editor_font_bold.tr(),
|
||||||
|
ActionType.fontItalic: LocaleKeys.md_editor_font_italic.tr(),
|
||||||
|
ActionType.fontStrikethrough: LocaleKeys.md_editor_font_strikethrough.tr(),
|
||||||
|
ActionType.textQuote: LocaleKeys.md_editor_text_quote.tr(),
|
||||||
|
ActionType.list: LocaleKeys.md_editor_list.tr(),
|
||||||
|
ActionType.h1: LocaleKeys.md_editor_h1.tr(),
|
||||||
|
ActionType.h2: LocaleKeys.md_editor_h2.tr(),
|
||||||
|
ActionType.h3: LocaleKeys.md_editor_h3.tr(),
|
||||||
|
ActionType.h4: LocaleKeys.md_editor_h4.tr(),
|
||||||
|
ActionType.h5: LocaleKeys.md_editor_h5.tr(),
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import 'package:dde_gesture_manager/extensions.dart';
|
import 'package:dde_gesture_manager/extensions.dart';
|
||||||
|
import 'package:dde_gesture_manager_api/apis.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class HelpButton extends StatelessWidget {
|
class HelpButton extends StatelessWidget {
|
||||||
const HelpButton({Key? key}) : super(key: key);
|
const HelpButton({Key? key}) : super(key: key);
|
||||||
@@ -7,7 +10,11 @@ class HelpButton extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {},
|
onTap: () async {
|
||||||
|
if (await canLaunch(Apis.appManualUrl(kIsWeb))) {
|
||||||
|
await launch(Apis.appManualUrl(kIsWeb));
|
||||||
|
}
|
||||||
|
},
|
||||||
child: MouseRegion(
|
child: MouseRegion(
|
||||||
cursor: SystemMouseCursors.click,
|
cursor: SystemMouseCursors.click,
|
||||||
child: Tooltip(
|
child: Tooltip(
|
||||||
|
|||||||
@@ -54,9 +54,16 @@ class _LoginWidgetState extends State<LoginWidget> {
|
|||||||
title: LocaleKeys.info_sign_up_hint_title.tr(),
|
title: LocaleKeys.info_sign_up_hint_title.tr(),
|
||||||
description: LocaleKeys.info_sign_up_hint_description.tr(),
|
description: LocaleKeys.info_sign_up_hint_description.tr(),
|
||||||
);
|
);
|
||||||
|
else if (code == HttpStatus.forbidden)
|
||||||
|
Notificator.info(
|
||||||
|
context,
|
||||||
|
title: LocaleKeys.info_user_blocked_hint_title.tr(),
|
||||||
|
description: LocaleKeys.info_user_blocked_hint_description.tr(),
|
||||||
|
);
|
||||||
else
|
else
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
},
|
},
|
||||||
onSubmitAnimationCompleted: () {
|
onSubmitAnimationCompleted: () {
|
||||||
var token = H().sp.getString(SPKeys.accessToken);
|
var token = H().sp.getString(SPKeys.accessToken);
|
||||||
@@ -69,7 +76,7 @@ class _LoginWidgetState extends State<LoginWidget> {
|
|||||||
.read<ConfigsProvider>()
|
.read<ConfigsProvider>()
|
||||||
.setProps(accessToken: token, email: H().sp.getString(SPKeys.loginEmail));
|
.setProps(accessToken: token, email: H().sp.getString(SPKeys.loginEmail));
|
||||||
},
|
},
|
||||||
onRecoverPassword: (_) {},
|
onRecoverPassword: (_) => null,
|
||||||
hideForgotPasswordButton: true,
|
hideForgotPasswordButton: true,
|
||||||
disableCustomPageTransformer: true,
|
disableCustomPageTransformer: true,
|
||||||
messages: LoginMessages(
|
messages: LoginMessages(
|
||||||
@@ -81,11 +88,13 @@ class _LoginWidgetState extends State<LoginWidget> {
|
|||||||
if (FlutterLogin.defaultEmailValidator(value) != null) {
|
if (FlutterLogin.defaultEmailValidator(value) != null) {
|
||||||
return LocaleKeys.me_login_email_error_hint.tr();
|
return LocaleKeys.me_login_email_error_hint.tr();
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
},
|
},
|
||||||
passwordValidator: (value) {
|
passwordValidator: (value) {
|
||||||
if (value!.isEmpty || value.length < 8 || value.length > 16) {
|
if (value!.isEmpty || value.length < 8 || value.length > 16) {
|
||||||
return LocaleKeys.me_login_password_hint.tr();
|
return LocaleKeys.me_login_password_hint.tr();
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
},
|
},
|
||||||
theme: LoginTheme(
|
theme: LoginTheme(
|
||||||
pageColorDark: Colors.transparent,
|
pageColorDark: Colors.transparent,
|
||||||
|
|||||||
@@ -0,0 +1,309 @@
|
|||||||
|
import 'package:auto_size_text/auto_size_text.dart';
|
||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:dde_gesture_manager/constants/constants.dart';
|
||||||
|
import 'package:dde_gesture_manager/http/api.dart';
|
||||||
|
import 'package:dde_gesture_manager/models/local_schemes_provider.dart';
|
||||||
|
import 'package:dde_gesture_manager/models/settings.provider.dart';
|
||||||
|
import 'package:dde_gesture_manager/utils/helper.dart';
|
||||||
|
import 'package:dde_gesture_manager/widgets/dde_button.dart';
|
||||||
|
import 'package:dde_gesture_manager_api/models.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:dde_gesture_manager/extensions.dart';
|
||||||
|
import 'package:markdown_editor_ot/markdown_editor.dart';
|
||||||
|
import 'package:numeral/numeral.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
|
enum MarketSortType {
|
||||||
|
recommend,
|
||||||
|
updated,
|
||||||
|
likes,
|
||||||
|
downloads,
|
||||||
|
}
|
||||||
|
|
||||||
|
class MarketWidget extends StatefulWidget {
|
||||||
|
const MarketWidget({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_MarketWidgetState createState() => _MarketWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MarketWidgetState extends State<MarketWidget> {
|
||||||
|
List<MarketSchemeTransMetaData> _schemes = [];
|
||||||
|
bool _hasMore = true;
|
||||||
|
int _currentPage = 0;
|
||||||
|
MarketSortType _type = MarketSortType.recommend;
|
||||||
|
String? _selected;
|
||||||
|
String? _hovering;
|
||||||
|
List<int> _likedSchemes = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if (context.hasToken)
|
||||||
|
Api.userLikes().then((value) {
|
||||||
|
if (mounted && value != null)
|
||||||
|
setState(() {
|
||||||
|
_likedSchemes = value;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
Api.marketSchemes(type: _type, page: _currentPage).then((value) {
|
||||||
|
if (mounted && value != null)
|
||||||
|
setState(() {
|
||||||
|
_schemes = value.items;
|
||||||
|
_selected = value.items.isNotEmpty ? value.items.first.uuid : null;
|
||||||
|
_hasMore = value.hasMore;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Color _getItemBackgroundColor(int index, String? schemeId) {
|
||||||
|
Color _color = index % 2 == 0 ? context.t.scaffoldBackgroundColor : context.t.backgroundColor;
|
||||||
|
if (schemeId == _hovering) _color = context.t.dialogBackgroundColor;
|
||||||
|
if (schemeId == _selected) _color = context.read<SettingsProvider>().currentActiveColor;
|
||||||
|
return _color;
|
||||||
|
}
|
||||||
|
|
||||||
|
_refreshList() {
|
||||||
|
Future.delayed(const Duration(milliseconds: 100), () {
|
||||||
|
Api.marketSchemes(type: _type, page: 0).then((value) {
|
||||||
|
if (mounted && value != null)
|
||||||
|
setState(() {
|
||||||
|
_schemes = value.items;
|
||||||
|
_hasMore = value.hasMore;
|
||||||
|
_selected = value.items.first.uuid;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_getMoreSchemes() {
|
||||||
|
'try to fetch page: ${_currentPage + 1}'.sout();
|
||||||
|
if (_hasMore)
|
||||||
|
Api.marketSchemes(type: _type, page: ++_currentPage).then((value) {
|
||||||
|
if (mounted && value != null)
|
||||||
|
setState(() {
|
||||||
|
_schemes.addAll(value.items);
|
||||||
|
_hasMore = value.hasMore;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var currentSelectedScheme = _schemes.firstWhereOrNull((e) => e.uuid == _selected);
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 15, bottom: 2),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
MarketSortType.recommend,
|
||||||
|
MarketSortType.updated,
|
||||||
|
MarketSortType.likes,
|
||||||
|
MarketSortType.downloads,
|
||||||
|
]
|
||||||
|
.map(
|
||||||
|
(e) => Flexible(
|
||||||
|
flex: 1,
|
||||||
|
fit: FlexFit.tight,
|
||||||
|
child: MouseRegion(
|
||||||
|
cursor: SystemMouseCursors.click,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_type = e;
|
||||||
|
_refreshList();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
'${LocaleKeys.market_sort_types}.${e.name}'.tr(),
|
||||||
|
style: _type == e ? TextStyle(fontWeight: FontWeight.bold, fontSize: 15) : null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
width: .3,
|
||||||
|
color: context.t.dividerColor,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(defaultBorderRadius),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 1, vertical: 2),
|
||||||
|
child: ListView.builder(
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (index == _schemes.length - 1) _getMoreSchemes();
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_selected = _schemes[index].uuid;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: MouseRegion(
|
||||||
|
cursor: SystemMouseCursors.click,
|
||||||
|
onEnter: (_) {
|
||||||
|
setState(() {
|
||||||
|
_hovering = _schemes[index].uuid;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
color: _getItemBackgroundColor(index, _schemes[index].uuid),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 6, right: 12.0),
|
||||||
|
child: DefaultTextStyle(
|
||||||
|
style: context.t.textTheme.bodyText2!.copyWith(
|
||||||
|
color: _selected == _schemes[index].uuid ? Colors.white : null,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(_schemes[index].name ?? ''),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 50,
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: AutoSizeText(
|
||||||
|
'${numeral(_schemes[index].downloads ?? 0, fractionDigits: 1)}'
|
||||||
|
.padLeft(5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Icon(
|
||||||
|
Icons.file_download,
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 50,
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: AutoSizeText(
|
||||||
|
'${numeral(_schemes[index].likes ?? 0, fractionDigits: 1)}'.padLeft(5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Icon(
|
||||||
|
_likedSchemes.contains(_schemes[index].id)
|
||||||
|
? Icons.thumb_up
|
||||||
|
: Icons.thumb_up_off_alt,
|
||||||
|
size: 17),
|
||||||
|
]
|
||||||
|
.map((e) => Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 3),
|
||||||
|
child: e,
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemCount: _schemes.length,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(height: 10),
|
||||||
|
Flexible(
|
||||||
|
child: Container(
|
||||||
|
height: double.infinity,
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
width: .3,
|
||||||
|
color: context.t.dividerColor,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(defaultBorderRadius),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 8),
|
||||||
|
child: MdPreview(
|
||||||
|
text: _schemes.firstWhereOrNull((e) => e.uuid == _selected)?.description ?? '',
|
||||||
|
widgetImage: (imageUrl) => CachedNetworkImage(
|
||||||
|
imageUrl: imageUrl,
|
||||||
|
placeholder: (context, url) => const SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 300,
|
||||||
|
child: Center(child: CircularProgressIndicator()),
|
||||||
|
),
|
||||||
|
errorWidget: (context, url, error) => const Icon(Icons.error),
|
||||||
|
),
|
||||||
|
onTapLink: H.launchURL,
|
||||||
|
textStyle: context.t.textTheme.bodyText2,
|
||||||
|
onCodeCopied: () {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 5),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
DButton.like(
|
||||||
|
enabled: context.watchHasToken,
|
||||||
|
onTap: () {
|
||||||
|
bool liked = _likedSchemes.contains(currentSelectedScheme!.id!);
|
||||||
|
Api.likeScheme(schemeId: currentSelectedScheme.uuid!, isLike: !liked).then((value) {
|
||||||
|
if (value) {
|
||||||
|
setState(() {
|
||||||
|
if (liked) {
|
||||||
|
_likedSchemes.remove(currentSelectedScheme.id);
|
||||||
|
currentSelectedScheme.likes = currentSelectedScheme.likes! - 1;
|
||||||
|
} else {
|
||||||
|
_likedSchemes.add(currentSelectedScheme.id!);
|
||||||
|
currentSelectedScheme.likes = currentSelectedScheme.likes! + 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
DButton.download(
|
||||||
|
enabled: (context.watch<LocalSchemesProvider>().schemes ?? []).every((e) => e.scheme.id != _selected),
|
||||||
|
onTap: () {
|
||||||
|
Api.downloadScheme(schemeId: currentSelectedScheme!.uuid!).then((value) {
|
||||||
|
if (value != null) {
|
||||||
|
H.handleDownloadScheme(context, value);
|
||||||
|
setState(() {
|
||||||
|
currentSelectedScheme.downloads = currentSelectedScheme.downloads! + 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.map((e) => Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 4),
|
||||||
|
child: e,
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,323 @@
|
|||||||
|
import 'package:auto_size_text/auto_size_text.dart';
|
||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:dde_gesture_manager/constants/constants.dart';
|
||||||
|
import 'package:dde_gesture_manager/extensions.dart';
|
||||||
|
import 'package:dde_gesture_manager/http/api.dart';
|
||||||
|
import 'package:dde_gesture_manager/models/configs.provider.dart';
|
||||||
|
import 'package:dde_gesture_manager/models/local_schemes_provider.dart';
|
||||||
|
import 'package:dde_gesture_manager/models/scheme_list_refresh_key.provider.dart';
|
||||||
|
import 'package:dde_gesture_manager/models/settings.provider.dart';
|
||||||
|
import 'package:dde_gesture_manager/utils/helper.dart';
|
||||||
|
import 'package:dde_gesture_manager/utils/notificator.dart';
|
||||||
|
import 'package:dde_gesture_manager/utils/simple_throttle.dart';
|
||||||
|
import 'package:dde_gesture_manager_api/models.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_platform_alert/flutter_platform_alert.dart';
|
||||||
|
import 'package:markdown_editor_ot/markdown_editor.dart';
|
||||||
|
import 'package:numeral/fun.dart';
|
||||||
|
|
||||||
|
import 'dde_button.dart';
|
||||||
|
|
||||||
|
enum SchemeListType {
|
||||||
|
uploaded,
|
||||||
|
downloaded,
|
||||||
|
liked,
|
||||||
|
}
|
||||||
|
|
||||||
|
class MeWidget extends StatefulWidget {
|
||||||
|
const MeWidget({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_MeWidgetState createState() => _MeWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MeWidgetState extends State<MeWidget> {
|
||||||
|
List<SimpleSchemeTransMetaData> _schemes = [];
|
||||||
|
SchemeListType _type = SchemeListType.uploaded;
|
||||||
|
String? _selected;
|
||||||
|
String? _hovering;
|
||||||
|
int _refreshKey = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
Api.userSchemes(type: _type).then((value) {
|
||||||
|
if (mounted && value != null)
|
||||||
|
setState(() {
|
||||||
|
_schemes = value;
|
||||||
|
_selected = value.isNotEmpty ? value.first.uuid : null;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Color _getItemBackgroundColor(int index, String? schemeId) {
|
||||||
|
Color _color = index % 2 == 0 ? context.t.scaffoldBackgroundColor : context.t.backgroundColor;
|
||||||
|
if (schemeId == _hovering) _color = context.t.dialogBackgroundColor;
|
||||||
|
if (schemeId == _selected) _color = context.read<SettingsProvider>().currentActiveColor;
|
||||||
|
return _color;
|
||||||
|
}
|
||||||
|
|
||||||
|
_refreshList() {
|
||||||
|
Future.delayed(const Duration(milliseconds: 100), () {
|
||||||
|
Api.userSchemes(type: _type).then((value) {
|
||||||
|
if (mounted && value != null)
|
||||||
|
setState(() {
|
||||||
|
_schemes = value;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var refreshKey = context.watch<SchemeListRefreshKeyProvider>().refreshKey;
|
||||||
|
if (_refreshKey != refreshKey) {
|
||||||
|
Future.microtask(SimpleThrottle.bind(_refreshList));
|
||||||
|
}
|
||||||
|
_refreshKey = refreshKey;
|
||||||
|
|
||||||
|
var currentSelectedScheme = _schemes.firstWhereOrNull((e) => e.uuid == _selected);
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(top: 10),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.person, size: defaultButtonHeight),
|
||||||
|
Flexible(
|
||||||
|
child: AutoSizeText(
|
||||||
|
context.watch<ConfigsProvider>().email ?? '',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DButton.logout(
|
||||||
|
enabled: true,
|
||||||
|
onTap: () => context.read<ConfigsProvider>().setProps(accessToken: '', email: ''),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 3, bottom: 2),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
SchemeListType.uploaded,
|
||||||
|
SchemeListType.downloaded,
|
||||||
|
SchemeListType.liked,
|
||||||
|
]
|
||||||
|
.map(
|
||||||
|
(e) => Flexible(
|
||||||
|
flex: 1,
|
||||||
|
fit: FlexFit.tight,
|
||||||
|
child: MouseRegion(
|
||||||
|
cursor: SystemMouseCursors.click,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_type = e;
|
||||||
|
});
|
||||||
|
Api.userSchemes(type: e).then((value) {
|
||||||
|
if (mounted && value != null)
|
||||||
|
setState(() {
|
||||||
|
_schemes = value;
|
||||||
|
_selected = value.isEmpty ? null : value.first.uuid;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
'${LocaleKeys.me_scheme_types}.${e.name}'.tr(),
|
||||||
|
style: _type == e ? TextStyle(fontWeight: FontWeight.bold, fontSize: 15) : null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
width: .3,
|
||||||
|
color: context.t.dividerColor,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(defaultBorderRadius),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 1, vertical: 2),
|
||||||
|
child: ListView.builder(
|
||||||
|
itemBuilder: (context, index) => GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_selected = _schemes[index].uuid;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: MouseRegion(
|
||||||
|
cursor: SystemMouseCursors.click,
|
||||||
|
onEnter: (_) {
|
||||||
|
setState(() {
|
||||||
|
_hovering = _schemes[index].uuid;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
color: _getItemBackgroundColor(index, _schemes[index].uuid),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 6, right: 12.0),
|
||||||
|
child: DefaultTextStyle(
|
||||||
|
style: context.t.textTheme.bodyText2!.copyWith(
|
||||||
|
color: _selected == _schemes[index].uuid ? Colors.white : null,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(_schemes[index].name ?? ''),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 50,
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: AutoSizeText(
|
||||||
|
'${numeral(_schemes[index].downloads ?? 0, fractionDigits: 1)}'
|
||||||
|
.padLeft(5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Icon(
|
||||||
|
Icons.file_download,
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 50,
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: AutoSizeText(
|
||||||
|
'${numeral(_schemes[index].likes ?? 0, fractionDigits: 1)}'.padLeft(5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Icon(_schemes[index].liked == true ? Icons.thumb_up : Icons.thumb_up_off_alt,
|
||||||
|
size: 17),
|
||||||
|
]
|
||||||
|
.map((e) => Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 3),
|
||||||
|
child: e,
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
itemCount: _schemes.length,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(height: 10),
|
||||||
|
Flexible(
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
width: .3,
|
||||||
|
color: context.t.dividerColor,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(defaultBorderRadius),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 8),
|
||||||
|
child: MdPreview(
|
||||||
|
text: _schemes.firstWhereOrNull((e) => e.uuid == _selected)?.description ?? '',
|
||||||
|
onTapLink: H.launchURL,
|
||||||
|
textStyle: context.t.textTheme.bodyText2,
|
||||||
|
widgetImage: (imageUrl) => CachedNetworkImage(
|
||||||
|
imageUrl: imageUrl,
|
||||||
|
placeholder: (context, url) => const SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 300,
|
||||||
|
child: Center(child: CircularProgressIndicator()),
|
||||||
|
),
|
||||||
|
errorWidget: (context, url, error) => const Icon(Icons.error),
|
||||||
|
),
|
||||||
|
onCodeCopied: () {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 5),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
if (_type == SchemeListType.uploaded)
|
||||||
|
DButton.share(
|
||||||
|
enabled: currentSelectedScheme?.shared == false,
|
||||||
|
onTap: () {
|
||||||
|
Notificator.showConfirm(
|
||||||
|
title: LocaleKeys.info_share_title.tr(),
|
||||||
|
description: LocaleKeys.info_share_description.tr())
|
||||||
|
.then((value) {
|
||||||
|
if (value == CustomButton.positiveButton) {
|
||||||
|
Notificator.success(context, title: LocaleKeys.info_share_success.tr());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
DButton.like(
|
||||||
|
enabled: true,
|
||||||
|
onTap: () {
|
||||||
|
Api.likeScheme(schemeId: currentSelectedScheme!.uuid!, isLike: !currentSelectedScheme.liked)
|
||||||
|
.then((value) {
|
||||||
|
if (value) {
|
||||||
|
context
|
||||||
|
.read<SchemeListRefreshKeyProvider>()
|
||||||
|
.setProps(refreshKey: DateTime.now().millisecondsSinceEpoch);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
DButton.download(
|
||||||
|
enabled: (context.watch<LocalSchemesProvider>().schemes ?? []).every((e) => e.scheme.id != _selected),
|
||||||
|
onTap: () {
|
||||||
|
Api.downloadScheme(schemeId: currentSelectedScheme!.uuid!).then((value) {
|
||||||
|
if (value != null) {
|
||||||
|
H.handleDownloadScheme(context, value);
|
||||||
|
context
|
||||||
|
.read<SchemeListRefreshKeyProvider>()
|
||||||
|
.setProps(refreshKey: DateTime.now().millisecondsSinceEpoch);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.map((e) => Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 4),
|
||||||
|
child: e,
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,8 +23,7 @@ class TableCellShortcutListener extends StatefulWidget {
|
|||||||
|
|
||||||
class _TableCellShortcutListenerState extends State<TableCellShortcutListener> {
|
class _TableCellShortcutListenerState extends State<TableCellShortcutListener> {
|
||||||
List<KeyNames> _shortcut = [];
|
List<KeyNames> _shortcut = [];
|
||||||
bool inputMode = false;
|
final FocusNode _focusNode = FocusNode();
|
||||||
FocusNode _focusNode = FocusNode();
|
|
||||||
|
|
||||||
_handleFocusChange() {
|
_handleFocusChange() {
|
||||||
if (!_focusNode.hasFocus) {
|
if (!_focusNode.hasFocus) {
|
||||||
@@ -43,6 +42,7 @@ class _TableCellShortcutListenerState extends State<TableCellShortcutListener> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
super.initState();
|
||||||
var __shortcut = widget.initShortcut.split('+');
|
var __shortcut = widget.initShortcut.split('+');
|
||||||
__shortcut.forEach((name) {
|
__shortcut.forEach((name) {
|
||||||
var keyNames = getPhysicalKeyNamesByRealName(name);
|
var keyNames = getPhysicalKeyNamesByRealName(name);
|
||||||
@@ -50,7 +50,6 @@ class _TableCellShortcutListenerState extends State<TableCellShortcutListener> {
|
|||||||
});
|
});
|
||||||
_shortcut.sort();
|
_shortcut.sort();
|
||||||
_focusNode.addListener(_handleFocusChange);
|
_focusNode.addListener(_handleFocusChange);
|
||||||
super.initState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -70,8 +69,8 @@ class _TableCellShortcutListenerState extends State<TableCellShortcutListener> {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
_shortcut = [];
|
_shortcut = [];
|
||||||
inputMode = true;
|
|
||||||
});
|
});
|
||||||
|
_focusNode.requestFocus();
|
||||||
},
|
},
|
||||||
child: Focus(
|
child: Focus(
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
#include <flutter_platform_alert/flutter_platform_alert_plugin.h>
|
#include <flutter_platform_alert/flutter_platform_alert_plugin.h>
|
||||||
|
#include <sentry_flutter/sentry_flutter_plugin.h>
|
||||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||||
#include <window_manager/window_manager_plugin.h>
|
#include <window_manager/window_manager_plugin.h>
|
||||||
|
|
||||||
@@ -14,6 +15,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
|||||||
g_autoptr(FlPluginRegistrar) flutter_platform_alert_registrar =
|
g_autoptr(FlPluginRegistrar) flutter_platform_alert_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterPlatformAlertPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterPlatformAlertPlugin");
|
||||||
flutter_platform_alert_plugin_register_with_registrar(flutter_platform_alert_registrar);
|
flutter_platform_alert_plugin_register_with_registrar(flutter_platform_alert_registrar);
|
||||||
|
g_autoptr(FlPluginRegistrar) sentry_flutter_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "SentryFlutterPlugin");
|
||||||
|
sentry_flutter_plugin_register_with_registrar(sentry_flutter_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
flutter_platform_alert
|
flutter_platform_alert
|
||||||
|
sentry_flutter
|
||||||
url_launcher_linux
|
url_launcher_linux
|
||||||
window_manager
|
window_manager
|
||||||
)
|
)
|
||||||
|
|||||||
+16
-12
@@ -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.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 1.0.0+1
|
version: 1.0.1+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.15.0 <3.0.0"
|
sdk: ">=2.15.0 <3.0.0"
|
||||||
@@ -23,25 +23,27 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
cupertino_icons: ^1.0.2
|
cupertino_icons: ^1.0.4
|
||||||
window_manager: ^0.0.5
|
window_manager: ^0.1.3
|
||||||
localstorage: ^4.0.0+1
|
localstorage: ^4.0.0+1
|
||||||
shared_preferences: ^2.0.7
|
shared_preferences: ^2.0.12
|
||||||
xdg_directories: 0.2.0
|
xdg_directories: ^0.2.0
|
||||||
gsettings: 0.2.0
|
gsettings: 0.2.3
|
||||||
provider: ^6.0.0
|
provider: ^6.0.2
|
||||||
package_info_plus: ^1.0.6
|
package_info_plus: 1.3.0
|
||||||
easy_localization: ^3.0.0
|
easy_localization: ^3.0.0
|
||||||
glass_kit: ^2.0.1
|
glass_kit: ^2.0.1
|
||||||
rect_getter: ^1.0.0
|
rect_getter: ^1.0.0
|
||||||
path_provider: ^2.0.5
|
path_provider: ^2.0.8
|
||||||
uuid: ^3.0.5
|
uuid: ^3.0.5
|
||||||
adaptive_scrollbar: ^2.1.0
|
adaptive_scrollbar: ^2.1.0
|
||||||
flutter_platform_alert: ^0.2.1
|
flutter_platform_alert: ^0.2.1
|
||||||
cached_network_image: ^3.2.0
|
cached_network_image: ^3.2.0
|
||||||
url_launcher: ^6.0.17
|
url_launcher: ^6.0.18
|
||||||
flutter_login: ^3.1.0
|
flutter_login: ^3.1.0
|
||||||
auto_size_text: ^3.0.0
|
auto_size_text: ^3.0.0
|
||||||
|
numeral: ^1.2.5
|
||||||
|
sentry_flutter: ^6.3.0
|
||||||
markdown_editor_ot:
|
markdown_editor_ot:
|
||||||
path: 3rd_party/markdown_editor_ot
|
path: 3rd_party/markdown_editor_ot
|
||||||
cherry_toast:
|
cherry_toast:
|
||||||
@@ -54,8 +56,10 @@ dependencies:
|
|||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
build_runner: 2.1.2
|
build_runner: 2.1.7
|
||||||
source_gen: 1.1.0
|
source_gen: 1.2.1
|
||||||
|
yaml: any
|
||||||
|
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|||||||
@@ -20,7 +20,13 @@
|
|||||||
"tip": "Display help documentation"
|
"tip": "Display help documentation"
|
||||||
},
|
},
|
||||||
"market": {
|
"market": {
|
||||||
"title": "Scheme market"
|
"title": "Scheme market",
|
||||||
|
"sort_types": {
|
||||||
|
"recommend": "Recommend",
|
||||||
|
"updated": "updated",
|
||||||
|
"likes": "likes",
|
||||||
|
"downloads": "downloads"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"local_manager": {
|
"local_manager": {
|
||||||
"title": "Local scheme management",
|
"title": "Local scheme management",
|
||||||
@@ -66,19 +72,26 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"operation": {
|
"operation": {
|
||||||
"add": "Add",
|
"add": "add",
|
||||||
"delete": "delete",
|
"delete": "delete",
|
||||||
"duplicate": "duplicate",
|
"duplicate": "duplicate",
|
||||||
"apply": "apply",
|
"apply": "apply",
|
||||||
"paste": "paste",
|
"paste": "paste",
|
||||||
"logout": "sign out"
|
"logout": "sign out",
|
||||||
|
"upload": "upload",
|
||||||
|
"download": "download",
|
||||||
|
"share": "share to market",
|
||||||
|
"like": "like"
|
||||||
},
|
},
|
||||||
"str": {
|
"str": {
|
||||||
"null": "Null",
|
"null": "Null",
|
||||||
"new_scheme": "New gesture scheme",
|
"new_scheme": "New gesture scheme",
|
||||||
"copy": "copy",
|
"copy": "copy",
|
||||||
"yes": "Yes",
|
"yes": "Yes",
|
||||||
"no": "No"
|
"no": "No",
|
||||||
|
"ok": "OK",
|
||||||
|
"share": "Share",
|
||||||
|
"cancel": "Cancel"
|
||||||
},
|
},
|
||||||
"built_in_commands": {
|
"built_in_commands": {
|
||||||
"ShowWorkspace": "ShowWorkspace",
|
"ShowWorkspace": "ShowWorkspace",
|
||||||
@@ -126,6 +139,37 @@
|
|||||||
"description_for_startup": "Click [{YES}] to view, click [{NO}] ignore this update",
|
"description_for_startup": "Click [{YES}] to view, click [{NO}] ignore this update",
|
||||||
"title_already_latest": "Already the latest version ~",
|
"title_already_latest": "Already the latest version ~",
|
||||||
"description_for_manual": "Visit the official website to see more?"
|
"description_for_manual": "Visit the official website to see more?"
|
||||||
|
},
|
||||||
|
"login_for_upload": {
|
||||||
|
"title": "please login",
|
||||||
|
"description": "You need to login first to perform upload operations"
|
||||||
|
},
|
||||||
|
"upload_and_share": {
|
||||||
|
"title": "Share the scheme at the same time?",
|
||||||
|
"description": "If you select [Share], other users can see this scheme and download it;\nIf you select [Cancel], you can still find this scheme in the [My Upload] list and share."
|
||||||
|
},
|
||||||
|
"user_blocked_hint": {
|
||||||
|
"title": "The account has been blocked!",
|
||||||
|
"description": "If you have any questions, please contact me using email."
|
||||||
|
},
|
||||||
|
"upload": {
|
||||||
|
"success": "Upload success ~",
|
||||||
|
"failed": "Upload failed..",
|
||||||
|
"name_occupied": "Name occupied……",
|
||||||
|
"pls_rename": "Please rename",
|
||||||
|
"pls_add_description": "Please enter the scheme description"
|
||||||
|
},
|
||||||
|
"share": {
|
||||||
|
"title": "Are you sure to sharing?",
|
||||||
|
"description": "Other users can see this scheme and download it after share",
|
||||||
|
"success": "Share success"
|
||||||
|
},
|
||||||
|
"apply_scheme": {
|
||||||
|
"success": "Apply success",
|
||||||
|
"description": "The gesture scheme will take effect after logout, pay attention to the save work data~",
|
||||||
|
"logout_immediately": "Logout",
|
||||||
|
"commands_copied_title": "Commands copied!",
|
||||||
|
"commands_copied_description": "Please open the terminal and paste the command to execute"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"me": {
|
"me": {
|
||||||
@@ -135,6 +179,29 @@
|
|||||||
"email_hint": "Please enter email",
|
"email_hint": "Please enter email",
|
||||||
"password_hint": "Please enter 8-16-bit password",
|
"password_hint": "Please enter 8-16-bit password",
|
||||||
"email_error_hint": "Please enter your vaild email"
|
"email_error_hint": "Please enter your vaild email"
|
||||||
}
|
},
|
||||||
|
"scheme_types": {
|
||||||
|
"uploaded": "Uploaded",
|
||||||
|
"downloaded": "Downloaded",
|
||||||
|
"liked": "Liked"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"md_editor": {
|
||||||
|
"init_text": "Please enter a description",
|
||||||
|
"done": "Done",
|
||||||
|
"undo": "Undo",
|
||||||
|
"redo": "redo",
|
||||||
|
"image": "Image",
|
||||||
|
"link": "Link",
|
||||||
|
"font_bold": "FontBold",
|
||||||
|
"font_italic": "FontItalic",
|
||||||
|
"font_strikethrough": "FontStrikethrough",
|
||||||
|
"text_quote": "TextQuote",
|
||||||
|
"list": "List",
|
||||||
|
"h1": "H1",
|
||||||
|
"h2": "H2",
|
||||||
|
"h3": "H3",
|
||||||
|
"h4": "H4",
|
||||||
|
"h5": "H5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,13 @@
|
|||||||
"tip": "显示帮助文档"
|
"tip": "显示帮助文档"
|
||||||
},
|
},
|
||||||
"market": {
|
"market": {
|
||||||
"title": "方案市场"
|
"title": "方案市场",
|
||||||
|
"sort_types": {
|
||||||
|
"recommend": "推荐",
|
||||||
|
"updated": "最近更新",
|
||||||
|
"likes": "点赞数",
|
||||||
|
"downloads": "下载量"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"local_manager": {
|
"local_manager": {
|
||||||
"title": "本地方案管理",
|
"title": "本地方案管理",
|
||||||
@@ -71,14 +77,21 @@
|
|||||||
"duplicate": "复制",
|
"duplicate": "复制",
|
||||||
"apply": "应用",
|
"apply": "应用",
|
||||||
"paste": "粘贴",
|
"paste": "粘贴",
|
||||||
"logout": "退出登录"
|
"logout": "退出登录",
|
||||||
|
"upload": "上传",
|
||||||
|
"download": "下载",
|
||||||
|
"share": "分享到市场",
|
||||||
|
"like": "点赞"
|
||||||
},
|
},
|
||||||
"str": {
|
"str": {
|
||||||
"null": "无",
|
"null": "无",
|
||||||
"new_scheme": "新建手势方案",
|
"new_scheme": "新建手势方案",
|
||||||
"copy": "副本",
|
"copy": "副本",
|
||||||
"yes": "是",
|
"yes": "是",
|
||||||
"no": "否"
|
"no": "否",
|
||||||
|
"ok": "好的",
|
||||||
|
"share": "分享",
|
||||||
|
"cancel": "放弃"
|
||||||
},
|
},
|
||||||
"built_in_commands": {
|
"built_in_commands": {
|
||||||
"ShowWorkspace": "显示工作区",
|
"ShowWorkspace": "显示工作区",
|
||||||
@@ -126,6 +139,37 @@
|
|||||||
"description_for_startup": "点击[{yes}]查看,点击[{no}]忽略本次更新",
|
"description_for_startup": "点击[{yes}]查看,点击[{no}]忽略本次更新",
|
||||||
"title_already_latest": "已经是最新版本~",
|
"title_already_latest": "已经是最新版本~",
|
||||||
"description_for_manual": "是否前去官网查看?"
|
"description_for_manual": "是否前去官网查看?"
|
||||||
|
},
|
||||||
|
"login_for_upload": {
|
||||||
|
"title": "请登录",
|
||||||
|
"description": "您需要先登录才能进行上传操作"
|
||||||
|
},
|
||||||
|
"upload_and_share": {
|
||||||
|
"title": "是否同时分享到方案市场?",
|
||||||
|
"description": "如果选择[分享],其他用户可以看到本方案并下载使用;\n如果选择[放弃],您仍可以稍后在[我的上传]列表中找到本方案进行操作。"
|
||||||
|
},
|
||||||
|
"user_blocked_hint": {
|
||||||
|
"title": "该账号已被封禁!",
|
||||||
|
"description": "如有疑问请通过发送邮件联系"
|
||||||
|
},
|
||||||
|
"upload": {
|
||||||
|
"success": "上传成功~",
|
||||||
|
"failed": "上传失败。。",
|
||||||
|
"name_occupied": "名称被占用……",
|
||||||
|
"pls_rename": "请重新命名",
|
||||||
|
"pls_add_description": "请填写方案描述~"
|
||||||
|
},
|
||||||
|
"share": {
|
||||||
|
"title": "确定分享?",
|
||||||
|
"description": "分享后其他用户可以看到本方案并下载使用",
|
||||||
|
"success": "分享成功"
|
||||||
|
},
|
||||||
|
"apply_scheme": {
|
||||||
|
"success": "设置成功",
|
||||||
|
"description": "手势方案需注销后才能生效,注销前注意保存工作进度~",
|
||||||
|
"logout_immediately": "立即注销",
|
||||||
|
"commands_copied_title": "命令复制成功!",
|
||||||
|
"commands_copied_description": "请打开终端后粘贴命令并执行"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"me": {
|
"me": {
|
||||||
@@ -135,6 +179,29 @@
|
|||||||
"email_hint": "请输入邮箱",
|
"email_hint": "请输入邮箱",
|
||||||
"password_hint": "请输入8-16位密码",
|
"password_hint": "请输入8-16位密码",
|
||||||
"email_error_hint": "请输入正确的邮箱"
|
"email_error_hint": "请输入正确的邮箱"
|
||||||
}
|
},
|
||||||
|
"scheme_types": {
|
||||||
|
"uploaded": "我的上传",
|
||||||
|
"downloaded": "我的下载",
|
||||||
|
"liked": "我的点赞"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"md_editor": {
|
||||||
|
"init_text": "请输入描述",
|
||||||
|
"done": "完成",
|
||||||
|
"undo": "撤销",
|
||||||
|
"redo": "重做",
|
||||||
|
"image": "图片",
|
||||||
|
"link": "链接",
|
||||||
|
"font_bold": "加粗",
|
||||||
|
"font_italic": "斜体",
|
||||||
|
"font_strikethrough": "删除线",
|
||||||
|
"text_quote": "文字引用",
|
||||||
|
"list": "无序列表",
|
||||||
|
"h1": "一级标题",
|
||||||
|
"h2": "二级标题",
|
||||||
|
"h3": "三级标题",
|
||||||
|
"h4": "四级标题",
|
||||||
|
"h5": "五级标题"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:yaml/yaml.dart';
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
var document = loadYaml(File('pubspec.yaml').readAsStringSync());
|
||||||
|
print(document['version'].replaceAll('+', '.'));
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"once": true,
|
||||||
|
"title": "欢迎",
|
||||||
|
"content": "感谢使用本工具,使用前建议先点击右下角阅读使用说明哦~"
|
||||||
|
}
|
||||||
Binary file not shown.
+13
-1
@@ -11,12 +11,14 @@
|
|||||||
For more details:
|
For more details:
|
||||||
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
|
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
|
||||||
-->
|
-->
|
||||||
<base href="/">
|
<base href="/dgm_web/">
|
||||||
|
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||||
<meta name="description" content="A new Flutter project.">
|
<meta name="description" content="A new Flutter project.">
|
||||||
|
|
||||||
|
<link rel="shortcut icon" href="icons/Icon-192.png" type="image/x-icon" />
|
||||||
|
|
||||||
<!-- iOS meta tags & icons -->
|
<!-- iOS meta tags & icons -->
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||||
@@ -25,6 +27,16 @@
|
|||||||
|
|
||||||
<title>dde_gesture_manager</title>
|
<title>dde_gesture_manager</title>
|
||||||
<link rel="manifest" href="manifest.json">
|
<link rel="manifest" href="manifest.json">
|
||||||
|
<!-- Baidu statistics -->
|
||||||
|
<script>
|
||||||
|
var _hmt = _hmt || [];
|
||||||
|
(function() {
|
||||||
|
var hm = document.createElement("script");
|
||||||
|
hm.src = "https://hm.baidu.com/hm.js?45acc9824a216a8f1792b419eb91f090";
|
||||||
|
var s = document.getElementsByTagName("script")[0];
|
||||||
|
s.parentNode.insertBefore(hm, s);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- This script installs service_worker.js to provide PWA functionality to
|
<!-- This script installs service_worker.js to provide PWA functionality to
|
||||||
|
|||||||
Reference in New Issue
Block a user