Compare commits

..

47 Commits
master ... dev

Author SHA1 Message Date
DebuggerX d3dc71714d feat: deb build script for arm64
3 years ago
DebuggerX 67246faace feat: deb build script for arm64
3 years ago
DebuggerX c2387714f2 fix: package permission issue when upload to UOS store.
3 years ago
DebuggerX e27671e247 fix: rename deb package's name for UOS store.
3 years ago
DebuggerX 6ebc05b376 fix: package name issue when upload to UOS store.
3 years ago
DebuggerX 0c87b714c4 feat: add startup bulletin; switch to edit mode when click text on md preview.
3 years ago
DebuggerX 49ec2a641e feat: configure sentry; add hasToken extension to context.
3 years ago
DebuggerX f760e7239c feat: add sentry.
3 years ago
DebuggerX b01b2ed194 fix: [Can't get package info after upgrgade to v1.3.1](https://github.com/fluttercommunity/plus_plugins/issues/747)
3 years ago
DebuggerX def733f7c5 fix: [Can't get package info after upgrgade to v1.3.1](https://github.com/fluttercommunity/plus_plugins/issues/747)
3 years ago
DebuggerX 775e7c8d3a feat: add email send report to log.
3 years ago
DebuggerX 1225eb7d63 feat: update api urls.
3 years ago
DebuggerX 14d1f2efd8 feat: update to production server url.
3 years ago
DebuggerX 7da5790eb2 fix: font issue on linux.
3 years ago
DebuggerX 8a0fc5e196 fix: font issue in md editor and preview; builtin command selector issue.
3 years ago
DebuggerX f7dfffd7a3 feat: use noto sans sc as default font; fix build script on arm64 linux.
3 years ago
DebuggerX 42c654bb00 feat: deb build script for uos.
3 years ago
DebuggerX b667823d27 feat: add baidu statistics code; use dialog instead of zenity for shell.
3 years ago
DebuggerX a6de246dd0 fix: bug fix for web.
3 years ago
DebuggerX bb9f00c89b
Update README.md
3 years ago
DebuggerX ec097dc16d
Update README.md
3 years ago
DebuggerX dd530fdf93 feat: add manual and changelog url.
3 years ago
DebuggerX c3ef400372 Merge remote-tracking branch 'origin/dev' into dev
3 years ago
DebuggerX 7ef959f9f7 fix: scheme storage on web.
3 years ago
DebuggerX f3acdb0b8e
Create LICENSE
3 years ago
DebuggerX e169078b6c fix: scheme storage on web.
3 years ago
DebuggerX 36f0d76552 fix: scheme storage on web.
3 years ago
DebuggerX d74a0ad167 fix: scheme storage on web.
3 years ago
DebuggerX 338c1c0b36 fix: scheme storage on web.
3 years ago
DebuggerX cacb14cac4 feat: UI text internationalization in MD editor.
3 years ago
DebuggerX a2553e1419 feat: UI text internationalization in MD editor.
3 years ago
DebuggerX d2e07cd89d feat: scheme apply feature.
3 years ago
DebuggerX 40eba0ddd7 feat: scheme download feature.
3 years ago
DebuggerX 049d9286f2 feat: scheme download feature.
3 years ago
DebuggerX a124f765cc feat: scheme download feature.
3 years ago
DebuggerX 44b5df3d24 feat: add retry to db connection when startup.
3 years ago
DebuggerX c567122d84 feat: fix some bugs.
3 years ago
DebuggerX a088f857e8 feat: fix upload error.
3 years ago
DebuggerX 50d3da98a5 feat: hash user password when sign up
3 years ago
DebuggerX faffbe1e00 feat: not allow upload scheme with name occupied.
3 years ago
DebuggerX da28e25377
Update README.md
3 years ago
DebuggerX 1109228933
Update README.md
3 years ago
DebuggerX 6ac2e7a031
Merge pull request #2 from debuggerx01/dev
3 years ago
DebuggerX b45fec2969
Update README.md
3 years ago
DebuggerX d548cac4dc feat: update README.
3 years ago
DebuggerX 25675aae8d feat: update README.
3 years ago
DebuggerX 5a5c9c11b8
Merge pull request #1 from debuggerx01/dev
3 years ago

1
.gitignore vendored

@ -1,2 +1,3 @@
.idea/
.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 桌面环境打造的触摸板手势管理工具
![logo](https://github.com/debuggerx01/dde_gesture_manager/blob/master/app/web/icons/Icon-192.png?raw=true)
专为 [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:

@ -8,12 +8,11 @@ import 'package:logging/logging.dart';
void main() async {
// Watch the config/ and web/ directories for changes, and hot-reload the server.
hierarchicalLoggingEnabled = true;
var hot = HotReloader(() async {
var logger = Logger.detached('dde_gesture_manager_api')
Logger.root
..level = Level.ALL
..onRecord.listen(prettyLog);
var logger = Logger.detached('dde_gesture_manager_api');
var app = Angel(logger: logger, reflector: MirrorsReflector());
await app.configure(configureServer);
return app;
@ -23,6 +22,5 @@ void main() async {
]);
var server = await hot.startServer('127.0.0.1', 3000);
print(
'dde_gesture_manager_api server listening at http://${server.address.address}:${server.port}');
print('dde_gesture_manager_api server listening at http://${server.address.address}:${server.port}');
}

@ -1,9 +1,15 @@
class Apis {
static const apiScheme = 'http';
static const apiHost = 'home.debuggerx.com';
static const apiPort = 30000;
static const apiHost = 'dgm_api.debuggerx.com';
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 auth = AuthApis();
@ -13,9 +19,9 @@ class Apis {
class AuthApis {
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();
}
@ -31,15 +37,17 @@ class SchemeApis {
String get upload => [path, 'upload'].joinPath();
String markAsShared({required StringParam schemeId}) => [path, 'mark_as_shared', schemeId].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 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 like({required StringParam schemeId, required StringParam isLike}) =>
[path, 'like', schemeId, isLike].joinPath();
String get userLikes => [path, 'user-likes'].joinPath();
}

@ -3,11 +3,32 @@ import 'dart:io';
import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_orm/angel3_orm.dart';
import 'package:angel3_orm_postgres/angel3_orm_postgres.dart';
import 'package:logging/logging.dart';
import 'package:postgres/postgres.dart';
const times = ['', 'st', 'nd', 'rd'];
const retryTimeOut = Duration(seconds: 30);
Future<void> configureServer(Angel app) async {
var connection = await connectToPostgres(app.configuration);
await connection.open();
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();
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 executor = PostgreSqlExecutor(connection, logger: logger);

@ -111,6 +111,9 @@ abstract class _SchemeForDownload {
@SerializableField(isNullable: false)
String? get name;
@SerializableField(defaultValue: false, isNullable: false)
bool? get shared;
@Column(type: ColumnType.text)
String? description;
@ -124,6 +127,7 @@ SchemeForDownload transSchemeForDownload(Scheme scheme) => SchemeForDownload(
name: scheme.name,
description: scheme.description,
gestures: scheme.gestures,
shared: scheme.shared,
);
@serializable
@ -172,10 +176,10 @@ abstract class _MarketSchemeTransMetaData {
}
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,
);
id: scheme.id,
description: scheme.description,
uuid: scheme.uuid,
name: scheme.name,
likes: scheme.metadata?['likes'] ?? 0,
downloads: scheme.metadata?['downloads'] ?? 0,
);

@ -16,7 +16,7 @@ abstract class _User extends BaseModel {
@SerializableField(isNullable: false)
String? get email;
@Column(isNullable: false, length: 32)
@Column(isNullable: false, length: 64)
@SerializableField(isNullable: true, exclude: true)
String? get password;

@ -6,6 +6,7 @@ import 'package:angel3_framework/angel3_framework.dart';
import 'package:dde_gesture_manager_api/apis.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/smtp_server.dart';
import 'package:uuid/uuid.dart';
@ -45,7 +46,9 @@ Future configureServer(Angel app) async {
},
);
send(message, smtpServer);
SendReport report = await send(message, smtpServer);
Logger('auth_controller').info(report);
return res.notFound();
} else if (user.value.password != userParams.password) {
return res.unauthorized();

@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:io';
import 'package:angel3_framework/angel3_framework.dart';
import 'package:dde_gesture_manager_api/apis.dart';
@ -21,13 +22,21 @@ Future configureServer(Angel app) async {
try {
var scheme = SchemeSerializer.fromMap(req.bodyAsMap);
var schemeQuery = SchemeQuery();
schemeQuery.where!.uuid.equals(scheme.uuid!);
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;

@ -1,5 +1,6 @@
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 'controllers/auth_controllers.dart' as auth_controllers;
import 'controllers/system_controllers.dart' as system_controllers;
@ -28,6 +29,9 @@ AngelConfigurer configureServer(FileSystem fileSystem) {
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.
app.fallback((req, res) => throw AngelHttpException.notFound());

@ -12,6 +12,7 @@ dependencies:
angel3_orm: ^4.0.6
angel3_orm_postgres: ^3.3.0
angel3_serialize: ^4.1.0
angel3_static: ^4.1.0
angel3_production: ^3.1.2
belatuk_pretty_logging: ^4.0.0
optional: ^6.0.0

@ -1,10 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<head>
<meta charset="utf-8"/>
<title>{% block title %}{% endblock %}</title>
</head>
<body>
{% block body %}{% endblock %}
</body>
<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>
<body>
{% block body %}{% endblock %}
</body>
</html>

@ -1,27 +1,9 @@
html, body {
height: 100%;
}
body {
margin: 0;
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;
margin: 0;
}
.title {
font-size: 96px;
body {
margin: .5em;
}

3
app/.gitignore vendored

@ -45,3 +45,6 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release
/deb_builder/
*.deb

@ -287,6 +287,8 @@ class _CherryToastState extends State<CherryToast> with TickerProviderStateMixin
late Animation<Offset> offsetAnimation;
late AnimationController slideController;
late BoxDecoration toastDecoration;
bool _dismissed = false;
@override
void initState() {
@ -311,7 +313,10 @@ class _CherryToastState extends State<CherryToast> with TickerProviderStateMixin
Timer(this.widget.toastDuration, () {
slideController.reverse();
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: () {
slideController.reverse();
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),

@ -19,6 +19,7 @@ class MarkdownBuilder implements md.NodeVisitor {
this.defaultTextStyle, {
this.tagTextStyle = defaultTagTextStyle,
required this.onCodeCopied,
this.richTap,
});
final _widgets = <Widget>[];
@ -30,6 +31,7 @@ class MarkdownBuilder implements md.NodeVisitor {
final BuildContext context;
final LinkTap linkTap;
final VoidCallback? richTap;
final WidgetImage widgetImage;
final double maxWidth;
final Function onCodeCopied;
@ -125,6 +127,7 @@ class MarkdownBuilder implements md.NodeVisitor {
children: last.textSpans,
style: last.textStyle,
),
onTap: richTap,
);
}
}

@ -14,6 +14,7 @@ class Markdown extends StatefulWidget {
required this.onCodeCopied,
this.maxWidth,
this.textStyle,
this.richTap,
}) : super(key: key);
final String data;
@ -28,6 +29,8 @@ class Markdown extends StatefulWidget {
final Function onCodeCopied;
final VoidCallback? richTap;
@override
MarkdownState createState() => MarkdownState();
}
@ -62,6 +65,7 @@ class MarkdownState extends State<Markdown> {
widget.maxWidth ?? MediaQuery.of(context).size.width,
widget.textStyle ?? defaultTextStyle(context),
onCodeCopied: widget.onCodeCopied,
richTap: widget.richTap,
).build(nodes);
}
}

@ -2,3 +2,4 @@ library markdown_editor;
export 'package:markdown_editor_ot/src/editor.dart';
export 'package:markdown_editor_ot/src/preview.dart';
export 'package:markdown_editor_ot/src/action.dart';

@ -11,6 +11,7 @@ class ActionImage extends StatefulWidget {
this.imageSelect,
required this.color,
this.getCursorPosition,
required this.message,
}) : super(key: key);
final ActionType type;
@ -20,6 +21,8 @@ class ActionImage extends StatefulWidget {
final Color? color;
final String? message;
@override
ActionImageState createState() => ActionImageState();
}
@ -60,7 +63,7 @@ class ActionImageState extends State<ActionImage> {
Widget build(BuildContext context) {
return Tooltip(
preferBelow: false,
message: _defaultImageAttributes
message: widget.message ?? _defaultImageAttributes
.firstWhere((img) => img.type == widget.type)
.tip,
child: IconButton(
@ -243,7 +246,6 @@ enum ActionType {
fontBold,
fontItalic,
fontStrikethrough,
fontDeleteLine,
textQuote,
list,
h1,

@ -24,6 +24,7 @@ class MdEditor extends StatefulWidget {
this.appendBottomWidget,
this.splitWidget,
this.textFocusNode,
required this.actionMessages,
required this.onComplete,
}) : super(key: key);
@ -51,6 +52,8 @@ class MdEditor extends StatefulWidget {
final OnComplete onComplete;
final Map<ActionType, String> actionMessages;
@override
State<StatefulWidget> createState() => MdEditorState();
}
@ -186,6 +189,7 @@ class MdEditorState extends State<MdEditor> with AutomaticKeepAliveClientMixin {
widgets.add(ActionImage(
type: ActionType.done,
message: widget.actionMessages[ActionType.done],
color: widget.actionIconColor,
tap: (t, s, i, [p]) {
widget.onComplete(getText());
@ -194,6 +198,7 @@ class MdEditorState extends State<MdEditor> with AutomaticKeepAliveClientMixin {
widgets.add(ActionImage(
type: ActionType.undo,
message: widget.actionMessages[ActionType.undo],
color: widget.actionIconColor,
tap: (t, s, i, [p]) {
_editPerform.undo();
@ -201,6 +206,7 @@ class MdEditorState extends State<MdEditor> with AutomaticKeepAliveClientMixin {
));
widgets.add(ActionImage(
type: ActionType.redo,
message: widget.actionMessages[ActionType.redo],
color: widget.actionIconColor,
tap: (t, s, i, [p]) {
_editPerform.redo();
@ -237,6 +243,7 @@ class MdEditorState extends State<MdEditor> with AutomaticKeepAliveClientMixin {
sortValue: getSortValue(ActionType.image),
widget: ActionImage(
type: ActionType.image,
message: widget.actionMessages[ActionType.image],
color: widget.actionIconColor,
tap: _disposeText,
imageSelect: widget.imageSelect,
@ -247,6 +254,7 @@ class MdEditorState extends State<MdEditor> with AutomaticKeepAliveClientMixin {
sortValue: getSortValue(ActionType.link),
widget: ActionImage(
type: ActionType.link,
message: widget.actionMessages[ActionType.link],
color: widget.actionIconColor,
tap: _disposeText,
),
@ -255,6 +263,7 @@ class MdEditorState extends State<MdEditor> with AutomaticKeepAliveClientMixin {
sortValue: getSortValue(ActionType.fontBold),
widget: ActionImage(
type: ActionType.fontBold,
message: widget.actionMessages[ActionType.fontBold],
color: widget.actionIconColor,
tap: _disposeText,
),
@ -263,6 +272,7 @@ class MdEditorState extends State<MdEditor> with AutomaticKeepAliveClientMixin {
sortValue: getSortValue(ActionType.fontItalic),
widget: ActionImage(
type: ActionType.fontItalic,
message: widget.actionMessages[ActionType.fontItalic],
color: widget.actionIconColor,
tap: _disposeText,
),
@ -271,6 +281,7 @@ class MdEditorState extends State<MdEditor> with AutomaticKeepAliveClientMixin {
sortValue: getSortValue(ActionType.fontStrikethrough),
widget: ActionImage(
type: ActionType.fontStrikethrough,
message: widget.actionMessages[ActionType.fontStrikethrough],
color: widget.actionIconColor,
tap: _disposeText,
),
@ -279,6 +290,7 @@ class MdEditorState extends State<MdEditor> with AutomaticKeepAliveClientMixin {
sortValue: getSortValue(ActionType.textQuote),
widget: ActionImage(
type: ActionType.textQuote,
message: widget.actionMessages[ActionType.textQuote],
color: widget.actionIconColor,
tap: _disposeText,
),
@ -287,6 +299,7 @@ class MdEditorState extends State<MdEditor> with AutomaticKeepAliveClientMixin {
sortValue: getSortValue(ActionType.list),
widget: ActionImage(
type: ActionType.list,
message: widget.actionMessages[ActionType.list],
color: widget.actionIconColor,
tap: _disposeText,
),
@ -295,6 +308,7 @@ class MdEditorState extends State<MdEditor> with AutomaticKeepAliveClientMixin {
sortValue: getSortValue(ActionType.h4),
widget: ActionImage(
type: ActionType.h4,
message: widget.actionMessages[ActionType.h4],
color: widget.actionIconColor,
tap: _disposeText,
),
@ -303,6 +317,7 @@ class MdEditorState extends State<MdEditor> with AutomaticKeepAliveClientMixin {
sortValue: getSortValue(ActionType.h5),
widget: ActionImage(
type: ActionType.h5,
message: widget.actionMessages[ActionType.h5],
color: widget.actionIconColor,
tap: _disposeText,
),
@ -311,6 +326,7 @@ class MdEditorState extends State<MdEditor> with AutomaticKeepAliveClientMixin {
sortValue: getSortValue(ActionType.h1),
widget: ActionImage(
type: ActionType.h1,
message: widget.actionMessages[ActionType.h1],
color: widget.actionIconColor,
tap: _disposeText,
),
@ -319,6 +335,7 @@ class MdEditorState extends State<MdEditor> with AutomaticKeepAliveClientMixin {
sortValue: getSortValue(ActionType.h2),
widget: ActionImage(
type: ActionType.h2,
message: widget.actionMessages[ActionType.h2],
color: widget.actionIconColor,
tap: _disposeText,
),
@ -327,6 +344,7 @@ class MdEditorState extends State<MdEditor> with AutomaticKeepAliveClientMixin {
sortValue: getSortValue(ActionType.h3),
widget: ActionImage(
type: ActionType.h3,
message: widget.actionMessages[ActionType.h3],
color: widget.actionIconColor,
tap: _disposeText,
),

@ -11,6 +11,7 @@ class MdPreview extends StatefulWidget {
required this.widgetImage,
required this.onCodeCopied,
this.textStyle,
this.richTap,
}) : super(key: key);
final String text;
@ -24,6 +25,8 @@ class MdPreview extends StatefulWidget {
/// If [onTapLink] is null,it will open the link with your default browser.
final TapLinkCallback? onTapLink;
final VoidCallback? richTap;
@override
State<StatefulWidget> createState() => MdPreviewState();
}
@ -50,6 +53,7 @@ class MdPreviewState extends State<MdPreview>
image: widget.widgetImage,
textStyle: widget.textStyle,
onCodeCopied: widget.onCodeCopied,
richTap: widget.richTap,
);
},
),

@ -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 "打包完成!"

@ -8,7 +8,10 @@ echo "Downloading WASM from $wasmLocation"
curl -o build/web/canvaskit.js "$wasmLocation/canvaskit.js"
curl -o build/web/canvaskit.wasm "$wasmLocation/canvaskit.wasm"
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+Color+Emoji+Compat!./assets/assets/css/Noto-Color-Emoji-Compat.css!" \
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 构建。

@ -19,6 +19,18 @@ const double defaultBorderRadius = 8;
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 = [
'ShowWorkspace',
'Handle4Or5FingersSwipeUp',
@ -40,3 +52,9 @@ enum PanelType {
local_manager,
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 loginEmail = 'USER_LOGIN_EMAIL';
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:dde_gesture_manager/extensions.dart';
extension ContextExtension on BuildContext {
ThemeData get t => Theme.of(this);
NavigatorState get n => Navigator.of(this);
bool get hasToken => this.read<ConfigsProvider>().accessToken.notNull;
bool get watchHasToken => this.watch<ConfigsProvider>().accessToken.notNull;
}

@ -1,6 +1,7 @@
import 'dart:convert';
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/extensions.dart';
import 'package:dde_gesture_manager/models/scheme.dart' as AppScheme;
@ -51,6 +52,8 @@ class Api {
if (builder is GetStatusCodeFunc) return builder({"statusCode": resp.statusCode});
T? res;
try {
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) {
@ -60,6 +63,8 @@ class Api {
return res;
};
static final _fullPathRegExp = RegExp('http(s?)://');
static Future<T?> _get<T>(
String path,
BeanBuilder<T> builder, {
@ -69,13 +74,15 @@ class Api {
}) =>
http
.get(
Uri(
scheme: Apis.apiScheme,
host: Apis.apiHost,
port: Apis.apiPort,
queryParameters: queryParams,
path: path,
),
path.startsWith(_fullPathRegExp)
? Uri.parse(path)
: Uri(
scheme: Apis.apiScheme,
host: Apis.apiHost,
port: Apis.apiPort,
queryParameters: queryParams,
path: path,
),
headers: <String, String>{
HttpHeaders.contentTypeHeader: ContentType.json.toString(),
}..addAll(
@ -100,12 +107,14 @@ class Api {
}) =>
http
.post(
Uri(
scheme: Apis.apiScheme,
host: Apis.apiHost,
port: Apis.apiPort,
path: path,
),
path.startsWith(_fullPathRegExp)
? Uri.parse(path)
: Uri(
scheme: Apis.apiScheme,
host: Apis.apiHost,
port: Apis.apiPort,
path: path,
),
body: jsonEncode(body),
headers: <String, String>{
HttpHeaders.contentTypeHeader: ContentType.json.toString(),
@ -131,7 +140,7 @@ class Api {
LoginSuccessSerializer.fromMap,
body: {
UserFields.email: email,
UserFields.password: password,
UserFields.password: User(email: email, password: password).secret('dgm_password'),
},
ignoreToken: true,
);
@ -146,7 +155,7 @@ class Api {
static Future<bool> checkAuthStatus() => _get<int>(Apis.auth.status, getStatusCodeFunc, ignoreErrorHandle: true)
.then((value) => value == HttpStatus.noContent);
static Future<bool> uploadScheme({required AppScheme.Scheme scheme, required bool share}) => _post(
static Future<UploadRespStatus> uploadScheme({required AppScheme.Scheme scheme, required bool share}) => _post(
Apis.scheme.upload,
getStatusCodeFunc,
body: SchemeSerializer.toMap(
@ -158,7 +167,17 @@ class Api {
shared: share,
),
),
).then((value) => value == HttpStatus.noContent);
).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));
@ -186,6 +205,26 @@ class Api {
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 {

@ -11,8 +11,11 @@ import 'package:dde_gesture_manager/themes/dark.dart';
import 'package:dde_gesture_manager/themes/light.dart';
import 'package:dde_gesture_manager/utils/helper.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:sentry_flutter/sentry_flutter.dart';
import 'pages/home.dart';
@ -21,13 +24,23 @@ Future<void> main() async {
EasyLocalization.logger.enableLevels = [];
await EasyLocalization.ensureInitialized();
await initConfigs();
runApp(EasyLocalization(
supportedLocales: supportedLocales,
fallbackLocale: zh_CN,
path: 'resources/langs',
assetLoader: CodegenLoader(),
child: MyApp(),
));
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,
fallbackLocale: zh_CN,
path: 'resources/langs',
assetLoader: CodegenLoader(),
child: MyApp(),
)),
);
}
class MyApp extends StatelessWidget {
@ -73,6 +86,11 @@ class MyApp extends StatelessWidget {
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();
}),
@ -88,7 +106,7 @@ class MyApp extends StatelessWidget {
void _checkAuthStatus(BuildContext context) {
if (H().lastCheckAuthStatusTime != null &&
H().lastCheckAuthStatusTime!.difference(DateTime.now()) < Duration(minutes: 10)) return;
if (context.read<ConfigsProvider>().accessToken.notNull) {
if (context.hasToken) {
Api.checkAuthStatus().then((value) {
if (!value) context.read<ConfigsProvider>().setProps(email: '', accessToken: '');
});
@ -96,3 +114,14 @@ void _checkAuthStatus(BuildContext context) {
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/extensions.dart';
import 'package:dde_gesture_manager/utils/helper.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
enum BrightnessMode {
system,
@ -45,9 +46,12 @@ class Configs {
set email(String? emailAddress) {
_email = emailAddress;
if (emailAddress.notNull)
if (emailAddress.notNull) {
H().sp.updateString(SPKeys.loginEmail, emailAddress!);
else
Sentry.configureScope(
(scope) => scope.user = SentryUser(email: emailAddress),
);
} else
H().sp.remove(SPKeys.loginEmail);
}

@ -19,28 +19,26 @@ class LocalSchemes implements LocalSchemesInterface<LocalSchemeEntryWeb> {
@override
Future<List<LocalSchemeEntryWeb>> get schemeEntries async {
return window.localStorage.keys
.map<LocalSchemeEntryWeb?>((key) {
if (key.startsWith('schemes.')) {
LocalSchemeEntryWeb? entry;
try {
var content = window.localStorage[key] ?? '';
var schemeJson = json.decode(content);
entry = LocalSchemeEntryWeb(
path: key,
scheme: Scheme.parse(schemeJson),
lastModifyTime: DateTime.parse(schemeJson['modified_at']),
);
} catch (e) {
e.sout();
}
return entry;
}
return null;
})
.where((e) => e != null)
.cast<LocalSchemeEntryWeb>()
.toList();
List<LocalSchemeEntryWeb> _localeSchemes = [];
for (var key in window.localStorage.keys) {
if (key.startsWith('schemes.')) {
var content = window.localStorage[key] ?? '';
var schemeJson;
try {
schemeJson = json.decode(content);
} catch (e) {
e.sout();
}
if (schemeJson != null) {
_localeSchemes.add(LocalSchemeEntryWeb(
path: key,
scheme: Scheme.parse(schemeJson),
lastModifyTime: DateTime.parse(schemeJson['modified_at']),
));
}
}
}
return Future.value(_localeSchemes);
}
@ProviderModelProp()
@ -49,7 +47,7 @@ class LocalSchemes implements LocalSchemesInterface<LocalSchemeEntryWeb> {
@override
Future<LocalSchemeEntry> create() => Future.value(
LocalSchemeEntryWeb(
path: Uuid().v1(),
path: 'schemes.${Uuid().v1()}',
scheme: Scheme.create(),
lastModifyTime: DateTime.now(),
),
@ -84,7 +82,9 @@ class LocalSchemeEntryWeb implements LocalSchemeEntry {
@override
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.setProps(schemes: [...provider.schemes!]..sort());
}

@ -54,7 +54,7 @@ class _ContentState extends State<Content> {
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
LocalManager(),
LocalManager(key: H.localManagerKey),
GestureEditor(),
MarketOrMe(),
],

@ -2,7 +2,6 @@ import 'package:adaptive_scrollbar/adaptive_scrollbar.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/content_layout.provider.dart';
import 'package:dde_gesture_manager/models/local_schemes_provider.dart';
import 'package:dde_gesture_manager/models/scheme.dart';
@ -301,7 +300,11 @@ class GestureEditor extends StatelessWidget {
child: DButton.upload(
enabled: schemeProvider.readOnly == false,
onTap: () async {
if (context.read<ConfigsProvider>().accessToken.isNull) {
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(),
@ -326,7 +329,7 @@ class GestureEditor extends StatelessWidget {
if (_share != null) {
Api.uploadScheme(scheme: schemeProvider, share: _share).then((value) {
if (value) {
if (value == UploadRespStatus.done) {
Notificator.success(context, title: LocaleKeys.info_upload_success.tr());
var localSchemesProvider = context.read<LocalSchemesProvider>();
var localSchemeEntry = localSchemesProvider.schemes!
@ -336,6 +339,12 @@ class GestureEditor extends StatelessWidget {
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());
}
@ -595,13 +604,11 @@ Widget _buildCommandCellsEditing(BuildContext context) {
('${LocaleKeys.built_in_commands}.$e').tr(),
textScaleFactor: .8,
),
value: ('${LocaleKeys.built_in_commands}.$e').tr(),
value: e,
),
)
.toList(),
value:
('${LocaleKeys.built_in_commands}.${(builtInCommands.contains(gesture.command) ? gesture.command : builtInCommands.first)!}')
.tr(),
value: (builtInCommands.contains(gesture.command) ? gesture.command : builtInCommands.first)!,
onChanged: (value) => context.read<GesturePropProvider>().setProps(
command: value,
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.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_api/models.dart' show SchemeForDownload;
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:uuid/uuid.dart';
class LocalManager extends StatefulWidget {
@ -19,10 +22,10 @@ class LocalManager extends StatefulWidget {
}) : super(key: key);
@override
State<LocalManager> createState() => _LocalManagerState();
State<LocalManager> createState() => LocalManagerState();
}
class _LocalManagerState extends State<LocalManager> {
class LocalManagerState extends State<LocalManager> {
late ScrollController _scrollController;
String? _hoveringItemPath;
late String _selectedItemPath;
@ -69,6 +72,28 @@ class _LocalManagerState extends State<LocalManager> {
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
Widget build(BuildContext context) {
var isOpen = context.watch<ContentLayoutProvider>().localManagerOpened == true;
@ -192,15 +217,7 @@ class _LocalManagerState extends State<LocalManager> {
DButton.add(
enabled: true,
onTap: () async {
var localSchemesProvider = context.read<LocalSchemesProvider>();
var newSchemes = [...?localSchemesProvider.schemes];
var newEntry = await localSchemesProvider.create();
newSchemes.add(newEntry);
localSchemesProvider.setProps(schemes: newSchemes..sort());
setState(() {
_selectedItemPath = newEntry.path;
});
_handleItemClick(context, newEntry);
await addLocalScheme(context);
},
),
DButton.delete(
@ -242,10 +259,11 @@ class _LocalManagerState extends State<LocalManager> {
DButton.apply(
enabled: true,
onTap: () {
var appliedId =
localSchemes.firstWhere((ele) => ele.path == _selectedItemPath).scheme.id!;
appliedId.sout();
context.read<ConfigsProvider>().setProps(appliedSchemeId: appliedId);
var appliedScheme =
localSchemes.firstWhere((ele) => ele.path == _selectedItemPath).scheme;
context.read<ConfigsProvider>().setProps(appliedSchemeId: appliedScheme.id);
SchemeApplyUtil().apply(context, appliedScheme);
Sentry.captureMessage('Scheme applied: [${appliedScheme.name}](${appliedScheme.id})');
},
),
]

@ -1,6 +1,5 @@
import 'package:dde_gesture_manager/constants/constants.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/widgets/dde_button.dart';
import 'package:dde_gesture_manager/widgets/login.dart';
@ -79,11 +78,11 @@ class MarketOrMe extends StatelessWidget {
);
}
Widget buildMeContent(BuildContext context) {
var accessToken = context.watch<ConfigsProvider>().accessToken;
if (accessToken.isNull) return LoginWidget();
return Expanded(child: MeWidget());
}
Widget buildMeContent(BuildContext context) => context.watchHasToken
? Expanded(
child: MeWidget(),
)
: LoginWidget();
Widget buildMarketContent(BuildContext context) => Expanded(child: MarketWidget());
}

@ -1,7 +1,13 @@
import 'package:flutter/material.dart';
import 'package:dde_gesture_manager/constants/constants.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
final _darkTheme = ThemeData(
brightness: Brightness.dark,
fontFamily: kIsWeb ? null : defaultFontFamily,
);
var darkTheme = ThemeData.dark().copyWith(
var darkTheme = _darkTheme.copyWith(
primaryColor: Colors.grey,
scaffoldBackgroundColor: Color(0xff252525),
backgroundColor: Color(0xff282828),
@ -9,23 +15,26 @@ var darkTheme = ThemeData.dark().copyWith(
color: Color(0xffc0c6d4),
),
dividerColor: Color(0xfff3f3f3),
textTheme: ThemeData.dark().textTheme.copyWith(
textTheme: _darkTheme.textTheme.copyWith(
headline1: TextStyle(
color: Color(0xffc0c6d4),
fontFamily: kIsWeb ? null : defaultFontFamily,
),
bodyText2: TextStyle(
color: Color(0xffc0c6d4),
fontFamily: kIsWeb ? null : defaultFontFamily,
),
),
popupMenuTheme: ThemeData.dark().popupMenuTheme.copyWith(
popupMenuTheme: _darkTheme.popupMenuTheme.copyWith(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(defaultBorderRadius),
),
),
dialogBackgroundColor: Color(0xff202020),
tooltipTheme: ThemeData.dark().tooltipTheme.copyWith(
tooltipTheme: _darkTheme.tooltipTheme.copyWith(
textStyle: TextStyle(
color: Colors.grey,
fontFamily: kIsWeb ? null : defaultFontFamily,
),
padding: EdgeInsets.symmetric(horizontal: 3, vertical: 2),
decoration: BoxDecoration(

@ -1,31 +1,40 @@
import 'package:dde_gesture_manager/constants/constants.dart';
import 'package:flutter/foundation.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,
scaffoldBackgroundColor: Color(0xfff8f8f8),
backgroundColor: Color(0xffffffff),
iconTheme: IconThemeData(
color: Color(0xff414d68),
),
dividerColor: Color(0xfff3f3f3),
textTheme: ThemeData.light().textTheme.copyWith(
dividerColor: Colors.grey.shade600,
textTheme: _lightTheme.textTheme.copyWith(
headline1: TextStyle(
color: Color(0xff414d68),
fontFamily: kIsWeb ? null : defaultFontFamily,
),
bodyText2: TextStyle(
color: Color(0xff414d68),
fontFamily: kIsWeb ? null : defaultFontFamily,
),
),
popupMenuTheme: ThemeData.dark().popupMenuTheme.copyWith(
popupMenuTheme: _lightTheme.popupMenuTheme.copyWith(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(defaultBorderRadius),
),
),
dialogBackgroundColor: Color(0xfffefefe),
tooltipTheme: ThemeData.dark().tooltipTheme.copyWith(
tooltipTheme: _lightTheme.tooltipTheme.copyWith(
textStyle: TextStyle(
color: Colors.grey.shade600,
fontFamily: kIsWeb ? null : defaultFontFamily,
),
padding: EdgeInsets.symmetric(horizontal: 3, vertical: 2),
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/extensions.dart';
import 'package:dde_gesture_manager/models/content_layout.provider.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:shared_preferences/shared_preferences.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:uuid/uuid.dart';
extension EnumByName<T extends Enum> on Iterable<T> {
@ -29,13 +34,15 @@ class H {
initSharedPreference() async {
_sp = await SharedPreferences.getInstance();
}
late BuildContext _topContext;
BuildContext get topContext => _topContext;
DateTime? lastCheckAuthStatusTime;
static final localManagerKey = GlobalKey<LocalManagerState>();
initTopContext(BuildContext context) {
_topContext = context;
}
@ -114,6 +121,34 @@ class H {
gestureProp.direction = tree.availableNode.availableNode.availableNode.direction;
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 {

@ -24,7 +24,7 @@ class DButton extends StatefulWidget {
factory DButton.add({
Key? key,
required enabled,
required bool enabled,
GestureTapCallback? onTap,
height = defaultButtonHeight * .7,
width = defaultButtonHeight * .7,
@ -41,7 +41,7 @@ class DButton extends StatefulWidget {
factory DButton.delete({
Key? key,
required enabled,
required bool enabled,
GestureTapCallback? onTap,
height = defaultButtonHeight * .7,
width = defaultButtonHeight * .7,
@ -58,7 +58,7 @@ class DButton extends StatefulWidget {
factory DButton.apply({
Key? key,
required enabled,
required bool enabled,
GestureTapCallback? onTap,
height = defaultButtonHeight * .7,
width = defaultButtonHeight * .7,
@ -75,7 +75,7 @@ class DButton extends StatefulWidget {
factory DButton.duplicate({
Key? key,
required enabled,
required bool enabled,
GestureTapCallback? onTap,
height = defaultButtonHeight * .7,
width = defaultButtonHeight * .7,
@ -92,7 +92,7 @@ class DButton extends StatefulWidget {
factory DButton.paste({
Key? key,
required enabled,
required bool enabled,
GestureTapCallback? onTap,
height = defaultButtonHeight * .7,
width = defaultButtonHeight * .7,
@ -109,7 +109,7 @@ class DButton extends StatefulWidget {
factory DButton.logout({
Key? key,
required enabled,
required bool enabled,
GestureTapCallback? onTap,
height = defaultButtonHeight,
width = defaultButtonHeight,
@ -126,7 +126,7 @@ class DButton extends StatefulWidget {
factory DButton.upload({
Key? key,
required enabled,
required bool enabled,
GestureTapCallback? onTap,
height = defaultButtonHeight,
width = defaultButtonHeight,
@ -143,7 +143,7 @@ class DButton extends StatefulWidget {
factory DButton.download({
Key? key,
required enabled,
required bool enabled,
GestureTapCallback? onTap,
height = defaultButtonHeight * .7,
width = defaultButtonHeight * .7,
@ -160,7 +160,7 @@ class DButton extends StatefulWidget {
factory DButton.share({
Key? key,
required enabled,
required bool enabled,
GestureTapCallback? onTap,
height = defaultButtonHeight * .7,
width = defaultButtonHeight * .7,
@ -177,7 +177,7 @@ class DButton extends StatefulWidget {
factory DButton.like({
Key? key,
required enabled,
required bool enabled,
GestureTapCallback? onTap,
height = defaultButtonHeight * .7,
width = defaultButtonHeight * .7,

@ -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/extensions.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:flutter/material.dart';
import 'package:markdown_editor_ot/markdown_editor.dart';
import 'package:url_launcher/url_launcher.dart';
class DMarkdownField extends StatefulWidget {
const DMarkdownField({
@ -36,14 +36,6 @@ class _DMarkdownFieldState extends State<DMarkdownField> {
super.initState();
}
_launchURL(String url) async {
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
@override
void didUpdateWidget(covariant DMarkdownField oldWidget) {
if (oldWidget.initText != widget.initText) {
@ -54,6 +46,14 @@ class _DMarkdownFieldState extends State<DMarkdownField> {
super.didUpdateWidget(oldWidget);
}
VoidCallback? get _onMdPreviewTap => widget.readOnly
? null
: () {
setState(() {
_previewText = null;
});
};
@override
Widget build(BuildContext context) {
return Focus(
@ -70,19 +70,15 @@ class _DMarkdownFieldState extends State<DMarkdownField> {
),
child: isPreview
? GestureDetector(
onTap: widget.readOnly
? null
: () {
setState(() {
_previewText = null;
});
},
onTap: _onMdPreviewTap,
child: MouseRegion(
cursor: widget.readOnly ? SystemMouseCursors.basic : SystemMouseCursors.text,
child: MdPreview(
text: _previewText ?? '',
padding: EdgeInsets.only(left: 15),
onTapLink: _launchURL,
onTapLink: H.launchURL,
richTap: _onMdPreviewTap,
textStyle: context.t.textTheme.bodyText2,
onCodeCopied: () {
Notificator.success(
context,
@ -104,6 +100,7 @@ class _DMarkdownFieldState extends State<DMarkdownField> {
)
: MdEditor(
initText: widget.initText,
hintText: LocaleKeys.md_editor_init_text.tr(),
textFocusNode: _focusNode,
padding: EdgeInsets.symmetric(horizontal: 5),
onComplete: (content) {
@ -114,6 +111,23 @@ class _DMarkdownFieldState extends State<DMarkdownField> {
else
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_api/apis.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
class HelpButton extends StatelessWidget {
const HelpButton({Key? key}) : super(key: key);
@ -7,7 +10,11 @@ class HelpButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {},
onTap: () async {
if (await canLaunch(Apis.appManualUrl(kIsWeb))) {
await launch(Apis.appManualUrl(kIsWeb));
}
},
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: Tooltip(

@ -2,8 +2,9 @@ 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/configs.provider.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';
@ -33,18 +34,18 @@ class _MarketWidgetState extends State<MarketWidget> {
MarketSortType _type = MarketSortType.recommend;
String? _selected;
String? _hovering;
int _refreshKey = 0;
List<int> _likedSchemes = [];
@override
void initState() {
super.initState();
Api.userLikes().then((value) {
if (mounted && value != null)
setState(() {
_likedSchemes = value;
});
});
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(() {
@ -131,17 +132,17 @@ class _MarketWidgetState extends State<MarketWidget> {
),
),
Expanded(
child: Container(
decoration: BoxDecoration(
border: Border.all(
width: .3,
color: context.t.dividerColor,
),
borderRadius: BorderRadius.circular(defaultBorderRadius),
),
child: Column(
children: [
Flexible(
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(
@ -221,8 +222,19 @@ class _MarketWidgetState extends State<MarketWidget> {
),
),
),
Divider(thickness: .5),
Flexible(
),
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(
@ -236,12 +248,14 @@ class _MarketWidgetState extends State<MarketWidget> {
),
errorWidget: (context, url, error) => const Icon(Icons.error),
),
onTapLink: H.launchURL,
textStyle: context.t.textTheme.bodyText2,
onCodeCopied: () {},
),
),
),
],
),
),
],
),
),
Padding(
@ -250,7 +264,7 @@ class _MarketWidgetState extends State<MarketWidget> {
mainAxisAlignment: MainAxisAlignment.end,
children: [
DButton.like(
enabled: context.watch<ConfigsProvider>().accessToken.notNull,
enabled: context.watchHasToken,
onTap: () {
bool liked = _likedSchemes.contains(currentSelectedScheme!.id!);
Api.likeScheme(schemeId: currentSelectedScheme.uuid!, isLike: !liked).then((value) {
@ -269,14 +283,15 @@ class _MarketWidgetState extends State<MarketWidget> {
},
),
DButton.download(
enabled: true,
enabled: (context.watch<LocalSchemesProvider>().schemes ?? []).every((e) => e.scheme.id != _selected),
onTap: () {
Api.downloadScheme(schemeId: currentSelectedScheme!.uuid!).then((value) {
/// todo:
value.sout();
setState(() {
currentSelectedScheme.downloads = currentSelectedScheme.downloads! + 1;
});
if (value != null) {
H.handleDownloadScheme(context, value);
setState(() {
currentSelectedScheme.downloads = currentSelectedScheme.downloads! + 1;
});
}
});
},
),

@ -5,8 +5,10 @@ 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';
@ -141,17 +143,17 @@ class _MeWidgetState extends State<MeWidget> {
),
),
Expanded(
child: Container(
decoration: BoxDecoration(
border: Border.all(
width: .3,
color: context.t.dividerColor,
),
borderRadius: BorderRadius.circular(defaultBorderRadius),
),
child: Column(
children: [
Flexible(
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(
@ -225,12 +227,25 @@ class _MeWidgetState extends State<MeWidget> {
),
),
),
Divider(thickness: .5),
Flexible(
),
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(
@ -243,9 +258,9 @@ class _MeWidgetState extends State<MeWidget> {
onCodeCopied: () {},
),
),
)
],
),
),
)
],
),
),
Padding(
@ -281,14 +296,15 @@ class _MeWidgetState extends State<MeWidget> {
},
),
DButton.download(
enabled: true,
enabled: (context.watch<LocalSchemesProvider>().schemes ?? []).every((e) => e.scheme.id != _selected),
onTap: () {
Api.downloadScheme(schemeId: currentSelectedScheme!.uuid!).then((value) {
/// todo:
value.sout();
context
.read<SchemeListRefreshKeyProvider>()
.setProps(refreshKey: DateTime.now().millisecondsSinceEpoch);
if (value != null) {
H.handleDownloadScheme(context, value);
context
.read<SchemeListRefreshKeyProvider>()
.setProps(refreshKey: DateTime.now().millisecondsSinceEpoch);
}
});
},
),

@ -23,8 +23,7 @@ class TableCellShortcutListener extends StatefulWidget {
class _TableCellShortcutListenerState extends State<TableCellShortcutListener> {
List<KeyNames> _shortcut = [];
bool inputMode = false;
FocusNode _focusNode = FocusNode();
final FocusNode _focusNode = FocusNode();
_handleFocusChange() {
if (!_focusNode.hasFocus) {
@ -43,6 +42,7 @@ class _TableCellShortcutListenerState extends State<TableCellShortcutListener> {
@override
void initState() {
super.initState();
var __shortcut = widget.initShortcut.split('+');
__shortcut.forEach((name) {
var keyNames = getPhysicalKeyNamesByRealName(name);
@ -50,7 +50,6 @@ class _TableCellShortcutListenerState extends State<TableCellShortcutListener> {
});
_shortcut.sort();
_focusNode.addListener(_handleFocusChange);
super.initState();
}
@override
@ -70,8 +69,8 @@ class _TableCellShortcutListenerState extends State<TableCellShortcutListener> {
onTap: () {
setState(() {
_shortcut = [];
inputMode = true;
});
_focusNode.requestFocus();
},
child: Focus(
autofocus: true,

@ -7,6 +7,7 @@
#include "generated_plugin_registrant.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 <window_manager/window_manager_plugin.h>
@ -14,6 +15,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) flutter_platform_alert_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterPlatformAlertPlugin");
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 =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);

@ -4,6 +4,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
flutter_platform_alert
sentry_flutter
url_launcher_linux
window_manager
)

@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.0+1
version: 1.0.1+1
environment:
sdk: ">=2.15.0 <3.0.0"
@ -30,7 +30,7 @@ dependencies:
xdg_directories: ^0.2.0
gsettings: 0.2.3
provider: ^6.0.2
package_info_plus: ^1.3.0
package_info_plus: 1.3.0
easy_localization: ^3.0.0
glass_kit: ^2.0.1
rect_getter: ^1.0.0
@ -43,6 +43,7 @@ dependencies:
flutter_login: ^3.1.0
auto_size_text: ^3.0.0
numeral: ^1.2.5
sentry_flutter: ^6.3.0
markdown_editor_ot:
path: 3rd_party/markdown_editor_ot
cherry_toast:
@ -57,6 +58,7 @@ dev_dependencies:
sdk: flutter
build_runner: 2.1.7
source_gen: 1.2.1
yaml: any
# For information on the generic Dart part of this file, see the

@ -154,12 +154,22 @@
},
"upload": {
"success": "Upload success ~",
"failed": "Upload failed.."
"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": {
@ -175,5 +185,23 @@
"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"
}
}

@ -154,12 +154,22 @@
},
"upload": {
"success": "上传成功~",
"failed": "上传失败。。"
"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": {
@ -175,5 +185,23 @@
"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": "感谢使用本工具,使用前建议先点击右下角阅读使用说明哦~"
}

@ -17,6 +17,8 @@
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<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 -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
@ -25,6 +27,16 @@
<title>dde_gesture_manager</title>
<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>
<body>
<!-- This script installs service_worker.js to provide PWA functionality to

Loading…
Cancel
Save