diff --git a/.gitignore b/.gitignore index 66f8fb5..bfa3562 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea/ .vscode/ +db_data/ diff --git a/README.md b/README.md index b0d424b..d9501bf 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,141 @@ # DDE Gesture Manager -专为 DDE 桌面环境打造的触摸板手势管理工具 \ No newline at end of file + +![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。 + +# DEMO + +[DDE手势管理器-web版](http://www.debuggerx.com/dgm_web/#/) + +# BUILD/RUN + +## 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 + + +- [ ] 方案下载功能实现 +- [ ] 方案应用功能实现 +- [ ] BugFix +- [ ] 浅色模式界面优化 +- [ ] 打包上架 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 版来解决。 + + diff --git a/app/lib/themes/light.dart b/app/lib/themes/light.dart index fc0b49c..5c28a0d 100644 --- a/app/lib/themes/light.dart +++ b/app/lib/themes/light.dart @@ -8,7 +8,7 @@ var lightTheme = ThemeData.light().copyWith( iconTheme: IconThemeData( color: Color(0xff414d68), ), - dividerColor: Color(0xfff3f3f3), + dividerColor: Colors.grey.shade600, textTheme: ThemeData.light().textTheme.copyWith( headline1: TextStyle( color: Color(0xff414d68), diff --git a/app/lib/utils/helper.dart b/app/lib/utils/helper.dart index b4b1ad3..787af6b 100644 --- a/app/lib/utils/helper.dart +++ b/app/lib/utils/helper.dart @@ -4,6 +4,7 @@ import 'package:dde_gesture_manager/models/content_layout.provider.dart'; import 'package:dde_gesture_manager/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 on Iterable { @@ -29,9 +30,9 @@ class H { initSharedPreference() async { _sp = await SharedPreferences.getInstance(); } - + late BuildContext _topContext; - + BuildContext get topContext => _topContext; DateTime? lastCheckAuthStatusTime; @@ -114,6 +115,14 @@ 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'; + } + } } class PreferredPanelsStatus { diff --git a/app/lib/widgets/dde_markdown_field.dart b/app/lib/widgets/dde_markdown_field.dart index cdf6b8d..0dc0edb 100644 --- a/app/lib/widgets/dde_markdown_field.dart +++ b/app/lib/widgets/dde_markdown_field.dart @@ -2,6 +2,7 @@ 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'; @@ -36,14 +37,6 @@ class _DMarkdownFieldState extends State { 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) { @@ -82,7 +75,7 @@ class _DMarkdownFieldState extends State { child: MdPreview( text: _previewText ?? '', padding: EdgeInsets.only(left: 15), - onTapLink: _launchURL, + onTapLink: H.launchURL, onCodeCopied: () { Notificator.success( context, diff --git a/app/lib/widgets/market.dart b/app/lib/widgets/market.dart index 39957d7..816bdc2 100644 --- a/app/lib/widgets/market.dart +++ b/app/lib/widgets/market.dart @@ -4,6 +4,7 @@ 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/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'; @@ -131,17 +132,17 @@ class _MarketWidgetState extends State { ), ), 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 { ), ), ), - 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,13 @@ class _MarketWidgetState extends State { ), errorWidget: (context, url, error) => const Icon(Icons.error), ), + onTapLink: H.launchURL, onCodeCopied: () {}, ), ), ), - ], - ), + ), + ], ), ), Padding( diff --git a/app/lib/widgets/me.dart b/app/lib/widgets/me.dart index 3c4054d..6a33aa0 100644 --- a/app/lib/widgets/me.dart +++ b/app/lib/widgets/me.dart @@ -7,6 +7,7 @@ import 'package:dde_gesture_manager/http/api.dart'; import 'package:dde_gesture_manager/models/configs.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 +142,17 @@ class _MeWidgetState extends State { ), ), 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 +226,24 @@ class _MeWidgetState extends State { ), ), ), - 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, widgetImage: (imageUrl) => CachedNetworkImage( imageUrl: imageUrl, placeholder: (context, url) => const SizedBox( @@ -243,9 +256,9 @@ class _MeWidgetState extends State { onCodeCopied: () {}, ), ), - ) - ], - ), + ), + ) + ], ), ), Padding(