├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── api ├── .dockerignore ├── .gitignore ├── 3rd_party │ └── neat_cache │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── analysis_options.yaml │ │ ├── lib │ │ ├── cache_provider.dart │ │ ├── neat_cache.dart │ │ └── src │ │ │ └── providers │ │ │ ├── inmemory.dart │ │ │ ├── redis.dart │ │ │ └── resp.dart │ │ └── pubspec.yaml ├── AUTHORS.md ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── analysis_options.yaml ├── bin │ ├── dev.dart │ ├── migrate.dart │ └── prod.dart ├── config │ └── default.yaml ├── docker-compose.yml ├── lib │ ├── apis.dart │ ├── dde_gesture_manager_api.dart │ ├── models.dart │ └── src │ │ ├── config │ │ ├── config.dart │ │ └── plugins │ │ │ ├── jwt.dart │ │ │ ├── orm.dart │ │ │ ├── plugins.dart │ │ │ └── redis_cache.dart │ │ ├── models │ │ ├── app_version.dart │ │ ├── base_model.dart │ │ ├── download_history.dart │ │ ├── like_record.dart │ │ ├── login_success.dart │ │ ├── scheme.dart │ │ └── user.dart │ │ └── routes │ │ ├── controllers │ │ ├── auth_controllers.dart │ │ ├── controller_extensions.dart │ │ ├── middlewares.dart │ │ ├── scheme_controllers.dart │ │ └── system_controllers.dart │ │ └── routes.dart ├── pubspec.yaml ├── source_gen.sh ├── start.sh ├── test │ └── all_test.dart ├── views │ ├── confirm_sign_up.html │ ├── error.html │ ├── layout.html │ └── sign_up_result.html └── web │ ├── css │ └── site.css │ ├── images │ └── favicon.png │ └── robots.txt └── app ├── .gitignore ├── .metadata ├── 3rd_party ├── cherry_toast │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── lib │ │ ├── cherry_toast.dart │ │ ├── cherry_toast_icon.dart │ │ └── resources │ │ │ ├── arrays.dart │ │ │ ├── colors.dart │ │ │ └── constants.dart │ └── pubspec.yaml ├── markdown_core │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── lib │ │ ├── builder.dart │ │ ├── markdown.dart │ │ └── text_style.dart │ └── pubspec.yaml ├── markdown_editor_ot │ ├── .gitignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── icons │ │ ├── demo.css │ │ ├── demo_index.html │ │ ├── iconfont.css │ │ ├── iconfont.eot │ │ ├── iconfont.js │ │ ├── iconfont.svg │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ └── iconfont.woff2 │ ├── lib │ │ ├── customize_physics.dart │ │ ├── fonts │ │ │ └── iconfont.ttf │ │ ├── markdown_editor.dart │ │ └── src │ │ │ ├── action.dart │ │ │ ├── edit_perform.dart │ │ │ ├── editor.dart │ │ │ └── preview.dart │ └── pubspec.yaml └── xdg_directories_web │ ├── .gitignore │ ├── LICENSE │ ├── lib │ └── xdg_directories.dart │ └── pubspec.yaml ├── README.md ├── build.yaml ├── build_deb.sh ├── build_web.sh ├── dde_package_info ├── entries │ └── applications │ │ └── com.debuggerx.dde-gesture-manager.desktop └── info ├── debian ├── changelog └── control ├── lib ├── builder │ ├── provider_annotation.dart │ ├── provider_builder.dart │ └── provider_generator.dart ├── constants │ ├── constants.dart │ ├── sp_keys.dart │ └── supported_locales.dart ├── extensions.dart ├── extensions │ ├── compare_extension.dart │ ├── context_extension.dart │ ├── shared_preferences_extension.dart │ ├── sout_extension.dart │ └── string_extension.dart ├── http │ └── api.dart ├── main.dart ├── models │ ├── configs.dart │ ├── content_layout.dart │ ├── local_schemes.dart │ ├── local_schemes_linux.dart │ ├── local_schemes_provider.dart │ ├── local_schemes_web.dart │ ├── scheme.dart │ ├── scheme_list_refresh_key.dart │ └── settings.dart ├── pages │ ├── content.dart │ ├── footer.dart │ ├── gesture_editor.dart │ ├── home.dart │ ├── local_manager.dart │ └── market_or_me.dart ├── themes │ ├── dark.dart │ └── light.dart ├── utils │ ├── alert_interface.dart │ ├── alert_platform.dart │ ├── alert_web.dart │ ├── apply_scheme_interface.dart │ ├── apply_scheme_linux.dart │ ├── apply_scheme_web.dart │ ├── helper.dart │ ├── init.dart │ ├── init_linux.dart │ ├── init_web.dart │ ├── keyboard_mapper.dart │ ├── notificator.dart │ └── simple_throttle.dart └── widgets │ ├── dde_button.dart │ ├── dde_data_table.dart │ ├── dde_markdown_field.dart │ ├── dde_text_field.dart │ ├── help_button.dart │ ├── language_switcher.dart │ ├── login.dart │ ├── market.dart │ ├── me.dart │ ├── table_cell_shortcut_listener.dart │ ├── table_cell_text_field.dart │ ├── theme_switcher.dart │ └── version_checker.dart ├── linux ├── .gitignore ├── CMakeLists.txt ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake ├── main.cc ├── my_application.cc └── my_application.h ├── pubspec.yaml ├── resources ├── langs │ ├── en.json │ └── zh-CN.json └── logo │ └── logo.png ├── source_gen.sh ├── test └── widget_test.dart ├── version.dart └── web ├── bulletin.json ├── favicon.ico ├── favicon.png ├── google_fonts └── Roboto-Regular.ttf ├── icons ├── Icon-192.png └── Icon-512.png ├── index.html └── manifest.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.dart] 4 | max_line_length = 120 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | db_data/ 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, dx8917312@gmail.com 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DDE Gesture Manager 2 | 3 | ![logo](https://github.com/debuggerx01/dde_gesture_manager/blob/master/app/web/icons/Icon-192.png?raw=true) 4 | 5 | 专为 [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。 6 | 7 | # web版 8 | 9 | [DDE手势管理器-web版](http://www.debuggerx.com/dgm_web/#/) 10 | 11 | # 功能 12 | - 创建、编辑、删除本地手势配置方案 13 | - 将选定手势方案应用到系统中 14 | - 注册登陆后可以上传、分享自己创建的配置方案 15 | - 可以下载、点赞他人分享的配置方案 16 | - 贴合 DDE 的 UI 设计风格,支持系统主题切换和活动色 17 | - 支持多语言 18 | 19 | # 手册 20 | [DDE手势管理器-说明手册](https://www.debuggerx.com/2022/01/21/dgm-manual/) 21 | 22 | # 运行 23 | 24 | ## api 25 | 26 | - 使用docker(推荐) 27 | 28 | 首先安装 docker 及 docker-compose,然后在`/api`目录下执行: 29 | 30 | ```shell 31 | bash start.sh 32 | ``` 33 | 34 | - 手动运行 35 | 1. 首先配置 dart 环境(如果已经配置 flutter 开发环境则无需再配置): 36 | [Dart SDK overview](https://dart.dev/tools/sdk) 37 | 38 | 2. 安装项目依赖,运行代码生成命令: 39 | 在`/api`目录下执行: 40 | ```shell 41 | bash source_gen.sh 42 | ``` 43 | 44 | 3. 安装 PostgreSQL 及 Redis 45 | - [PostgreSQL Downloads](https://www.postgresql.org/download/) 46 | - [Redis Downloads](https://redis.io/download) 47 | 48 | 然后在 `/config/development.yaml` 设置如下配置: 49 | ```yaml 50 | # Development-only server configuration. 51 | debug: true 52 | postgres: 53 | host: [db host] 54 | port: 5432 55 | database_name: gesture_manager 56 | username: postgres 57 | password: [db password] 58 | use_ssl: false 59 | time_zone: Asia/Shanghai 60 | redis: 61 | host: [redis host] 62 | port: 6379 63 | password: [redis password] 64 | smtp: 65 | username: [smtp account name] 66 | password: [smtp account password] 67 | host: [smtp server host] 68 | ``` 69 | 70 | 4. 设置数据库 71 | - 登录数据库,创建名为 `gesture_manager` 的数据库 72 | 73 | ```sql 74 | create database gesture_manager; 75 | ``` 76 | 77 | - 运行 Migration: 78 | 79 | ```bash 80 | dart bin/migrate.dart 81 | ``` 82 | 5. 运行 api 83 | ```bash 84 | dart bin/dev.dart 85 | ``` 86 | 87 | ## app 88 | 89 | 1. 配置 flutter 开发环境,并启用 Linux 支持: 90 | - [Linux install](https://docs.flutter.dev/get-started/install/linux) 91 | - [Additional Linux requirements](https://docs.flutter.dev/get-started/install/linux#additional-linux-requirements) 92 | 93 | 2. 修改服务器连接地址 94 | 在 `/api` 目录下修改 `lib/apis.dart`: 95 | ```dart 96 | class Apis { 97 | static const apiScheme = 'http'; 98 | static const apiHost = 'localhost'; // 设置为api的地址 99 | static const apiPort = 3000; // 设置为api监听的端口 100 | 101 | static const appNewVersionUrl = 'https://www.debuggerx.com'; 102 | 103 | …… 104 | } 105 | ``` 106 | 107 | 3. 安装项目依赖,运行代码生成命令: 108 | 在`/app`目录下执行: 109 | ```shell 110 | bash source_gen.sh 111 | ``` 112 | 113 | 4. 运行app项目: 114 | - Linux: 115 | ```shell 116 | flutter run -d linux 117 | ``` 118 | 119 | - web: 120 | ```shell 121 | flutter run -d chrome 122 | ``` 123 | 124 | 125 | 126 | # RoadMap 127 | 128 | 129 | - [x] 方案下载功能实现 130 | - [x] 方案应用功能实现 131 | - [x] BugFix 132 | - [x] MD 编辑器中的UI文本国际化 133 | - [x] 编写帮助说明文档 134 | - [ ] 浅色模式界面优化 135 | - [x] 打包上架 Deepin/UOS 应用商店 136 | 137 | 138 | # FAQ 139 | 140 | - Q:为什么要开发这个工具 141 | 142 | A:本人是 [Deepin Linux](https://www.deepin.org/zh/) 的老粉了,日常学习工作和生活娱乐几乎完全在 Deepin/UOS 系统下进行。同时我还是个手势重度依赖者,除了鼠标手势,对笔记本的触摸板手势一样有很强的自定义需求。但是从 Deepin 系统增加手势功能到如今也有5年多了,官方一直没有在系统层面给出自定义触摸板手势的功能入口,我不得不经常通过手工修改系统手势配置文件的方式来实现自定义。但是长久以来,一方面是自己每次新装系统都需要重新设置,一方面是不断看到论坛和用户群有朋友反馈询问修改方法,遂决定动手写一个方便使用,并支持配置分享下载的GUI工具 143 | 144 | - Q:为什么使用 flutter 开发而不是 Qt/DTK/GTK …… 145 | 146 | A:因为本人对 flutter 比较熟悉,有4年多的研究积累,而且对于 flutter 的跨平台效果非常看好,而C/C++的经验相对缺乏,又恰逢2021下半年这个时间点,google官方的一大重点就是对桌面应用开发的支持,于是决定尝试通过使用 flutter 实现本工具。 147 | 148 | - Q:为何还要兼容开发Web版本 149 | 150 | A:得益于 flutter 的跨平台能力,在开发 Linux 桌面版应用的基础上,可以以很低的成本同步开发出 Web 版,于是一方面出于技术探索的目的,从一开始的功能规划我就将 Web 支持放在了基础需求中。另外,Web 版还有三个明显的好处: 151 | 152 | 1. 用户可以不必安装桌面应用,仅仅通过浏览器打开网页就能体验本工具的功能,方便了用户预览体验,也方便本项目的转发推广; 153 | 2. 由于 UOS 系统默认是锁 root 权限的,某些情况下的用户(比如机关单位的普通员工)可能不方便安装运行第三方软件,虽然我有将本工具上架 UOS 软件商店的打算,但是并不一定能够保证及时更新,所以此时可以通过使用 Web 版来实现和桌面版相同的功能和接近的体验; 154 | 3. 还有一部分用户可能使用的是国产CPU,可能并不是 flutter 的编译工具所支持的,或者虽然 flutter 支持,但是由于我没有对应的机器进项编译打包,所以可能暂时无法为这些用户提供二进制的程序使用,此时这些用户一样可以通过使用 Web 版来解决。 155 | 156 | - Q:为何使用 dart 编写服务端,而不用其他更流行常见的语言和技术 157 | 158 | A:作为全栈开发,虽然有多种其他语言和流行框架的后端开发经验,但是那些方案,有些是框架本身太重太吃资源,不适合这个小项目使用,有些是语言本身实在是写烦了,开发起来没有动力……在看到一些朋友和大佬分享使用 dart 开发后端的经验之后,我想,是不是可以让前后端项目使用相同的语言,以"同构"的方式开发,并将前后端的一些"弱关联"转变成由语法来保证正确性的"强依赖"呢? 159 | 160 | 所以在这个项目中,我让`api`直接作为`app`的依赖,`app`的网络请求处理中直接使用`api`侧导出的请求参数定义和结果模型,探索一种可以不用再通过文档进行前后端配合的开发模式——因为我相信,文档总是不可靠的,只有代码本身不会骗人 :joy: 161 | -------------------------------------------------------------------------------- /api/.dockerignore: -------------------------------------------------------------------------------- 1 | .dart_tool 2 | .idea 3 | .pub 4 | .vscode 5 | logs/ 6 | test/ 7 | build/ 8 | .analysis-options 9 | .packages 10 | *.g.dart -------------------------------------------------------------------------------- /api/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Dart template 3 | # See https://www.dartlang.org/tools/private-files.html 4 | 5 | # source_gen 6 | .dart_tool 7 | 8 | # Files and directories created by pub 9 | .buildlog 10 | .packages 11 | .project 12 | .pub/ 13 | .scripts-bin/ 14 | build/ 15 | **/packages/ 16 | 17 | # Files created by dart2js 18 | # (Most Dart developers will use pub build to compile Dart, use/modify these 19 | # rules if you intend to use dart2js directly 20 | # Convention is to use extension '.dart.js' for Dart compiled to Javascript to 21 | # differentiate from explicit Javascript files) 22 | *.dart.js 23 | *.part.js 24 | *.js.deps 25 | *.js.map 26 | *.info.json 27 | 28 | # Directory created by dartdoc 29 | doc/api/ 30 | 31 | # Don't commit pubspec lock file 32 | # (Library packages only! Remove pattern if developing an application package) 33 | pubspec.lock 34 | ### JetBrains template 35 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 36 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 37 | 38 | # User-specific stuff: 39 | .idea/workspace.xml 40 | .idea/tasks.xml 41 | .idea/dictionaries 42 | .idea/vcs.xml 43 | .idea/jsLibraryMappings.xml 44 | 45 | # Sensitive or high-churn files: 46 | .idea/dataSources.ids 47 | .idea/dataSources.xml 48 | .idea/dataSources.local.xml 49 | .idea/sqlDataSources.xml 50 | .idea/dynamic.xml 51 | .idea/uiDesigner.xml 52 | 53 | # Gradle: 54 | .idea/gradle.xml 55 | .idea/libraries 56 | 57 | # Mongo Explorer plugin: 58 | .idea/mongoSettings.xml 59 | 60 | ## File-based project format: 61 | *.iws 62 | 63 | ## Plugin-specific files: 64 | 65 | # IntelliJ 66 | /out/ 67 | 68 | # mpeltonen/sbt-idea plugin 69 | .idea_modules/ 70 | 71 | # JIRA plugin 72 | atlassian-ide-plugin.xml 73 | 74 | # Crashlytics plugin (for Android Studio and IntelliJ) 75 | com_crashlytics_export_strings.xml 76 | crashlytics.properties 77 | crashlytics-build.properties 78 | fabric.properties 79 | 80 | ### VSCode template 81 | .vscode/ 82 | !.vscode/settings.json 83 | !.vscode/tasks.json 84 | !.vscode/launch.json 85 | !.vscode/extensions.json 86 | 87 | logs/ 88 | *.pem 89 | .DS_Store 90 | server_log.txt 91 | 92 | .metals/ 93 | 94 | /config/production.yaml 95 | /config/development.yaml 96 | 97 | *.g.dart 98 | -------------------------------------------------------------------------------- /api/3rd_party/neat_cache/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v2.0.1 2 | * Fixed issue when adding multiple commands without waiting for them to complete first. 3 | 4 | ## v2.0.0 5 | * Migrated to null-safety, dropping dependency on `package:dartis`. 6 | 7 | ## v1.0.2 8 | * Upgrade `package:dartis`. 9 | 10 | ## v1.0.1 11 | * Avoid unnecessary purging when calling `set` with a `create` function that 12 | returns `null`. 13 | 14 | ## v1.0.0 15 | * Initial release. 16 | -------------------------------------------------------------------------------- /api/3rd_party/neat_cache/README.md: -------------------------------------------------------------------------------- 1 | Neat Cache 2 | ========== 3 | 4 | Abstractions around in-memory caches stores such as redis, with timeouts and 5 | automatic reconnects. 6 | 7 | **Disclaimer:** This is not an officially supported Google product. 8 | 9 | ## Example 10 | 11 | ```dart 12 | import 'dart:async' show Future; 13 | import 'dart:convert' show utf8; 14 | import 'package:neat_cache/neat_cache.dart'; 15 | 16 | Future main() async { 17 | final cacheProvider = Cache.redisCacheProvider('redis://localhost:6379'); 18 | final cache = Cache(cacheProvider); 19 | 20 | /// Create a sub-cache using a prefix, and apply a codec to store utf8 21 | final userCache = cache.withPrefix('users').withCodec(utf8); 22 | 23 | /// Get data form cache 24 | String userinfo = await userCache['peter-pan'].get(); 25 | print(userinfo); 26 | 27 | /// Put data into cache 28 | await userCache['winnie'].set('Like honey'); 29 | 30 | await cacheProvider.close(); 31 | } 32 | ``` 33 | 34 | 35 | ## Development 36 | To test the redis `CacheProvider` a redis instance must be running on 37 | `localhost:6379`, this can be setup with: 38 | 39 | * `docker run --rm -p 127.0.0.1:6379:6379 redis` 40 | -------------------------------------------------------------------------------- /api/3rd_party/neat_cache/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:pedantic/analysis_options.yaml 2 | -------------------------------------------------------------------------------- /api/3rd_party/neat_cache/lib/cache_provider.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'dart:async'; 16 | import 'neat_cache.dart' show Cache; 17 | 18 | /// Low-level interface for a cache. 19 | /// 20 | /// This can be an in-memory cache, something that writes to disk or to an 21 | /// cache service such as memcached or redis. 22 | /// 23 | /// The [Cache] provided by `package:neat_cache`, is intended to wrap 24 | /// a [CacheProvider] and provide a more convinient high-level interface. 25 | /// 26 | /// Implementers of [CacheProvider] can implement something that stores a value 27 | /// of any type `T`, but usually implementers should aim to implement 28 | /// `CacheProvider>` which stores binary data. 29 | /// 30 | /// Implementations of the [CacheProvider] interface using a remote backing 31 | /// store should throw [IntermittentCacheException] when an intermittent network 32 | /// issue occurs. The [CacheProvider] should obviously attempt to reconnect to 33 | /// the remote backing store, but it should not retry operations. 34 | /// 35 | /// Operations will be retried by [Cache], if necessary. Many use-cases of 36 | /// caching are resilient to intermittent failures. 37 | abstract class CacheProvider { 38 | /// Fetch data stored under [key]. 39 | /// 40 | /// If nothing is cached for [key], this **must** return `null`. 41 | Future get(String key); 42 | 43 | /// Set [value] stored at [key] with optional [ttl]. 44 | /// 45 | /// If a value is already stored at [key], that value should be overwritten 46 | /// with the new [value] given here. 47 | /// 48 | /// When given [ttl] is advisory, however, implementers should avoid returning 49 | /// entries that are far past their [ttl]. 50 | Future set(String key, T value, [Duration? ttl]); 51 | 52 | /// Clear value stored at [key]. 53 | /// 54 | /// After this has returned future calls to [get] for the given [key] should 55 | /// not return any value, unless a new value have been set. 56 | Future purge(String key); 57 | 58 | /// Close all connections, causing all future operations to throw. 59 | /// 60 | /// This method frees resources used by this [CacheProvider], if backed by 61 | /// a remote service like redis, this should close the connection. 62 | /// 63 | /// Calling [close] multiple times does not throw. But after this has returned 64 | /// all future operations should throw [StateError]. 65 | Future close(); 66 | } 67 | 68 | /// Exception thrown when there is an intermittent exception. 69 | /// 70 | /// This is typically thrown if there is an intermittent connection error. 71 | class IntermittentCacheException implements Exception { 72 | final String _message; 73 | IntermittentCacheException(this._message); 74 | 75 | @override 76 | String toString() => _message; 77 | } 78 | -------------------------------------------------------------------------------- /api/3rd_party/neat_cache/lib/src/providers/inmemory.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'dart:async'; 16 | import '../../cache_provider.dart'; 17 | 18 | class _InMemoryEntry { 19 | final T value; 20 | final DateTime? _expires; 21 | _InMemoryEntry(this.value, [this._expires]); 22 | bool get isExpired => _expires != null && _expires!.isBefore(DateTime.now()); 23 | } 24 | 25 | /// Simple two-generational LRU cache inspired by: 26 | /// https://github.com/sindresorhus/quick-lru 27 | class InMemoryCacheProvider extends CacheProvider { 28 | /// New generation of cache entries. 29 | Map> _new = >{}; 30 | 31 | /// Old generation of cache entries. 32 | Map> _old = >{}; 33 | 34 | /// Maximum size before clearing old generation. 35 | final int _maxSize; 36 | 37 | /// Have this been closed. 38 | bool _isClosed = false; 39 | 40 | InMemoryCacheProvider(this._maxSize); 41 | 42 | /// Clear old generation, if _maxSize have been reached. 43 | void _maintainGenerations() { 44 | if (_new.length >= _maxSize) { 45 | _old = _new; 46 | _new = {}; 47 | } 48 | } 49 | 50 | @override 51 | Future get(String key) async { 52 | if (_isClosed) { 53 | throw StateError('CacheProvider.close() have been called'); 54 | } 55 | // Lookup in the new generation 56 | var entry = _new[key]; 57 | if (entry != null) { 58 | if (!entry.isExpired) { 59 | return entry.value; 60 | } 61 | // Remove, if expired 62 | _new.remove(key); 63 | } 64 | // Lookup in the old generation 65 | entry = _old[key]; 66 | if (entry != null) { 67 | if (!entry.isExpired) { 68 | // If not expired, we insert the entry into the new generation 69 | _new[key] = entry; 70 | _maintainGenerations(); 71 | return entry.value; 72 | } 73 | // Remove, if expired 74 | _old.remove(key); 75 | } 76 | return null; 77 | } 78 | 79 | @override 80 | Future set(String key, T value, [Duration? ttl]) async { 81 | if (_isClosed) { 82 | throw StateError('CacheProvider.close() have been called'); 83 | } 84 | if (ttl == null) { 85 | _new[key] = _InMemoryEntry(value); 86 | } else { 87 | _new[key] = _InMemoryEntry(value, DateTime.now().add(ttl)); 88 | } 89 | // Always remove key from old generation to avoid risks of looking up there 90 | // if it's overwritten by an entry with a shorter ttl 91 | _old.remove(key); 92 | _maintainGenerations(); 93 | } 94 | 95 | @override 96 | Future purge(String key) async { 97 | if (_isClosed) { 98 | throw StateError('CacheProvider.close() have been called'); 99 | } 100 | _new.remove(key); 101 | _old.remove(key); 102 | } 103 | 104 | @override 105 | Future close() async { 106 | _isClosed = true; 107 | _old = {}; 108 | _new = {}; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /api/3rd_party/neat_cache/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: neat_cache 2 | version: 2.0.1 3 | description: | 4 | A neat cache abstraction for wrapping in-memory or redis caches. 5 | homepage: https://github.com/google/dart-neats/tree/master/neat_cache 6 | repository: https://github.com/google/dart-neats.git 7 | issue_tracker: https://github.com/google/dart-neats/labels/pkg:neat_cache 8 | dependencies: 9 | convert: ^3.0.0 10 | logging: ^1.0.1 11 | retry: ^3.0.0 12 | async: ^2.7.0 13 | meta: ^1.4.0 14 | dev_dependencies: 15 | test: ^1.5.1 16 | pedantic: ^1.4.0 17 | environment: 18 | sdk: '>=2.12.0 <3.0.0' 19 | -------------------------------------------------------------------------------- /api/AUTHORS.md: -------------------------------------------------------------------------------- 1 | Primary Authors 2 | =============== 3 | 4 | * __[Thomas Hii](dukefirehawk.apps@gmail.com)__ 5 | 6 | Thomas is the current maintainer of the code base. He has refactored and migrated the 7 | code base to support NNBD. 8 | 9 | * __[Tobe O](thosakwe@gmail.com)__ 10 | 11 | Tobe has written much of the original code prior to NNBD migration. He has moved on and 12 | is no longer involved with the project. 13 | -------------------------------------------------------------------------------- /api/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 1.0.0 4 | 5 | * Changed to use `angel3` packages 6 | * Updated to support NNBD 7 | * Updated README 8 | * Updated default `postgresql` setup 9 | * Updated linter to `package:lints` 10 | -------------------------------------------------------------------------------- /api/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution 2 | 3 | Any help from the open-source community is always welcome and needed: 4 | 5 | 1. Found an issue? 6 | - Please [fill a bug report][tracker] with error message and steps to reproduce it. 7 | 2. Wish a feature? 8 | - Open a feature request with use cases. 9 | 3. Are you using and liking the project? 10 | - Create an article about your use case 11 | - Do a post on your likes and dislikes 12 | - Make a donation. 13 | 4. Are you a developer? 14 | - Fix a bug and send a [pull request][pull_request] 15 | - Implement a new feature 16 | - Improve the Unit Tests 17 | - Improve the [User Guide][doc] and send a [document pull request][doc_repo] 18 | 5. Have you already helped in any way? 19 | - **Many thanks to the contributors and everybody that uses this project!** 20 | 21 | [tracker]: https://github.com/dukefirehawk/angel/issues 22 | [pull_request]: https://github.com/dukefirehawk/angel/pulls 23 | [doc]: https://angel3-docs.dukefirehawk.com 24 | [doc_repo]: https://github.com/dukefirehawk/angel3-guide/pulls -------------------------------------------------------------------------------- /api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM dart:stable AS build-env 2 | LABEL stage=dart_builder 3 | ENV PUB_HOSTED_URL="https://pub.flutter-io.cn" 4 | ENV ANGEL_ENV=production 5 | COPY ./ ./ 6 | RUN pub get 7 | RUN bash source_gen.sh && dart compile exe bin/prod.dart -o /server 8 | ENTRYPOINT ["dart", "bin/migrate.dart", "up"] 9 | 10 | FROM scratch 11 | WORKDIR /app 12 | ENV ANGEL_ENV=production 13 | ADD ./views ./views 14 | ADD ./config ./config 15 | COPY --from=build-env /runtime/ / 16 | COPY --from=build-env /server /app 17 | EXPOSE 3000 18 | ENTRYPOINT ["./server", "-a", "0.0.0.0", "--port", "3000"] -------------------------------------------------------------------------------- /api/LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, dukefirehawk.com 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /api/README.md: -------------------------------------------------------------------------------- 1 | # ORM Starter Application for Angel3 framework 2 | 3 | This is an ORM starter application for [Angel3 framework](https://angel3-framework.web.app) which is a full-stack Web framework in Dart. The default database is `postgresql`. `mysql` support is still in active development. 4 | 5 | ## Installation & Setup 6 | 7 | 1. Download and install [Dart](https://dart.dev/get-dart). 8 | 2. Install `postgresql` version 9, 10, 11 or 12. **postgresql 13 is not working as the driver do not support SCRAM** 9 | 3. Create a new user and database in postgres using `psql` cli. For example: 10 | 11 | ```sql 12 | postgres=# create database appdb; 13 | postgres=# create user appuser with encrypted password 'App1970#'; 14 | postgres=# grant all privileges on database appdb to appuser; 15 | ``` 16 | 17 | 4. Update the `postgres` section in the `config/default.yaml` file with the newly created user and database name. 18 | 19 | ```yaml 20 | postgres: 21 | host: localhost 22 | port: 5432 23 | database_name: appdb 24 | username: appuser 25 | password: App1970# 26 | useSSL: false 27 | time_zone: UTC 28 | ``` 29 | 30 | 5. Run the migration to generate `migrations` and `greetings` tables in the database. 31 | 32 | ```bash 33 | dart bin/migration.dart 34 | ``` 35 | 36 | ### Development 37 | 38 | 1. Run the following command to start Angel3 server in dev mode to *hot-reloaded* on file changes: 39 | 40 | ```bash 41 | dart --observe bin/dev.dart 42 | ``` 43 | 44 | 2. Modify the code and watch the changes applied to the application 45 | 46 | ### Production 47 | 48 | 1. Run the following command: 49 | 50 | ```bash 51 | dart bin/prod.dart 52 | ``` 53 | 54 | 2. Run as docker. Edit and run the provided `Dockerfile` to build the image. 55 | 56 | ## Resources 57 | 58 | Visit the [Developer Guide](https://angel3-docs.dukefirehawk.com/guides) for dozens of guides and resources, including video tutorials, to get up and running as quickly as possible with Angel3. 59 | 60 | Examples and complete projects can be found [here](https://angel3-framework.web.app/#/examples). 61 | 62 | You can also view the [API Documentation](https://pub.dev/documentation/angel3_framework/latest/). 63 | -------------------------------------------------------------------------------- /api/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lints/recommended.yaml 2 | 3 | linter: 4 | rules: 5 | avoid_renaming_method_parameters: false 6 | overridden_fields: false 7 | unnecessary_statements: true 8 | -------------------------------------------------------------------------------- /api/bin/dev.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:dde_gesture_manager_api/dde_gesture_manager_api.dart'; 3 | import 'package:belatuk_pretty_logging/belatuk_pretty_logging.dart'; 4 | import 'package:angel3_container/mirrors.dart'; 5 | import 'package:angel3_framework/angel3_framework.dart'; 6 | import 'package:angel3_hot/angel3_hot.dart'; 7 | import 'package:logging/logging.dart'; 8 | 9 | void main() async { 10 | // Watch the config/ and web/ directories for changes, and hot-reload the server. 11 | var hot = HotReloader(() async { 12 | Logger.root 13 | ..level = Level.ALL 14 | ..onRecord.listen(prettyLog); 15 | var logger = Logger.detached('dde_gesture_manager_api'); 16 | var app = Angel(logger: logger, reflector: MirrorsReflector()); 17 | await app.configure(configureServer); 18 | return app; 19 | }, [ 20 | Directory('config'), 21 | Directory('lib'), 22 | ]); 23 | 24 | var server = await hot.startServer('127.0.0.1', 3000); 25 | print('dde_gesture_manager_api server listening at http://${server.address.address}:${server.port}'); 26 | } 27 | -------------------------------------------------------------------------------- /api/bin/migrate.dart: -------------------------------------------------------------------------------- 1 | import 'package:angel3_configuration/angel3_configuration.dart'; 2 | import 'package:angel3_migration/angel3_migration.dart'; 3 | import 'package:angel3_migration_runner/angel3_migration_runner.dart'; 4 | import 'package:angel3_migration_runner/postgres.dart'; 5 | import 'package:angel3_orm_postgres/angel3_orm_postgres.dart'; 6 | import 'package:dde_gesture_manager_api/models.dart'; 7 | import 'package:dde_gesture_manager_api/src/config/plugins/orm.dart'; 8 | import 'package:dde_gesture_manager_api/src/models/download_history.dart'; 9 | import 'package:dde_gesture_manager_api/src/models/like_record.dart'; 10 | import 'package:file/local.dart'; 11 | import 'package:logging/logging.dart'; 12 | 13 | late Map configuration; 14 | 15 | void main(List args) async { 16 | // Enable the logging 17 | Logger.root.level = Level.INFO; 18 | Logger.root.onRecord.listen((rec) { 19 | print('${rec.time}: ${rec.level.name}: ${rec.loggerName}: ${rec.message}'); 20 | 21 | if (rec.error != null) { 22 | print(rec.error); 23 | print(rec.stackTrace); 24 | } 25 | }); 26 | 27 | var fs = LocalFileSystem(); 28 | configuration = await loadStandaloneConfiguration(fs); 29 | var connection = await connectToPostgres(configuration); 30 | var migrationRunner = PostgresMigrationRunner(connection, migrations: [ 31 | UserMigration(), 32 | UserSeed(), 33 | SchemeMigration(), 34 | DownloadHistoryMigration(), 35 | LikeRecordMigration(), 36 | AppVersionMigration(), 37 | AppVersionSeed(), 38 | ]); 39 | await runMigrations(migrationRunner, args); 40 | } 41 | 42 | class UserSeed extends Migration { 43 | @override 44 | void up(Schema schema) async { 45 | await doUserSeed(); 46 | } 47 | 48 | @override 49 | void down(Schema schema) async {} 50 | } 51 | 52 | Future doUserSeed() async { 53 | var connection = await connectToPostgres(configuration); 54 | await connection.open(); 55 | var executor = PostgreSqlExecutor(connection); 56 | var userQuery = UserQuery(); 57 | userQuery.where?.email.equals('admin@admin.com'); 58 | var one = await userQuery.getOne(executor); 59 | if (one.isEmpty) { 60 | userQuery = UserQuery(); 61 | userQuery.values.copyFrom(User( 62 | email: 'admin@admin.com', 63 | password: '1234567890', 64 | )); 65 | return userQuery.insert(executor).then((value) => connection.close()); 66 | } 67 | return connection.close(); 68 | } 69 | 70 | class AppVersionSeed extends Migration { 71 | @override 72 | void up(Schema schema) async { 73 | await doAppVersionSeed(); 74 | } 75 | 76 | @override 77 | void down(Schema schema) async {} 78 | } 79 | 80 | Future doAppVersionSeed() async { 81 | var connection = await connectToPostgres(configuration); 82 | await connection.open(); 83 | var executor = PostgreSqlExecutor(connection); 84 | var appVersionQuery = AppVersionQuery(); 85 | var one = await appVersionQuery.getOne(executor); 86 | if (one.isEmpty) { 87 | appVersionQuery = AppVersionQuery(); 88 | appVersionQuery.values.copyFrom(AppVersion( 89 | versionCode: 1, 90 | versionName: '1.0.0', 91 | )); 92 | return appVersionQuery.insert(executor).then((value) => connection.close()); 93 | } 94 | return connection.close(); 95 | } 96 | -------------------------------------------------------------------------------- /api/bin/prod.dart: -------------------------------------------------------------------------------- 1 | import 'package:dde_gesture_manager_api/dde_gesture_manager_api.dart'; 2 | import 'package:angel3_production/angel3_production.dart'; 3 | 4 | // NOTE: By default, the Runner class does not use the `MirrorsReflector`, or any 5 | // reflector, by default. 6 | // 7 | // If your application is using any sort of functionality reliant on annotations or reflection, 8 | // either include the MirrorsReflector, or use a static reflector variant. 9 | // 10 | // The following use cases require reflection: 11 | // * Use of Controllers, via @Expose() or @ExposeWS() 12 | // * Use of dependency injection into constructors, whether in controllers or plain `container.make` calls 13 | // * Use of the `ioc` function in any route 14 | // 15 | // The `MirrorsReflector` from `package:angel_container/mirrors.dart` is by far the most convenient pattern, 16 | // so use it if possible. 17 | // 18 | // However, the following alternatives exist: 19 | // * Generation via `package:angel_container_generator` 20 | // * Creating an instance of `StaticReflector` 21 | // * Manually implementing the `Reflector` interface (cumbersome; not recommended) 22 | // 23 | // As of January 4th, 2018, the documentation has not yet been updated to state this, 24 | // so in the meantime, visit the Angel chat for further questions: 25 | // 26 | // https://gitter.im/angel_dart/discussion 27 | void main(List args) => Runner('dde_gesture_manager_api', configureServer).run(args); 28 | -------------------------------------------------------------------------------- /api/config/default.yaml: -------------------------------------------------------------------------------- 1 | # Default server configuration. 2 | host: 127.0.0.1 3 | port: 3000 4 | postgres: 5 | host: db 6 | port: 5432 7 | database_name: gesture_manager 8 | username: postgres 9 | password: gesture_manager_secret 10 | use_ssl: false 11 | time_zone: Asia/Shanghai 12 | 13 | redis: 14 | host: kv 15 | port: 6379 16 | 17 | jwt_secret: "OvA9SBLnncot8gFHvt8Gh1qkQ1ptGIQW" 18 | password_salt: "Z5b84rrgsKmfNFNRExAC4BCJe5aZPdJq" -------------------------------------------------------------------------------- /api/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2.4" 2 | 3 | services: 4 | kv: 5 | image: redis:alpine 6 | restart: always 7 | container_name: dgm_redis 8 | ports: 9 | - "6379:6379" 10 | networks: 11 | - dgm_api_default 12 | 13 | db: 14 | image: postgres:alpine 15 | restart: always 16 | container_name: dgm_postgres 17 | environment: 18 | POSTGRES_DB: gesture_manager 19 | POSTGRES_PASSWORD: gesture_manager_secret 20 | volumes: 21 | - ../db_data:/var/lib/postgresql/data 22 | ports: 23 | - "5432:5432" 24 | networks: 25 | - dgm_api_default 26 | 27 | api: 28 | build: . 29 | image: dgm_api_image 30 | container_name: dgm_api 31 | ports: 32 | - 3000:3000 33 | restart: always 34 | depends_on: 35 | - db 36 | - kv 37 | links: 38 | - db:db 39 | - kv:kv 40 | networks: 41 | - dgm_api_default 42 | 43 | networks: 44 | dgm_api_default: 45 | name: dgm_api_default 46 | -------------------------------------------------------------------------------- /api/lib/apis.dart: -------------------------------------------------------------------------------- 1 | class Apis { 2 | static const apiScheme = 'http'; 3 | static const apiHost = 'dgm_api.debuggerx.com'; 4 | static const apiPort = 3000; 5 | 6 | static const appNewVersionUrl = 'https://www.debuggerx.com/2022/01/21/dgm-changelog?from=app'; 7 | 8 | static appBulletinUrl(bool isWeb) => 9 | 'https://www.debuggerx.com/dgm_web/bulletin.json?from=app_${isWeb ? 'web' : 'linux'}'; 10 | 11 | static appManualUrl(bool isWeb) => 12 | 'https://www.debuggerx.com/2022/01/21/dgm-manual?from=app_${isWeb ? 'web' : 'linux'}'; 13 | 14 | static final system = SystemApis(); 15 | static final auth = AuthApis(); 16 | static final scheme = SchemeApis(); 17 | } 18 | 19 | class AuthApis { 20 | static final String path = '/auth'; 21 | 22 | String get loginOrSignup => [path, 'login-or-signup'].joinPath(); 23 | 24 | String confirmSignup({required StringParam accessKey}) => [path, 'confirm-sign-up', accessKey].joinPath(); 25 | 26 | String get status => [path, 'status'].joinPath(); 27 | } 28 | 29 | class SystemApis { 30 | static final String path = '/system'; 31 | 32 | String get appVersion => [path, 'app-version'].joinPath(); 33 | } 34 | 35 | class SchemeApis { 36 | static final String path = '/scheme'; 37 | 38 | String get upload => [path, 'upload'].joinPath(); 39 | 40 | String markAsShared({required StringParam schemeId}) => [path, 'mark-as-shared', schemeId].joinPath(); 41 | 42 | String user({required StringParam type}) => [path, 'user', type].joinPath(); 43 | 44 | String market({required StringParam type, required IntParam page, required IntParam pageSize}) => 45 | [path, 'market', type, page, pageSize].joinPath(); 46 | 47 | String download({required StringParam schemeId}) => [path, 'download', schemeId].joinPath(); 48 | 49 | String like({required StringParam schemeId, required StringParam isLike}) => 50 | [path, 'like', schemeId, isLike].joinPath(); 51 | 52 | String get userLikes => [path, 'user-likes'].joinPath(); 53 | } 54 | 55 | final _paramsMap = { 56 | 'BoolParam': BoolParam.nameOnRoute, 57 | 'IntParam': IntParam.nameOnRoute, 58 | 'DoubleParam': DoubleParam.nameOnRoute, 59 | 'StringParam': StringParam.nameOnRoute, 60 | }; 61 | 62 | extension JoinPath on List { 63 | joinPath() => join('/'); 64 | } 65 | 66 | extension RouteUrl on Function { 67 | String get route { 68 | var funStr = toString(); 69 | funStr = funStr.replaceAll(RegExp(r'.+\(\{'), ' ').replaceAll(RegExp(r'\}\).+'), ' ').replaceAll(' required ', ''); 70 | var parts = funStr.split(','); 71 | Map params = {}; 72 | for (var part in parts) { 73 | var p = part.trim().split(' '); 74 | params[Symbol(p.last)] = (_paramsMap[p.first] as Function).call(p.last); 75 | } 76 | return Function.apply(this, [], params); 77 | } 78 | } 79 | 80 | class BoolParam { 81 | final bool val; 82 | String? name; 83 | 84 | BoolParam(this.val); 85 | 86 | BoolParam.nameOnRoute(this.name) : val = true; 87 | 88 | @override 89 | String toString() => name == null ? val.toString() : 'bool:$name'; 90 | } 91 | 92 | class IntParam { 93 | final int val; 94 | String? name; 95 | 96 | IntParam(this.val); 97 | 98 | IntParam.nameOnRoute(this.name) : val = 0; 99 | 100 | @override 101 | String toString() => name == null ? val.toString() : 'int:$name'; 102 | } 103 | 104 | class DoubleParam { 105 | final double val; 106 | String? name; 107 | 108 | DoubleParam(this.val); 109 | 110 | DoubleParam.nameOnRoute(this.name) : val = 0; 111 | 112 | @override 113 | String toString() => name == null ? val.toString() : 'double:$name'; 114 | } 115 | 116 | class StringParam { 117 | final String val; 118 | String? name; 119 | 120 | StringParam(this.val); 121 | 122 | StringParam.nameOnRoute(this.name) : val = ''; 123 | 124 | @override 125 | String toString() => name == null ? val.toString() : ':$name'; 126 | } 127 | 128 | extension BoolParamExt on bool { 129 | BoolParam get param => BoolParam(this); 130 | } 131 | 132 | extension IntParamExt on int { 133 | IntParam get param => IntParam(this); 134 | } 135 | 136 | extension DoubleParamExt on double { 137 | DoubleParam get param => DoubleParam(this); 138 | } 139 | 140 | extension StringParamExt on String { 141 | StringParam get param => StringParam(this); 142 | } 143 | -------------------------------------------------------------------------------- /api/lib/dde_gesture_manager_api.dart: -------------------------------------------------------------------------------- 1 | /// Your very own web application! 2 | import 'dart:async'; 3 | import 'package:angel3_framework/angel3_framework.dart'; 4 | import 'package:file/local.dart'; 5 | import 'src/config/config.dart' as configuration; 6 | import 'src/routes/routes.dart' as routes; 7 | 8 | /// Configures the server instance. 9 | Future configureServer(Angel app) async { 10 | // Grab a handle to the file system, so that we can do things like 11 | // serve static files. 12 | var fs = const LocalFileSystem(); 13 | 14 | // Set up our application, using the plug-ins defined with this project. 15 | await app.configure(configuration.configureServer(fs)); 16 | await app.configure(routes.configureServer(fs)); 17 | } 18 | -------------------------------------------------------------------------------- /api/lib/models.dart: -------------------------------------------------------------------------------- 1 | export 'src/models/user.dart'; 2 | export 'src/models/app_version.dart'; 3 | export 'src/models/login_success.dart'; 4 | export 'src/models/scheme.dart'; -------------------------------------------------------------------------------- /api/lib/src/config/config.dart: -------------------------------------------------------------------------------- 1 | import 'package:angel3_configuration/angel3_configuration.dart'; 2 | import 'package:angel3_framework/angel3_framework.dart'; 3 | import 'package:angel3_jinja/angel3_jinja.dart'; 4 | import 'package:file/file.dart'; 5 | import 'plugins/plugins.dart' as plugins; 6 | 7 | /// This is a perfect place to include configuration and load plug-ins. 8 | AngelConfigurer configureServer(FileSystem fileSystem) { 9 | return (Angel app) async { 10 | // Load configuration from the `config/` directory. 11 | // 12 | // See: https://github.com/angel-dart/configuration 13 | await app.configure(configuration(fileSystem)); 14 | 15 | // Configure our application to render jinja templates from the `views/` directory. 16 | // 17 | // See: https://github.com/angel-dart/jinja 18 | await app.configure(jinja(path: fileSystem.directory('views').path)); 19 | 20 | // Apply another plug-ins, i.e. ones that *you* have written. 21 | // 22 | // Typically, the plugins in `lib/src/config/plugins/plugins.dart` are plug-ins 23 | // that add functionality specific to your application. 24 | // 25 | // If you write a plug-in that you plan to use again, or are 26 | // using one created by the community, include it in 27 | // `lib/src/config/config.dart`. 28 | await plugins.configureServer(app); 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /api/lib/src/config/plugins/jwt.dart: -------------------------------------------------------------------------------- 1 | import 'package:angel3_auth/angel3_auth.dart'; 2 | import 'package:angel3_framework/angel3_framework.dart'; 3 | import 'package:angel3_orm/angel3_orm.dart' as orm; 4 | import 'package:dde_gesture_manager_api/models.dart'; 5 | 6 | Future configureServer(Angel app) async { 7 | var auth = AngelAuth( 8 | jwtKey: app.configuration['jwt_secret'], 9 | allowCookie: false, 10 | deserializer: (p) async => (UserQuery()..where!.id.equals(int.parse(p))) 11 | .getOne(app.container!.make()) 12 | .then((value) => value.isNotEmpty ? value.value : User(email: '')), 13 | serializer: (p) => p.id ?? '', 14 | ); 15 | await auth.configureServer(app); 16 | } 17 | -------------------------------------------------------------------------------- /api/lib/src/config/plugins/orm.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'package:angel3_framework/angel3_framework.dart'; 4 | import 'package:angel3_orm/angel3_orm.dart'; 5 | import 'package:angel3_orm_postgres/angel3_orm_postgres.dart'; 6 | import 'package:logging/logging.dart'; 7 | import 'package:postgres/postgres.dart'; 8 | 9 | const times = ['', 'st', 'nd', 'rd']; 10 | 11 | const retryTimeOut = Duration(seconds: 30); 12 | 13 | Future configureServer(Angel app) async { 14 | final _log = Logger('OrmPlugin'); 15 | late PostgreSQLConnection connection; 16 | var _startTime = DateTime.now(); 17 | var _retry = 1; 18 | while (_startTime.difference(DateTime.now()).abs() <= retryTimeOut) { 19 | try { 20 | connection = await connectToPostgres(app.configuration); 21 | await connection.open(); 22 | break; 23 | } catch (e, st) { 24 | await connection.close(); 25 | _log.severe( 26 | 'Failed to connect, the $_retry${_retry <= 3 ? times[_retry] : 'th'} retry will do in a second.', e, st); 27 | sleep(Duration(seconds: 1)); 28 | _retry++; 29 | if (_startTime.difference(DateTime.now()).abs() > retryTimeOut) rethrow; 30 | } 31 | } 32 | 33 | var logger = app.environment.isProduction ? null : app.logger; 34 | var executor = PostgreSqlExecutor(connection, logger: logger); 35 | 36 | app 37 | ..container!.registerSingleton(executor) 38 | ..shutdownHooks.add((_) => connection.close()); 39 | } 40 | 41 | Future connectToPostgres(Map configuration) async { 42 | var postgresConfig = configuration['postgres'] as Map? ?? {}; 43 | var connection = PostgreSQLConnection( 44 | postgresConfig['host'] as String? ?? 'localhost', 45 | postgresConfig['port'] as int? ?? 5432, 46 | postgresConfig['database_name'] as String? ?? 47 | Platform.environment['USER'] ?? 48 | Platform.environment['USERNAME'] ?? 49 | '', 50 | username: postgresConfig['username'] as String?, 51 | password: postgresConfig['password'] as String?, 52 | timeZone: postgresConfig['time_zone'] as String? ?? 'UTC', 53 | timeoutInSeconds: postgresConfig['timeout_in_seconds'] as int? ?? 30, 54 | useSSL: postgresConfig['use_ssl'] as bool? ?? false); 55 | return connection; 56 | } 57 | -------------------------------------------------------------------------------- /api/lib/src/config/plugins/plugins.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:angel3_framework/angel3_framework.dart'; 3 | import 'orm.dart' as orm; 4 | import 'jwt.dart' as jwt; 5 | import 'redis_cache.dart' as redis_cache; 6 | 7 | Future configureServer(Angel app) async { 8 | // Include any plugins you have made here. 9 | await app.configure(orm.configureServer); 10 | await app.configure(jwt.configureServer); 11 | await app.configure(redis_cache.configureServer); 12 | } 13 | -------------------------------------------------------------------------------- /api/lib/src/config/plugins/redis_cache.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:angel3_framework/angel3_framework.dart'; 4 | import 'package:logging/logging.dart'; 5 | import 'package:neat_cache/neat_cache.dart'; 6 | 7 | Future configureServer(Angel app) async { 8 | final _log = Logger('RedisPlugin'); 9 | 10 | if (app.container == null) { 11 | _log.severe('Angel3 container is null'); 12 | throw StateError('Angel.container is null. All authentication will fail.'); 13 | } 14 | var appContainer = app.container!; 15 | final cache = RedisCache(app.configuration); 16 | appContainer.registerSingleton(cache); 17 | } 18 | 19 | class RedisCache { 20 | late Cache cache; 21 | 22 | RedisCache(Map config) { 23 | var redisConfig = config['redis'] as Map? ?? {}; 24 | 25 | final cacheProvider = Cache.redisCacheProvider( 26 | Uri( 27 | scheme: 'redis', 28 | host: redisConfig['host'], 29 | port: redisConfig['port'], 30 | userInfo: redisConfig['password'], 31 | ), 32 | commandTimeLimit: const Duration(seconds: 1), 33 | ); 34 | cache = Cache(cacheProvider).withCodec(utf8); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /api/lib/src/models/app_version.dart: -------------------------------------------------------------------------------- 1 | import 'package:angel3_orm/angel3_orm.dart'; 2 | import 'package:angel3_serialize/angel3_serialize.dart'; 3 | import 'package:dde_gesture_manager_api/src/models/base_model.dart'; 4 | import 'package:angel3_migration/angel3_migration.dart'; 5 | import 'package:optional/optional.dart'; 6 | part 'app_version.g.dart'; 7 | 8 | @serializable 9 | @orm 10 | abstract class _AppVersion extends BaseModel { 11 | @SerializableField(isNullable: false) 12 | @Column(isNullable: false) 13 | String? get versionName; 14 | 15 | @SerializableField(isNullable: false) 16 | @Column(isNullable: false) 17 | int? get versionCode; 18 | } 19 | 20 | @serializable 21 | @Orm(tableName: 'app_versions', generateMigrations: false) 22 | abstract class _AppVersionResp { 23 | @SerializableField(isNullable: false) 24 | String? get versionName; 25 | 26 | @SerializableField(isNullable: false) 27 | int? get versionCode; 28 | } -------------------------------------------------------------------------------- /api/lib/src/models/base_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:angel3_orm/angel3_orm.dart'; 2 | import 'package:angel3_serialize/angel3_serialize.dart'; 3 | 4 | abstract class BaseModel extends Model { 5 | @SerializableField(isNullable: true) 6 | @Column(type: ColumnType.json) 7 | Map? get metadata; 8 | } 9 | -------------------------------------------------------------------------------- /api/lib/src/models/download_history.dart: -------------------------------------------------------------------------------- 1 | import 'package:angel3_orm/angel3_orm.dart'; 2 | import 'package:angel3_serialize/angel3_serialize.dart'; 3 | import 'package:dde_gesture_manager_api/src/models/base_model.dart'; 4 | import 'package:angel3_migration/angel3_migration.dart'; 5 | import 'package:optional/optional.dart'; 6 | 7 | part 'download_history.g.dart'; 8 | 9 | @serializable 10 | @orm 11 | abstract class _DownloadHistory extends BaseModel { 12 | @Column(isNullable: false, indexType: IndexType.standardIndex) 13 | @SerializableField(isNullable: false) 14 | int? get uid; 15 | 16 | @Column(isNullable: false, indexType: IndexType.standardIndex) 17 | @SerializableField(isNullable: false) 18 | int? get schemeId; 19 | } -------------------------------------------------------------------------------- /api/lib/src/models/like_record.dart: -------------------------------------------------------------------------------- 1 | import 'package:angel3_orm/angel3_orm.dart'; 2 | import 'package:angel3_serialize/angel3_serialize.dart'; 3 | import 'package:dde_gesture_manager_api/src/models/base_model.dart'; 4 | import 'package:angel3_migration/angel3_migration.dart'; 5 | import 'package:optional/optional.dart'; 6 | 7 | part 'like_record.g.dart'; 8 | 9 | @serializable 10 | @orm 11 | abstract class _LikeRecord extends BaseModel { 12 | @Column(isNullable: false, indexType: IndexType.standardIndex) 13 | @SerializableField(isNullable: false) 14 | int? get uid; 15 | 16 | @Column(isNullable: false, indexType: IndexType.standardIndex) 17 | @SerializableField(isNullable: false) 18 | int? get schemeId; 19 | 20 | @Column(isNullable: false, indexType: IndexType.standardIndex) 21 | @SerializableField(isNullable: false) 22 | bool? get liked; 23 | } 24 | 25 | 26 | @serializable 27 | @Orm(tableName: 'like_records', generateMigrations: false) 28 | abstract class _UserLikes { 29 | @Column(isNullable: false) 30 | @SerializableField(isNullable: false) 31 | int? id; 32 | 33 | @Column(isNullable: false) 34 | @SerializableField(exclude: true) 35 | int? get uid; 36 | 37 | @Column(isNullable: false) 38 | @SerializableField(exclude: true) 39 | bool? get liked; 40 | } -------------------------------------------------------------------------------- /api/lib/src/models/login_success.dart: -------------------------------------------------------------------------------- 1 | import 'package:angel3_serialize/angel3_serialize.dart'; 2 | 3 | part 'login_success.g.dart'; 4 | 5 | @serializable 6 | class _LoginSuccess { 7 | @SerializableField(isNullable: false) 8 | String? token; 9 | } -------------------------------------------------------------------------------- /api/lib/src/models/scheme.dart: -------------------------------------------------------------------------------- 1 | import 'package:angel3_orm/angel3_orm.dart'; 2 | import 'package:angel3_serialize/angel3_serialize.dart'; 3 | import 'package:dde_gesture_manager_api/src/models/base_model.dart'; 4 | import 'package:angel3_migration/angel3_migration.dart'; 5 | import 'package:optional/optional.dart'; 6 | 7 | part 'scheme.g.dart'; 8 | 9 | @serializable 10 | @orm 11 | abstract class _Scheme extends BaseModel { 12 | @Column(isNullable: false, indexType: IndexType.unique) 13 | @SerializableField(isNullable: false) 14 | String? get uuid; 15 | 16 | @Column(isNullable: false) 17 | @SerializableField(isNullable: false) 18 | String? get name; 19 | 20 | @Column(isNullable: false, indexType: IndexType.standardIndex) 21 | @SerializableField(isNullable: true, exclude: true) 22 | int? uid; 23 | 24 | @Column(type: ColumnType.text) 25 | String? description; 26 | 27 | @Column(isNullable: false, indexType: IndexType.standardIndex) 28 | @SerializableField(defaultValue: false, isNullable: false) 29 | bool? get shared; 30 | 31 | @Column(type: ColumnType.jsonb) 32 | @SerializableField() 33 | @DefaultsTo([]) 34 | List? get gestures; 35 | } 36 | 37 | @serializable 38 | @Orm(tableName: 'schemes', generateMigrations: false) 39 | abstract class _SimpleScheme { 40 | @Column() 41 | int? id; 42 | 43 | @Column(isNullable: false, indexType: IndexType.unique) 44 | @SerializableField(isNullable: false) 45 | String? get uuid; 46 | 47 | @Column(isNullable: false) 48 | @SerializableField(isNullable: false) 49 | String? get name; 50 | 51 | @Column(isNullable: false, indexType: IndexType.standardIndex) 52 | @SerializableField(isNullable: true, exclude: true) 53 | int? uid; 54 | 55 | @Column(type: ColumnType.text) 56 | String? description; 57 | 58 | @Column(isNullable: false, indexType: IndexType.standardIndex) 59 | @SerializableField(defaultValue: false, isNullable: false) 60 | bool? get shared; 61 | 62 | @SerializableField(isNullable: true) 63 | @Column(type: ColumnType.json) 64 | Map? get metadata; 65 | 66 | @SerializableField(isNullable: true) 67 | @Column(expression: 'lr.liked') 68 | bool? get liked; 69 | } 70 | 71 | @serializable 72 | abstract class _SimpleSchemeTransMetaData { 73 | @SerializableField(isNullable: false) 74 | int? id; 75 | 76 | @SerializableField(isNullable: false) 77 | String? get uuid; 78 | 79 | @SerializableField(isNullable: false) 80 | String? get name; 81 | 82 | @SerializableField(isNullable: false) 83 | String? description; 84 | 85 | @SerializableField(defaultValue: false, isNullable: false) 86 | bool? get shared; 87 | 88 | int? get downloads; 89 | 90 | int? get likes; 91 | 92 | bool get liked; 93 | } 94 | 95 | SimpleSchemeTransMetaData transSimpleSchemeMetaData(SimpleScheme scheme) => SimpleSchemeTransMetaData( 96 | id: scheme.id, 97 | description: scheme.description, 98 | uuid: scheme.uuid, 99 | name: scheme.name, 100 | shared: scheme.shared, 101 | liked: scheme.liked ?? false, 102 | likes: scheme.metadata?['likes'] ?? 0, 103 | downloads: scheme.metadata?['downloads'] ?? 0, 104 | ); 105 | 106 | @serializable 107 | abstract class _SchemeForDownload { 108 | @SerializableField(isNullable: false) 109 | String? get uuid; 110 | 111 | @SerializableField(isNullable: false) 112 | String? get name; 113 | 114 | @SerializableField(defaultValue: false, isNullable: false) 115 | bool? get shared; 116 | 117 | @Column(type: ColumnType.text) 118 | String? description; 119 | 120 | @SerializableField() 121 | @DefaultsTo([]) 122 | List? get gestures; 123 | } 124 | 125 | SchemeForDownload transSchemeForDownload(Scheme scheme) => SchemeForDownload( 126 | uuid: scheme.uuid, 127 | name: scheme.name, 128 | description: scheme.description, 129 | gestures: scheme.gestures, 130 | shared: scheme.shared, 131 | ); 132 | 133 | @serializable 134 | @Orm(tableName: 'schemes', generateMigrations: false) 135 | abstract class _MarketScheme { 136 | @Column() 137 | int? id; 138 | 139 | @Column(isNullable: false, indexType: IndexType.unique) 140 | @SerializableField(isNullable: false) 141 | String? get uuid; 142 | 143 | @Column(isNullable: false) 144 | @SerializableField(isNullable: false) 145 | String? get name; 146 | 147 | @Column(type: ColumnType.text) 148 | String? description; 149 | 150 | @Column(isNullable: false) 151 | @SerializableField(exclude: true) 152 | bool? get shared; 153 | 154 | @SerializableField(isNullable: true) 155 | @Column(type: ColumnType.json) 156 | Map? get metadata; 157 | } 158 | 159 | @serializable 160 | abstract class _MarketSchemeTransMetaData { 161 | @SerializableField(isNullable: false) 162 | int? id; 163 | 164 | @SerializableField(isNullable: false) 165 | String? get uuid; 166 | 167 | @SerializableField(isNullable: false) 168 | String? get name; 169 | 170 | @SerializableField(isNullable: false) 171 | String? description; 172 | 173 | int? get downloads; 174 | 175 | int? get likes; 176 | } 177 | 178 | MarketSchemeTransMetaData transMarketSchemeMetaData(MarketScheme scheme) => MarketSchemeTransMetaData( 179 | id: scheme.id, 180 | description: scheme.description, 181 | uuid: scheme.uuid, 182 | name: scheme.name, 183 | likes: scheme.metadata?['likes'] ?? 0, 184 | downloads: scheme.metadata?['downloads'] ?? 0, 185 | ); 186 | -------------------------------------------------------------------------------- /api/lib/src/models/user.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:angel3_migration/angel3_migration.dart'; 4 | import 'package:angel3_serialize/angel3_serialize.dart'; 5 | import 'package:angel3_orm/angel3_orm.dart'; 6 | import 'package:dde_gesture_manager_api/src/models/base_model.dart'; 7 | import 'package:optional/optional.dart'; 8 | import 'package:crypto/crypto.dart'; 9 | 10 | part 'user.g.dart'; 11 | 12 | @serializable 13 | @orm 14 | abstract class _User extends BaseModel { 15 | @Column(isNullable: false, indexType: IndexType.unique) 16 | @SerializableField(isNullable: false) 17 | String? get email; 18 | 19 | @Column(isNullable: false, length: 64) 20 | @SerializableField(isNullable: true, exclude: true) 21 | String? get password; 22 | 23 | @Column(isNullable: false) 24 | @SerializableField(defaultValue: false) 25 | bool? get blocked; 26 | 27 | String secret(String salt) => base64.encode(Hmac(sha256, salt.codeUnits).convert((password ?? '').codeUnits).bytes); 28 | } 29 | -------------------------------------------------------------------------------- /api/lib/src/routes/controllers/auth_controllers.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | 4 | import 'package:angel3_auth/angel3_auth.dart'; 5 | import 'package:angel3_framework/angel3_framework.dart'; 6 | import 'package:dde_gesture_manager_api/apis.dart'; 7 | import 'package:dde_gesture_manager_api/models.dart'; 8 | import 'package:dde_gesture_manager_api/src/routes/controllers/middlewares.dart'; 9 | import 'package:logging/logging.dart'; 10 | import 'package:mailer/mailer.dart'; 11 | import 'package:mailer/smtp_server.dart'; 12 | import 'package:uuid/uuid.dart'; 13 | 14 | import 'controller_extensions.dart'; 15 | 16 | Future configureServer(Angel app) async { 17 | app.post(Apis.auth.loginOrSignup, (req, res) async { 18 | var userParams = UserSerializer.fromMap(req.bodyAsMap); 19 | userParams.password = req.bodyAsMap[UserFields.password]; 20 | var userQuery = UserQuery(); 21 | userQuery.where?.email.equals(userParams.email ?? ''); 22 | var user = await userQuery.getOne(req.queryExecutor); 23 | 24 | if (user.isEmpty) { 25 | String accessKey = Uuid().v1(); 26 | 27 | await req.cache 28 | .withPrefix('sign_up:')[accessKey] 29 | .set(json.encode({'email': userParams.email, 'password': userParams.password}), Duration(minutes: 30)); 30 | var smtpConfig = app.configuration['smtp']; 31 | var smtpServer = 32 | SmtpServer(smtpConfig['host'], ssl: true, username: smtpConfig['username'], password: smtpConfig['password']); 33 | var message = Message() 34 | ..from = Address(smtpConfig['username']) 35 | ..recipients.add(userParams.email) 36 | ..subject = '确认注册' 37 | ..html = await app.viewGenerator!( 38 | 'confirm_sign_up.html', 39 | { 40 | "confirm_url": Uri( 41 | scheme: Apis.apiScheme, 42 | host: Apis.apiHost, 43 | port: Apis.apiPort, 44 | path: Apis.auth.confirmSignup(accessKey: accessKey.param), 45 | ), 46 | }, 47 | ); 48 | 49 | SendReport report = await send(message, smtpServer); 50 | Logger('auth_controller').info(report); 51 | 52 | return res.notFound(); 53 | } else if (user.value.password != userParams.password) { 54 | return res.unauthorized(); 55 | } else if (user.value.blocked == true) { 56 | return res.forbidden(); 57 | } else { 58 | var angelAuth = req.container!.make(); 59 | await angelAuth.loginById(user.value.id!, req, res); 60 | var authToken = req.container!.make(); 61 | authToken.payload[UserFields.password] = user.value.secret(app.configuration['password_salt']); 62 | var serializedToken = authToken.serialize(angelAuth.hmac); 63 | return res.json(LoginSuccess(token: serializedToken)); 64 | } 65 | }); 66 | 67 | app.get(Apis.auth.confirmSignup.route, (req, res) async { 68 | var accessKey = req.params['accessKey']; 69 | var cache = req.cache.withPrefix('sign_up:'); 70 | var signupInfo = await cache[accessKey].get(); 71 | if (signupInfo != null && signupInfo is String && signupInfo.isNotEmpty) { 72 | var decodedSignupInfo = json.decode(signupInfo); 73 | var userQuery = UserQuery(); 74 | userQuery.values.copyFrom(User( 75 | email: decodedSignupInfo[UserFields.email], 76 | password: decodedSignupInfo[UserFields.password], 77 | )); 78 | await userQuery.insert(req.queryExecutor); 79 | cache[accessKey].purge(); 80 | return res.render('sign_up_result.html', {'success': true}); 81 | } 82 | return res.render('sign_up_result.html', {'success': false}); 83 | }); 84 | 85 | app.get( 86 | Apis.auth.status, 87 | chain( 88 | [ 89 | jwtMiddleware(), 90 | (req, res) => req.user!.blocked == false ? res.noContent() : res.forbidden(), 91 | ], 92 | ), 93 | ); 94 | } 95 | -------------------------------------------------------------------------------- /api/lib/src/routes/controllers/controller_extensions.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:angel3_framework/angel3_framework.dart'; 4 | import 'package:angel3_orm/angel3_orm.dart' as orm; 5 | import 'package:dde_gesture_manager_api/models.dart'; 6 | import 'package:dde_gesture_manager_api/src/config/plugins/redis_cache.dart'; 7 | import 'package:neat_cache/neat_cache.dart'; 8 | 9 | extension ResponseNoContent on ResponseContext { 10 | noContent() { 11 | statusCode = HttpStatus.noContent; 12 | return close(); 13 | } 14 | 15 | notFound() { 16 | statusCode = HttpStatus.notFound; 17 | return close(); 18 | } 19 | 20 | unauthorized() { 21 | statusCode = HttpStatus.unauthorized; 22 | return close(); 23 | } 24 | 25 | forbidden() { 26 | statusCode = HttpStatus.forbidden; 27 | return close(); 28 | } 29 | 30 | unProcessableEntity() { 31 | statusCode = HttpStatus.unprocessableEntity; 32 | return close(); 33 | } 34 | } 35 | 36 | extension QueryWhereId on orm.Query { 37 | set whereId(int id) { 38 | (where as dynamic).id.equals(id); 39 | } 40 | } 41 | 42 | extension QueryExecutor on RequestContext { 43 | orm.QueryExecutor get queryExecutor => container!.make(); 44 | } 45 | 46 | extension RedisClient on RequestContext { 47 | Cache get cache => container!.make().cache; 48 | } 49 | 50 | extension JWTUserInstance on RequestContext { 51 | User? get user { 52 | try { 53 | return container!.make(); 54 | } catch (_) { 55 | return null; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /api/lib/src/routes/controllers/middlewares.dart: -------------------------------------------------------------------------------- 1 | import 'package:angel3_auth/angel3_auth.dart'; 2 | import 'package:angel3_framework/angel3_framework.dart'; 3 | 4 | import 'package:dde_gesture_manager_api/models.dart'; 5 | import '../controllers/controller_extensions.dart'; 6 | 7 | RequestHandler jwtMiddleware({ignoreError = false}) { 8 | return (RequestContext req, ResponseContext res, {bool throwError = true}) async { 9 | bool _reject(ResponseContext res, [ignoreError = false]) { 10 | if (ignoreError) return true; 11 | if (throwError) { 12 | res.forbidden(); 13 | } 14 | return false; 15 | } 16 | 17 | if (req.container != null) { 18 | var reqContainer = req.container!; 19 | if (reqContainer.has() || req.method == 'OPTIONS') { 20 | return true; 21 | } else if (reqContainer.has>()) { 22 | try { 23 | User user = await reqContainer.makeAsync(); 24 | var authToken = req.container!.make(); 25 | if (user.secret(req.app!.configuration['password_salt']) != authToken.payload[UserFields.password]) { 26 | return _reject(res, ignoreError); 27 | } 28 | } catch (e) { 29 | if (ignoreError) return true; 30 | rethrow; 31 | } 32 | return true; 33 | } else { 34 | return _reject(res, ignoreError); 35 | } 36 | } else { 37 | return _reject(res, ignoreError); 38 | } 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /api/lib/src/routes/controllers/system_controllers.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:angel3_framework/angel3_framework.dart'; 4 | import 'package:dde_gesture_manager_api/apis.dart'; 5 | import 'package:dde_gesture_manager_api/src/models/app_version.dart'; 6 | import 'controller_extensions.dart'; 7 | 8 | Future configureServer(Angel app) async { 9 | app.get( 10 | Apis.system.appVersion, 11 | (req, res) async { 12 | var appVersionQuery = AppVersionQuery(); 13 | appVersionQuery.orderBy(AppVersionFields.versionCode, descending: true); 14 | return appVersionQuery.getOne(req.queryExecutor).then((value) => AppVersionResp( 15 | versionName: value.value.versionName, 16 | versionCode: value.value.versionCode, 17 | )); 18 | }, 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /api/lib/src/routes/routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:angel3_framework/angel3_framework.dart'; 2 | import 'package:angel3_cors/angel3_cors.dart'; 3 | import 'package:angel3_static/angel3_static.dart'; 4 | import 'package:file/file.dart'; 5 | import 'controllers/auth_controllers.dart' as auth_controllers; 6 | import 'controllers/system_controllers.dart' as system_controllers; 7 | import 'controllers/scheme_controllers.dart' as scheme_controllers; 8 | 9 | /// Put your app routes here! 10 | /// 11 | /// See the wiki for information about routing, requests, and responses: 12 | /// * https://angel3-docs.dukefirehawk.com/guides/basic-routing 13 | /// * https://angel3-docs.dukefirehawk.com/guides/requests-and-responses 14 | 15 | AngelConfigurer configureServer(FileSystem fileSystem) { 16 | return (Angel app) async { 17 | // ParseBody middleware 18 | app.fallback((req, res) async { 19 | if (req.method == "POST") { 20 | await req.parseBody(); 21 | } 22 | return true; 23 | }); 24 | 25 | app.fallback(cors()); 26 | 27 | // Typically, you want to mount controllers first, after any global middleware. 28 | await app.configure(system_controllers.configureServer); 29 | await app.configure(auth_controllers.configureServer); 30 | await app.configure(scheme_controllers.configureServer); 31 | 32 | var vDir = VirtualDirectory(app, fileSystem, source: fileSystem.directory('web')); 33 | app.fallback(vDir.handleRequest); 34 | 35 | // Throw a 404 if no route matched the request. 36 | app.fallback((req, res) => throw AngelHttpException.notFound()); 37 | 38 | // Set our application up to handle different errors. 39 | // 40 | // Read the following for documentation: 41 | // * https://angel3-docs.dukefirehawk.com/guides/error-handling 42 | 43 | var oldErrorHandler = app.errorHandler; 44 | app.errorHandler = (e, req, res) async { 45 | if (req.accepts('text/html', strict: true)) { 46 | if (e.statusCode == 404 && req.accepts('text/html', strict: true)) { 47 | await res.render('error.html', {'message': 'No router exists for ${req.uri}'}); 48 | } else { 49 | return await res.render('error.html', { 50 | 'message': [e.message, '', e.stackTrace.toString().replaceAll('\n', '
')].join('
') 51 | }); 52 | } 53 | } else { 54 | return await oldErrorHandler(e, req, res); 55 | } 56 | }; 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /api/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: dde_gesture_manager_api 2 | version: 1.0.0 3 | description: An ORM starter application for Angel3 framework 4 | publish_to: none 5 | environment: 6 | sdk: '>=2.15.0 <3.0.0' 7 | dependencies: 8 | angel3_auth: ^4.0.0 9 | angel3_configuration: ^4.1.0 10 | angel3_framework: ^4.2.0 11 | angel3_migration: ^4.0.3 12 | angel3_orm: ^4.0.6 13 | angel3_orm_postgres: ^3.3.0 14 | angel3_serialize: ^4.1.0 15 | angel3_static: ^4.1.0 16 | angel3_production: ^3.1.2 17 | belatuk_pretty_logging: ^4.0.0 18 | optional: ^6.0.0 19 | logging: ^1.0.0 20 | mailer: ^5.0.2 21 | uuid: ^3.0.5 22 | angel3_cors: ^4.1.0 23 | neat_cache: 24 | path: 3rd_party/neat_cache 25 | dev_dependencies: 26 | angel3_hot: ^4.3.0 27 | angel3_jinja: ^2.0.1 28 | angel3_migration_runner: ^4.1.1 29 | angel3_orm_generator: ^4.3.0 30 | angel3_serialize_generator: ^4.3.0 31 | angel3_test: ^4.0.0 32 | build_runner: ^2.0.3 33 | io: ^1.0.0 34 | test: ^1.17.5 35 | lints: ^1.0.0 36 | -------------------------------------------------------------------------------- /api/source_gen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | dart pub get 4 | dart run build_runner build -------------------------------------------------------------------------------- /api/start.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | docker-compose build 4 | docker-compose up -d 5 | MIGRATION_IMAGE="$(docker image ls --filter label=stage=dart_builder -q)" 6 | docker run --name=dgm_api_migrate --network dgm_api_default "$MIGRATION_IMAGE" 7 | docker rm "$(docker ps -a --filter name=dgm_api_migrate -q)" 8 | docker image prune -f --filter label=stage=dart_builder 9 | docker image prune -f -------------------------------------------------------------------------------- /api/test/all_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:dde_gesture_manager_api/dde_gesture_manager_api.dart'; 2 | import 'package:angel3_framework/angel3_framework.dart'; 3 | import 'package:angel3_test/angel3_test.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | // Angel also includes facilities to make testing easier. 7 | // 8 | // `package:angel_test` ships a client that can test 9 | // both plain HTTP and WebSockets. 10 | // 11 | // Tests do not require your server to actually be mounted on a port, 12 | // so they will run faster than they would in other frameworks, where you 13 | // would have to first bind a socket, and then account for network latency. 14 | // 15 | // See the documentation here: 16 | // https://github.com/angel-dart/test 17 | // 18 | // If you are unfamiliar with Dart's advanced testing library, you can read up 19 | // here: 20 | // https://github.com/dart-lang/test 21 | 22 | void main() async { 23 | late TestClient client; 24 | 25 | setUp(() async { 26 | var app = Angel(); 27 | await app.configure(configureServer); 28 | 29 | client = await connectTo(app); 30 | }); 31 | 32 | tearDown(() async { 33 | await client.close(); 34 | }); 35 | 36 | test('index returns 200', () async { 37 | // Request a resource at the given path. 38 | var response = await client.get(Uri.parse('/')); 39 | 40 | // Expect a 200 response. 41 | expect(response, hasStatus(200)); 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /api/views/confirm_sign_up.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block title %}确认注册{% endblock %} 3 | {% block body %} 4 |

确认注册

5 |

如果是您本人点击了《DDE手势管理器》的登录/注册按钮,并收到了本邮件,请点击下面的链接以完成注册,完成后请回到软件中使用之前填入的邮箱和密码登录账户。

6 |

如果您对上面的操作并不知情,则可能是其他用户错误使用了您的邮箱地址进行了注册,请无视本邮件,不要点击下面的链接,谢谢合作~

7 | {{ confirm_url }} 8 | {% endblock %} -------------------------------------------------------------------------------- /api/views/error.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block title %}Error{% endblock %} 3 | {% block body %} 4 |

Error: {{ message }}

5 | {% endblock %} -------------------------------------------------------------------------------- /api/views/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% block title %}{% endblock %} 6 | 7 | 8 | 9 | 10 | 11 | {% block body %}{% endblock %} 12 | 13 | -------------------------------------------------------------------------------- /api/views/sign_up_result.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block title %}注册结果{% endblock %} 3 | {% block body %} 4 |

注册结果

5 | {% if success %} 6 |

注册成功~

7 | {% else %} 8 |

注册失败..

9 |

可能是因为:

10 |
    11 |
  • 本链接已经超过三十分钟的有效期,请重新在软件中点击注册按钮
  • 12 |
  • 本链接已经被点击并注册成功,请勿重复点击
  • 13 |
  • 其他错误……非常抱歉,如果方便的话请通过邮件联系我~
  • 14 |
15 | {% endif%} 16 | {% endblock %} -------------------------------------------------------------------------------- /api/web/css/site.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | padding: 0; 4 | margin: 0; 5 | } 6 | 7 | body { 8 | margin: .5em; 9 | } -------------------------------------------------------------------------------- /api/web/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debuggerx01/dde_gesture_manager/d2c699a4fe4b06bca67b16b8d785e69be8f406d1/api/web/images/favicon.png -------------------------------------------------------------------------------- /api/web/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /admin -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | pubspec.lock 34 | 35 | # Web related 36 | lib/generated_plugin_registrant.dart 37 | 38 | # Symbolication related 39 | app.*.symbols 40 | 41 | # Obfuscation related 42 | app.*.map.json 43 | 44 | # Android Studio will place build artifacts here 45 | /android/app/debug 46 | /android/app/profile 47 | /android/app/release 48 | 49 | /deb_builder/ 50 | *.deb 51 | -------------------------------------------------------------------------------- /app/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: f4abaa0735eba4dfd8f33f73363911d63931fe03 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /app/3rd_party/cherry_toast/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.0.2] - 19/08/2021 2 | 3 | * Add heartbeat animation on icon 4 | * Customizable icon size and color 5 | * Bug fixes 6 | 7 | ## [1.0.1] - 24/07/2021 8 | 9 | * Add customizable border radius 10 | 11 | ## [1.0.0] - 20/07/2021 12 | 13 | * Add support to all platforms 14 | * Add support to null safety 15 | * Add multiple built-in themes 16 | * Add built-in animations 17 | * Support LTR and RTL layout rendering 18 | * Support Top and Bottom toast display 19 | * Full customization to title, description and action components 20 | -------------------------------------------------------------------------------- /app/3rd_party/cherry_toast/lib/cherry_toast_icon.dart: -------------------------------------------------------------------------------- 1 | import 'package:cherry_toast/resources/constants.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class CherryToatIcon extends StatefulWidget { 5 | ///the color that will be applied on the circle behind the icon 6 | ///(required) 7 | final Color color; 8 | 9 | ///The toast icon widget (required) 10 | /// 11 | final IconData icon; 12 | 13 | ///the size of the icon (required) 14 | /// 15 | final double iconSize; 16 | 17 | ///the icon color (required) 18 | final Color iconColor; 19 | 20 | ///define wether the animation will be applied on the icon or not 21 | /// 22 | final bool enableAnimation; 23 | 24 | CherryToatIcon( 25 | {required this.color, 26 | required this.icon, 27 | required this.iconSize, 28 | required this.iconColor, 29 | required this.enableAnimation}); 30 | 31 | @override 32 | _CherryToatIconState createState() => _CherryToatIconState(); 33 | } 34 | 35 | class _CherryToatIconState extends State 36 | with TickerProviderStateMixin { 37 | late Animation _heartAnimation; 38 | late AnimationController _heartAnimationController; 39 | 40 | @override 41 | void initState() { 42 | super.initState(); 43 | if (this.widget.enableAnimation) { 44 | _heartAnimationController = AnimationController( 45 | vsync: this, duration: Duration(milliseconds: 1200)); 46 | _heartAnimation = Tween( 47 | begin: this.widget.iconSize * 0.7, 48 | end: this.widget.iconSize * 0.95) 49 | .animate(CurvedAnimation( 50 | curve: Curves.bounceOut, parent: _heartAnimationController)); 51 | 52 | _heartAnimationController.addStatusListener((AnimationStatus status) { 53 | if (status == AnimationStatus.completed) { 54 | _heartAnimationController.repeat(); 55 | } 56 | }); 57 | _heartAnimationController.forward(); 58 | } 59 | } 60 | 61 | @override 62 | Widget build(BuildContext context) { 63 | return Container( 64 | width: DEFAULT_ICON_LAYOUT_SIZE, 65 | height: DEFAULT_ICON_LAYOUT_SIZE, 66 | decoration: BoxDecoration( 67 | shape: BoxShape.circle, color: this.widget.color.withAlpha(20)), 68 | child: Center( 69 | child: this.widget.enableAnimation 70 | ? AnimatedBuilder( 71 | builder: (context, child) { 72 | return Icon(this.widget.icon, 73 | size: this._heartAnimation.value, 74 | color: this.widget.iconColor); 75 | }, 76 | animation: this._heartAnimationController, 77 | ) 78 | : Icon(this.widget.icon, 79 | size: this.widget.iconSize, color: this.widget.iconColor)), 80 | ); 81 | } 82 | 83 | @override 84 | void dispose() { 85 | if (this.widget.enableAnimation) { 86 | _heartAnimationController.dispose(); 87 | } 88 | super.dispose(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /app/3rd_party/cherry_toast/lib/resources/arrays.dart: -------------------------------------------------------------------------------- 1 | enum CHERRY_TYPE { SUCCESS, WARINING, ERROR, INFO, CUSTOM } 2 | 3 | enum POSITION { TOP, BOTTOM } 4 | 5 | enum ANIMATION_TYPE { FROM_TOP, FROM_LEFT, FROM_RIGHT } 6 | 7 | enum TOAST_LAYOUT { LTR, RTL } 8 | -------------------------------------------------------------------------------- /app/3rd_party/cherry_toast/lib/resources/colors.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | const Color ERROR_COLOR = Color(0xffE43837); 4 | const Color SUCCESS_COLOR = Color(0xFF2F9449); 5 | const Color INFO_COLOR = Color(0xFF4E5CB9); 6 | const Color WARINING_COLOR = Color(0xffFC9F00); 7 | -------------------------------------------------------------------------------- /app/3rd_party/cherry_toast/lib/resources/constants.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | const String PACKAGE_NAME = "cherry_toast"; 4 | 5 | const TextStyle DEFAULT_TITLTE_STYLE = 6 | const TextStyle(color: Colors.black, fontWeight: FontWeight.bold); 7 | const TextStyle DEFAULT_DESCRIPTION_STYLE = 8 | const TextStyle(color: Colors.black); 9 | 10 | const Duration DEFAULT_ANIMATION_DURATION = Duration(milliseconds: 1500); 11 | const Duration DEFAULT_TOAST_DURATION = Duration(milliseconds: 3000); 12 | const Cubic DEFAULT_ANIMATION_CURVE = Curves.ease; 13 | 14 | const double CLOSE_BUTTON_SIZE = 15; 15 | 16 | const double DEFAULT_ICON_SIZE = 20; 17 | const double DEFAULT_ICON_LAYOUT_SIZE = 40; 18 | const double DEFAULT_RADIUS = 20; 19 | -------------------------------------------------------------------------------- /app/3rd_party/cherry_toast/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: cherry_toast 2 | description: A new way to display toasts in flutter with elegant design and animations 3 | version: 1.0.2 4 | homepage: https://github.com/koukibadr/Cherry-Toast 5 | 6 | environment: 7 | sdk: '>=2.12.0 <3.0.0' 8 | flutter: ">=1.17.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | dev_dependencies: 15 | flutter_test: 16 | sdk: flutter 17 | 18 | flutter: 19 | -------------------------------------------------------------------------------- /app/3rd_party/markdown_core/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.0 2 | 3 | * first version 4 | -------------------------------------------------------------------------------- /app/3rd_party/markdown_core/README.md: -------------------------------------------------------------------------------- 1 | # markdown_core 2 | 3 | Parse markdown and render it into rich text. 4 | 5 | ![show](https://xia-weiyang.github.io/gif/markdown_core.gif) 6 | 7 | ``` dart 8 | Markdown( 9 | data: markdownDataString, 10 | linkTap: (link) => print('点击了链接 $link'), 11 | textStyle: // your text style , 12 | image: (imageUrl) { 13 | print('imageUrl $imageUrl'); 14 | return // Your image widget ; 15 | }, 16 | ) 17 | ``` 18 | -------------------------------------------------------------------------------- /app/3rd_party/markdown_core/lib/markdown.dart: -------------------------------------------------------------------------------- 1 | library markdown_core; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:markdown/markdown.dart'; 5 | import 'package:markdown_core/builder.dart'; 6 | import 'package:markdown_core/text_style.dart'; 7 | 8 | class Markdown extends StatefulWidget { 9 | const Markdown({ 10 | Key? key, 11 | required this.data, 12 | required this.linkTap, 13 | required this.image, 14 | required this.onCodeCopied, 15 | this.maxWidth, 16 | this.textStyle, 17 | this.richTap, 18 | }) : super(key: key); 19 | 20 | final String data; 21 | 22 | final LinkTap linkTap; 23 | 24 | final WidgetImage image; 25 | 26 | final double? maxWidth; 27 | 28 | final TextStyle? textStyle; 29 | 30 | final Function onCodeCopied; 31 | 32 | final VoidCallback? richTap; 33 | 34 | @override 35 | MarkdownState createState() => MarkdownState(); 36 | } 37 | 38 | class MarkdownState extends State { 39 | @override 40 | void initState() { 41 | super.initState(); 42 | } 43 | 44 | @override 45 | Widget build(BuildContext context) { 46 | return Column( 47 | crossAxisAlignment: CrossAxisAlignment.start, 48 | children: _parseMarkdown(), 49 | ); 50 | } 51 | 52 | List _parseMarkdown() { 53 | // debugPrint(markdownToHtml( 54 | // widget.data, 55 | // extensionSet: ExtensionSet.gi法inaltHubWeb, 56 | // )); 57 | final List lines = widget.data.split(RegExp(r'\r?\n')); 58 | final nodes = Document( 59 | extensionSet: ExtensionSet.gitHubWeb, 60 | ).parseLines(lines); 61 | return MarkdownBuilder( 62 | context, 63 | widget.linkTap, 64 | widget.image, 65 | widget.maxWidth ?? MediaQuery.of(context).size.width, 66 | widget.textStyle ?? defaultTextStyle(context), 67 | onCodeCopied: widget.onCodeCopied, 68 | richTap: widget.richTap, 69 | ).build(nodes); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/3rd_party/markdown_core/lib/text_style.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | const List kTextTags = const [ 4 | 'h1', 5 | 'h2', 6 | 'h3', 7 | 'h4', 8 | 'h5', 9 | 'h6', 10 | 'p', 11 | 'code', 12 | 'strong', 13 | 'em', 14 | 'del', 15 | 'a', 16 | ]; 17 | 18 | const List kTextParentTags = const [ 19 | 'h1', 20 | 'h2', 21 | 'h3', 22 | 'h4', 23 | 'h5', 24 | 'h6', 25 | 'p', 26 | 'li', 27 | ]; 28 | 29 | TextStyle defaultTextStyle(BuildContext context) => TextStyle( 30 | fontSize: 18, 31 | height: 1.8, 32 | color: Theme.of(context).brightness == Brightness.dark 33 | ? const Color(0xffaaaaaa) 34 | : const Color(0xff444444), 35 | ); 36 | 37 | TextStyle defaultTagTextStyle(String lastTag, String tag, TextStyle textStyle) { 38 | switch (tag) { 39 | case 'h1': 40 | textStyle = textStyle.copyWith( 41 | fontSize: (textStyle.fontSize ?? 0) + 9, 42 | ); 43 | break; 44 | case 'h2': 45 | textStyle = textStyle.copyWith( 46 | fontSize: (textStyle.fontSize ?? 0) + 6, 47 | ); 48 | break; 49 | case 'h3': 50 | textStyle = textStyle.copyWith( 51 | fontSize: (textStyle.fontSize ?? 0) + 4, 52 | ); 53 | break; 54 | case 'h4': 55 | textStyle = textStyle.copyWith( 56 | fontSize: (textStyle.fontSize ?? 0) + 3, 57 | ); 58 | break; 59 | case 'h5': 60 | textStyle = textStyle.copyWith( 61 | fontSize: (textStyle.fontSize ?? 0) + 2, 62 | ); 63 | break; 64 | case 'h6': 65 | textStyle = textStyle.copyWith( 66 | fontSize: (textStyle.fontSize ?? 0) + 1, 67 | ); 68 | break; 69 | case 'p': 70 | break; 71 | case 'li': 72 | break; 73 | case 'code': 74 | textStyle = textStyle.copyWith( 75 | fontSize: (textStyle.fontSize ?? 0) - 3, 76 | color: textStyle.color?.withAlpha(200), 77 | ); 78 | if (lastTag == 'p') { 79 | textStyle = textStyle.copyWith( 80 | color: Colors.red.shade800, 81 | ); 82 | } 83 | 84 | break; 85 | case 'strong': 86 | textStyle = textStyle.copyWith( 87 | fontWeight: FontWeight.bold, 88 | ); 89 | break; 90 | case 'em': 91 | textStyle = textStyle.copyWith( 92 | fontStyle: FontStyle.italic, 93 | ); 94 | break; 95 | case 'del': 96 | textStyle = textStyle.copyWith( 97 | decoration: TextDecoration.lineThrough, 98 | ); 99 | break; 100 | case 'a': 101 | textStyle = textStyle.copyWith( 102 | color: Colors.blue, 103 | ); 104 | break; 105 | } 106 | return textStyle; 107 | } 108 | -------------------------------------------------------------------------------- /app/3rd_party/markdown_core/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: markdown_core 2 | description: Parse markdown and render it into rich text. 3 | version: 1.0.0 4 | homepage: https://github.com/xia-weiyang/markdown_core 5 | repository: https://github.com/xia-weiyang/markdown_core 6 | 7 | environment: 8 | sdk: '>=2.12.0 <3.0.0' 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | markdown: ^4.0.1 15 | html_unescape: ^2.0.0 16 | 17 | dev_dependencies: 18 | flutter_test: 19 | sdk: flutter 20 | 21 | # For information on the generic Dart part of this file, see the 22 | # following page: https://dart.dev/tools/pub/pubspec 23 | 24 | # The following section is specific to Flutter. 25 | flutter: 26 | 27 | # To add assets to your package, add an assets section, like this: 28 | # assets: 29 | # - images/a_dot_burr.jpeg 30 | # - images/a_dot_ham.jpeg 31 | # 32 | # For details regarding assets in packages, see 33 | # https://flutter.dev/assets-and-images/#from-packages 34 | # 35 | # An image asset can refer to one or more resolution-specific "variants", see 36 | # https://flutter.dev/assets-and-images/#resolution-aware. 37 | 38 | # To add custom fonts to your package, add a fonts section here, 39 | # in this "flutter" section. Each entry in this list should have a 40 | # "family" key with the font family name, and a "fonts" key with a 41 | # list giving the asset and other descriptors for the font. For 42 | # example: 43 | # fonts: 44 | # - family: Schyler 45 | # fonts: 46 | # - asset: fonts/Schyler-Regular.ttf 47 | # - asset: fonts/Schyler-Italic.ttf 48 | # style: italic 49 | # - family: Trajan Pro 50 | # fonts: 51 | # - asset: fonts/TrajanPro.ttf 52 | # - asset: fonts/TrajanPro_Bold.ttf 53 | # weight: 700 54 | # 55 | # For details regarding fonts in packages, see 56 | # https://flutter.dev/custom-fonts/#from-packages 57 | -------------------------------------------------------------------------------- /app/3rd_party/markdown_editor_ot/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .flutter-plugins 6 | .pub/ 7 | 8 | .idea/ 9 | *.iml 10 | *.lock 11 | 12 | 13 | build/ 14 | ios/ 15 | android/ 16 | -------------------------------------------------------------------------------- /app/3rd_party/markdown_editor_ot/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.2 2 | * Fix icon not display. 3 | ## 1.0.1 4 | * Change directory structure. 5 | ## 1.0.0 6 | -------------------------------------------------------------------------------- /app/3rd_party/markdown_editor_ot/README.md: -------------------------------------------------------------------------------- 1 | # markdown_editor 2 | 3 | Simple and easy to implement your markdown editor, it uses its own parser. 4 | 5 | ![show](https://xia-weiyang.github.io/gif/markdown_editor.gif) 6 | 7 | If you only need to render, you can refer to [https://github.com/xia-weiyang/markdown_core](https://github.com/xia-weiyang/markdown_core) 8 | 9 | ``` dart 10 | MarkdownEditor( 11 | initText: 'initText', 12 | initTitle: 'initText', 13 | onTapLink: (link){ 14 | print('点击了链接 $link'); 15 | }, 16 | imageWidget: (imageUrl) { 17 | return // Your image widget ; 18 | }, 19 | imageSelect: (){ // Click image select btn 20 | return // selected image link; 21 | }, 22 | ) 23 | ``` 24 | -------------------------------------------------------------------------------- /app/3rd_party/markdown_editor_ot/icons/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face {font-family: "iconfont"; 2 | src: url('iconfont.eot?t=1556453824121'); /* IE9 */ 3 | src: url('iconfont.eot?t=1556453824121#iefix') format('embedded-opentype'), /* IE6-IE8 */ 4 | url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAj4AAsAAAAAEpAAAAirAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCGCgqSBI8oATYCJANkCzQABCAFhG0HgyMbNxBRlHJSPrI/QCa34oe0RdSJe8owZa62zYloz17rYnli8QhMmvikYAJefgAAFkwnj+C///1e97kz73EAQWVFEThWKQpT476tirE1joVEVznJ8tWPB55x2B+ERoUMVlB97otIe4tCOrheBeu5wvfqXuWEZRdZfdlKzPrW2VYk+X7P5wBPxZXXzJ06TDQ2c5O5+H85258Enamgvp7sOqIysiJ2A+8EPEVM20VPIl4FP1lZ/N0p8e/uXvtGkTVvyxNII0m4Hmji/ecGB0gDD7rYiQT+JbnBtek20CLtkw0iRzifEVSqP7/fzyqi0eyQCO3X9Aw53/1iapEkXmmQRLwx8dJYMw19pa20Poux6cEqwLcKK5Z5NQRq80oF1Jy6cEN0cYIvVJuxuRZdIQXP8JVSnCuODEY7ZMrAj2lK4Ml+//osqlNCkgUhrvUwdtKII+/FSwK9TnofNEGvOR0wOQ4M7IJDr5SGn4Jc3LUFtap+sE8A0+Jj935/J/KjMEqiMuqjIfJh5bnxY3ho44Nojyx1RHGUR53tskNYZ0RZmZYtX0YUKlVuWymoZv+BlxiXyRVKlVqj1affgEEJEiVJliJVmnQUCqvaAlSJwkminQBCdAASohtgiB6AI3oBGaIPkCP6AQViAFAihgAVYgRQI0YBDWIC0CImAX2IKUA/Yh4wgFgADBIngCHiBWCY52UZI7ZwBTJGIfwMwBgvYTLGIQyHjAkIy9CASYAp84g9OCDdgB0gRD+4ySTJuAK7N0V5WCQlYzv4WuHlgJVt0061s6bW2kWnlLfuVaWnuahX521RlKXLNceyweWeecvb2hF3c9Oh8aV4DhiRY5RXM7W/AUvmvtMzEOCWJtZgUBiBsqyMzGwirIzk5hGFqKQJVrGCrYioFAnShEDSPq2TVzJ04TY85kvApVfw+5k8F0utOc5VRZwPyj0sTaGIQByMaAnIGN6iXYVZ4WWAdM9oZQJS3KzZ56PbzG/D01g7S7ajApDB4CS3SVoNZ0xDqoAsMis4y69nh9kN2YGOQbF53bjBHMxQOJV5ZXZkU8hW1VNCB45CZ7aKeA2JoS2C64hQLQrJt5h9uIBKkEUAKdKUoKzWaO592G8dQeVjUZ4KSYNYdMzoQVMoe/Wh+fj6BeTX9fvLmGC2mqb7h7MP65Nt8tjP/tpl2+RJs0PAECLoZH5JZx5J1d1njTPXL5sXY+nJBnla7HC2TyTUZ/I86XMOpsRuyLVLzitniC5UVttuLPQE+DFE1mFtp38YtapVpk+4SD8hMcKXAT3bktBZgs9kGRjiHWMPfJ28OWe0mc8DuCwdZq+VdfKJirMsMOs4S6OshWCJyJJelMdsJhez6AHSHp/Pz1es/EbwDNIpZYQZ8ARQEa5eYcx5JSpGhoppus7x6Lu+Xof84/b4AMMz4vs2ue1g2oC0sw5pvaOu8lf/z3mARm7uJMAzfiwSmTA2UqbCLkeJtdi6oUM0P9FBksbzQxQcy8ANrEaPI8b3Vw4rqhzKgFXyGoAe40uff1o1gf0YrSMYTOj8CTAReY0mRDaUPXsPeas9jyNaR5BVZ7pbIAPtjhPROh99YD8g98UxTee6n2o7q3c0QsHod7N2KqOJeXkTEatPwXr1g2u+w7a5NlsPgSZZH7raAlw+mbuJzpXMKg3ajDPQy3Bf5cG0/geQInXX/+7YPRWJBbAuxiVTYgN/2JaMaKlNnWKuwXQX13QtzFON2noHrkmXBEqbsFMAYsJ1SlInDxwjdUkJDF9WBCQEonUcboJSBvJTJHU4TJkLLro6jSChUYK/rX01Vo02jOJZFevBT/GhHHv4UqU6YX2ozArHths3wvxI4/vIcp0a3sLXgu9jnkbsjHgybJRLD92+fdQwF4s/hP9le5+EvgljPX09fbZtaz08bfiaw09yPWzrpxGw1dULWwdT2KH8Opmwqqh3ZOWZxfVoeKnd3bVbSrhBBxbHTZsWV1zPKIvy1hi4jW2bhaqq0CxJmZSx8b+ly66EvodCT5Ytm+Y7Y7XCfYlyYtUxTk6cXKL49jjniYYOT6T65VeTg+tPTNrMdNARm/ybQ0s6PeN+Sa/0wbH/nFtHW6KVOG3vtS/sN8SbwZ8/7q6t7e0/qe3DVfSpl5ObtZue/wId119QPxZcH73pH7gXqetn70WELR+fIsurp6Mnj8UZSZlnPx2Jx5L1ay5MTV1Yf1XQ8KwLp5pXz5xu7yDZlfW3y5XmidX/S0D6ln4KYKmfiyprCw/Hmi2Bvl9U/iXpsb/jouISXsqA7sGi6HhtMbgqsdtaF6NTbwvTEN3PMj1rcX8db8W/qcuTj+cc+F/GH9GT/n6+9cXO6FRQPb2UCNSa/fLlCvbydYpQTtXy3yW2w4gptTryEqjlKPgqtgx7NyzRWGHuP0A05kclhTk0leMqOhx3ozKNfczheBpV21F/fGPEklYKroBt24iSQedRSb/PaHB8ig7Ht1GZcV/TcnjGomqPAQvO2FjPB3mcQDMY1RhUNsUy09UsnerXIK+9JpYy2ukWUGWxVFs3bdkWn4YSqIl9qr58G3OmMsKgTml7gPeomoS9kPKmAkXNfZs3Z7N7bEoxCOkSARoDhjJk0Ew6KYVK2cRMxh7/GpCreRpltLtKegsgFWtQqn5n2i1ge1pQFmp3KHWX9clts8J0GUVNCQqUOqYDXhYgZUG9WS+QYptMVMhr2qeC9TdWVLCpe1G4LWYBNXH4VJJMrky5CpWqvgZfZKs+9WtAgxrSsEY0qjGNa0KTmtI0UWNIQfPGBnozz7dJmyBtZBjg2bFVFKAN0MY5s/62jnlt47Wd1w7PnbOqWtbepgsnOm+roaxQew8MZkECKevQuLZEyaL4aY0Mvc+u7BBU3rlisg64IKzzomAituxhrqwuWSxvS2i3uhwS7yC3sU+T1SW3ERhMjJRY2Xa2ASgpkbrktwXKzO4HzcVe1Es91mZQCA==') format('woff2'), 5 | url('iconfont.woff?t=1556453824121') format('woff'), 6 | url('iconfont.ttf?t=1556453824121') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ 7 | url('iconfont.svg?t=1556453824121#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family: "iconfont" !important; 12 | font-size: 16px; 13 | font-style: normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-format-bold:before { 19 | content: "\e757"; 20 | } 21 | 22 | .icon-format-color-text:before { 23 | content: "\e758"; 24 | } 25 | 26 | .icon-format-header-:before { 27 | content: "\e75b"; 28 | } 29 | 30 | .icon-format-header-1:before { 31 | content: "\e75c"; 32 | } 33 | 34 | .icon-format-header-2:before { 35 | content: "\e75d"; 36 | } 37 | 38 | .icon-format-header-3:before { 39 | content: "\e75e"; 40 | } 41 | 42 | .icon-format-header-4:before { 43 | content: "\e75f"; 44 | } 45 | 46 | .icon-format-header-5:before { 47 | content: "\e760"; 48 | } 49 | 50 | .icon-format-italic:before { 51 | content: "\e762"; 52 | } 53 | 54 | .icon-format-list-bulleted:before { 55 | content: "\e764"; 56 | } 57 | 58 | .icon-format-list-numbers:before { 59 | content: "\e765"; 60 | } 61 | 62 | .icon-format-quote:before { 63 | content: "\e768"; 64 | } 65 | 66 | .icon-format-size:before { 67 | content: "\e769"; 68 | } 69 | 70 | .icon-format-strikethrough:before { 71 | content: "\e76a"; 72 | } 73 | 74 | .icon-format-title:before { 75 | content: "\e76f"; 76 | } 77 | 78 | .icon-format-underline:before { 79 | content: "\e770"; 80 | } 81 | 82 | .icon-image:before { 83 | content: "\e7ac"; 84 | } 85 | 86 | .icon-link-variant:before { 87 | content: "\e7d8"; 88 | } 89 | 90 | .icon-redo:before { 91 | content: "\e873"; 92 | } 93 | 94 | .icon-redo-variant:before { 95 | content: "\e874"; 96 | } 97 | 98 | .icon-timer:before { 99 | content: "\e8eb"; 100 | } 101 | 102 | .icon-undo-variant:before { 103 | content: "\e907"; 104 | } 105 | 106 | .icon-undo:before { 107 | content: "\e908"; 108 | } 109 | 110 | .icon-weather-cloudy:before { 111 | content: "\e92d"; 112 | } 113 | 114 | -------------------------------------------------------------------------------- /app/3rd_party/markdown_editor_ot/icons/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debuggerx01/dde_gesture_manager/d2c699a4fe4b06bca67b16b8d785e69be8f406d1/app/3rd_party/markdown_editor_ot/icons/iconfont.eot -------------------------------------------------------------------------------- /app/3rd_party/markdown_editor_ot/icons/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debuggerx01/dde_gesture_manager/d2c699a4fe4b06bca67b16b8d785e69be8f406d1/app/3rd_party/markdown_editor_ot/icons/iconfont.ttf -------------------------------------------------------------------------------- /app/3rd_party/markdown_editor_ot/icons/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debuggerx01/dde_gesture_manager/d2c699a4fe4b06bca67b16b8d785e69be8f406d1/app/3rd_party/markdown_editor_ot/icons/iconfont.woff -------------------------------------------------------------------------------- /app/3rd_party/markdown_editor_ot/icons/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debuggerx01/dde_gesture_manager/d2c699a4fe4b06bca67b16b8d785e69be8f406d1/app/3rd_party/markdown_editor_ot/icons/iconfont.woff2 -------------------------------------------------------------------------------- /app/3rd_party/markdown_editor_ot/lib/customize_physics.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomizePhysics extends ScrollPhysics { 4 | const CustomizePhysics({ 5 | ScrollPhysics? parent, 6 | }) : super(parent: parent); 7 | 8 | @override 9 | CustomizePhysics applyTo(ScrollPhysics? ancestor) { 10 | return CustomizePhysics(parent: buildParent(ancestor)); 11 | } 12 | 13 | @override 14 | bool shouldAcceptUserOffset(ScrollMetrics position) => false; 15 | 16 | @override 17 | bool get allowImplicitScrolling => false; 18 | 19 | @override 20 | double applyBoundaryConditions(ScrollMetrics position, double value) { 21 | return 0; 22 | } 23 | 24 | @override 25 | Simulation? createBallisticSimulation( 26 | ScrollMetrics position, double velocity) { 27 | return null; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/3rd_party/markdown_editor_ot/lib/fonts/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debuggerx01/dde_gesture_manager/d2c699a4fe4b06bca67b16b8d785e69be8f406d1/app/3rd_party/markdown_editor_ot/lib/fonts/iconfont.ttf -------------------------------------------------------------------------------- /app/3rd_party/markdown_editor_ot/lib/markdown_editor.dart: -------------------------------------------------------------------------------- 1 | library markdown_editor; 2 | 3 | export 'package:markdown_editor_ot/src/editor.dart'; 4 | export 'package:markdown_editor_ot/src/preview.dart'; 5 | export 'package:markdown_editor_ot/src/action.dart'; 6 | -------------------------------------------------------------------------------- /app/3rd_party/markdown_editor_ot/lib/src/edit_perform.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// 撤销与前进 4 | class EditPerform { 5 | EditPerform( 6 | this._textEditingController, { 7 | this.initText = '', 8 | }); 9 | 10 | /// 最大的存储长度 11 | final _maxLength = 50; 12 | 13 | /// 初始文本 14 | final String initText; 15 | 16 | var _undoList = <_EditData>[]; 17 | var _redoList = <_EditData>[]; 18 | 19 | final TextEditingController _textEditingController; 20 | 21 | void change(text) { 22 | if (_textEditingController.text != '') { 23 | if (_undoList.isNotEmpty) { 24 | if (_textEditingController.text == _undoList.last.text) return; 25 | } 26 | if (_undoList.length >= _maxLength) _undoList.removeAt(0); 27 | _undoList.add(_EditData(_textEditingController.text, 28 | _textEditingController.selection.baseOffset)); 29 | _redoList.clear(); 30 | } 31 | } 32 | 33 | /// 撤销 34 | void undo() { 35 | // print(_undoList); 36 | if (_undoList.isNotEmpty) { 37 | _redoList.add(_undoList.last); 38 | _undoList.removeLast(); 39 | if (_undoList.isNotEmpty) { 40 | _textEditingController.value = TextEditingValue( 41 | text: _undoList.last.text, 42 | selection: TextSelection( 43 | extentOffset: _undoList.last.position, 44 | baseOffset: _undoList.last.position), 45 | ); 46 | } else { 47 | _textEditingController.value = TextEditingValue( 48 | text: initText, 49 | selection: TextSelection( 50 | extentOffset: initText.length, baseOffset: initText.length), 51 | ); 52 | } 53 | } 54 | } 55 | 56 | /// 恢复 57 | void redo() { 58 | // print(_redoList); 59 | if (_redoList.isNotEmpty) { 60 | _textEditingController.value = TextEditingValue( 61 | text: _redoList.last.text, 62 | selection: TextSelection( 63 | extentOffset: _redoList.last.position, 64 | baseOffset: _redoList.last.position), 65 | ); 66 | _undoList.add(_redoList.last); 67 | _redoList.removeLast(); 68 | } 69 | } 70 | } 71 | 72 | class _EditData { 73 | final String text; 74 | final int position; 75 | 76 | _EditData(this.text, this.position); 77 | 78 | @override 79 | String toString() { 80 | return 'text:$text position:$position'; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/3rd_party/markdown_editor_ot/lib/src/preview.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:markdown_core/builder.dart'; 3 | import 'package:markdown_core/markdown.dart'; 4 | 5 | class MdPreview extends StatefulWidget { 6 | MdPreview({ 7 | Key? key, 8 | required this.text, 9 | this.padding = const EdgeInsets.all(0.0), 10 | this.onTapLink, 11 | required this.widgetImage, 12 | required this.onCodeCopied, 13 | this.textStyle, 14 | this.richTap, 15 | }) : super(key: key); 16 | 17 | final String text; 18 | final EdgeInsetsGeometry padding; 19 | final WidgetImage widgetImage; 20 | final TextStyle? textStyle; 21 | 22 | final Function onCodeCopied; 23 | 24 | /// Call this method when it tap link of markdown. 25 | /// If [onTapLink] is null,it will open the link with your default browser. 26 | final TapLinkCallback? onTapLink; 27 | 28 | final VoidCallback? richTap; 29 | 30 | @override 31 | State createState() => MdPreviewState(); 32 | } 33 | 34 | class MdPreviewState extends State 35 | with AutomaticKeepAliveClientMixin { 36 | @override 37 | Widget build(BuildContext context) { 38 | super.build(context); 39 | return SingleChildScrollView( 40 | child: Padding( 41 | padding: widget.padding, 42 | child: LayoutBuilder( 43 | builder: (BuildContext context, BoxConstraints constraints) { 44 | return Markdown( 45 | data: widget.text, 46 | maxWidth: constraints.maxWidth, 47 | linkTap: (link) { 48 | debugPrint(link); 49 | if (widget.onTapLink != null) { 50 | widget.onTapLink!(link); 51 | } 52 | }, 53 | image: widget.widgetImage, 54 | textStyle: widget.textStyle, 55 | onCodeCopied: widget.onCodeCopied, 56 | richTap: widget.richTap, 57 | ); 58 | }, 59 | ), 60 | ), 61 | ); 62 | } 63 | 64 | @override 65 | bool get wantKeepAlive => true; 66 | } 67 | 68 | typedef void TapLinkCallback(String link); 69 | -------------------------------------------------------------------------------- /app/3rd_party/markdown_editor_ot/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: markdown_editor_ot 2 | description: Simple and easy to implement your markdown editor, it uses its own parser. 3 | version: 1.0.2 4 | homepage: https://github.com/xia-weiyang/markdown_editor_ot 5 | 6 | environment: 7 | sdk: '>=2.12.0 <3.0.0' 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | shared_preferences: ^2.0.4 14 | markdown_core: 15 | path: 16 | ../markdown_core 17 | 18 | dev_dependencies: 19 | flutter_test: 20 | sdk: flutter 21 | 22 | # For information on the generic Dart part of this file, see the 23 | # following page: https://www.dartlang.org/tools/pub/pubspec 24 | 25 | # The following section is specific to Flutter. 26 | flutter: 27 | 28 | # To add assets to your package, add an assets section, like this: 29 | # assets: 30 | # - images/a_dot_burr.jpeg 31 | # - images/a_dot_ham.jpeg 32 | # 33 | # For details regarding assets in packages, see 34 | # https://flutter.io/assets-and-images/#from-packages 35 | # 36 | # An image asset can refer to one or more resolution-specific "variants", see 37 | # https://flutter.io/assets-and-images/#resolution-aware. 38 | 39 | # To add custom fonts to your package, add a fonts section here, 40 | # in this "flutter" section. Each entry in this list should have a 41 | # "family" key with the font family name, and a "fonts" key with a 42 | # list giving the asset and other descriptors for the font. For 43 | 44 | fonts: 45 | - family: MyIconFont 46 | fonts: 47 | - asset: packages/markdown_editor_ot/fonts/iconfont.ttf 48 | 49 | # example: 50 | # fonts: 51 | # - family: Schyler 52 | # fonts: 53 | # - asset: fonts/Schyler-Regular.ttf 54 | # - asset: fonts/Schyler-Italic.ttf 55 | # style: italic 56 | # - family: Trajan Pro 57 | # fonts: 58 | # - asset: fonts/TrajanPro.ttf 59 | # - asset: fonts/TrajanPro_Bold.ttf 60 | # weight: 700 61 | # 62 | # For details regarding fonts in packages, see 63 | # https://flutter.io/custom-fonts/#from-packages -------------------------------------------------------------------------------- /app/3rd_party/xdg_directories_web/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | build/ 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Flutter.podspec 62 | **/ios/Flutter/Generated.xcconfig 63 | **/ios/Flutter/app.flx 64 | **/ios/Flutter/app.zip 65 | **/ios/Flutter/flutter_assets/ 66 | **/ios/Flutter/flutter_export_environment.sh 67 | **/ios/ServiceDefinitions.json 68 | **/ios/Runner/GeneratedPluginRegistrant.* 69 | 70 | # Exceptions to above rules. 71 | !**/ios/**/default.mode1v3 72 | !**/ios/**/default.mode2v3 73 | !**/ios/**/default.pbxuser 74 | !**/ios/**/default.perspectivev3 75 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 76 | -------------------------------------------------------------------------------- /app/3rd_party/xdg_directories_web/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 The Flutter Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following 11 | disclaimer in the documentation and/or other materials provided 12 | with the distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived 15 | from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /app/3rd_party/xdg_directories_web/lib/xdg_directories.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | library xdg_directories; 6 | 7 | import 'package:flutter_web_plugins/flutter_web_plugins.dart'; 8 | import 'package:universal_io/io.dart'; 9 | 10 | class XDGDirectoriesPlugin { 11 | static void registerWith(Registrar registrar) => null; 12 | } 13 | 14 | @override 15 | Directory get cacheHome => throw UnimplementedError(); 16 | 17 | @override 18 | List get configDirs => throw UnimplementedError(); 19 | 20 | @override 21 | Directory get configHome => throw UnimplementedError(); 22 | 23 | @override 24 | List get dataDirs => throw UnimplementedError(); 25 | 26 | @override 27 | Directory get dataHome => throw UnimplementedError(); 28 | 29 | @override 30 | Directory? getUserDirectory(String dirName) { 31 | throw UnimplementedError(); 32 | } 33 | 34 | @override 35 | Set getUserDirectoryNames() { 36 | throw UnimplementedError(); 37 | } 38 | 39 | @override 40 | Directory? get runtimeDir => throw UnimplementedError(); 41 | -------------------------------------------------------------------------------- /app/3rd_party/xdg_directories_web/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: xdg_directories_web 2 | description: A Dart package for reading XDG directory configuration information on Linux. 3 | version: 0.2.0 4 | 5 | flutter: 6 | plugin: 7 | platforms: 8 | web: 9 | fileName: xdg_directories.dart 10 | pluginClass: XDGDirectoriesPlugin 11 | 12 | environment: 13 | sdk: ">=2.12.0-0 <3.0.0" 14 | flutter: ">=1.5.0 <2.0.0" 15 | 16 | dependencies: 17 | universal_io: ^2.0.4 18 | flutter: 19 | sdk: flutter 20 | flutter_web_plugins: 21 | sdk: flutter 22 | 23 | dev_dependencies: 24 | test: ^1.16.0 25 | -------------------------------------------------------------------------------- /app/README.md: -------------------------------------------------------------------------------- 1 | # DDE Gesture Manager 2 | 专为 DDE 桌面环境打造的触摸板手势管理工具 3 | 4 | ## ProviderGenerator 5 | 利用 [source_gen](https://pub.dev/packages/source_gen) 和 [build_runner](https://pub.flutter-io.cn/packages/build_runner) 生成 [provider](https://pub.flutter-io.cn/packages/provider) 的模板代码: 6 | 1. 在 `lib/models/` 下编写模型类 7 | ```dart 8 | import 'package:dde_gesture_manager/builder/provider_annotation.dart'; 9 | 10 | @ProviderModel() 11 | class Test { 12 | @ProviderModelProp() 13 | bool? tested; 14 | 15 | @ProviderModelProp() 16 | String? name; 17 | } 18 | 19 | ``` 20 | 21 | 2. `app` 项目目录下执行 `flutter packages pub get && flutter packages pub run build_runner build --delete-conflicting-outputs` 22 | 23 | 3. 将在 `lib/models/test.provider.dart` 生成如下代码: 24 | ```dart 25 | import 'package:flutter/foundation.dart'; 26 | import 'package:dde_gesture_manager/extensions/compare_extension.dart'; 27 | import 'test.dart'; 28 | 29 | class TestProvider extends Test with ChangeNotifier { 30 | void setProps({ 31 | bool? tested, 32 | String? name, 33 | }) { 34 | bool changed = false; 35 | if (tested.diff(this.tested)) { 36 | this.tested = tested; 37 | changed = true; 38 | } 39 | if (name.diff(this.name)) { 40 | this.name = name; 41 | changed = true; 42 | } 43 | if (changed) notifyListeners(); 44 | } 45 | } 46 | 47 | ``` 48 | 49 | ## easy_localization 50 | ### 生成资源代码 51 | `flutter pub run easy_localization:generate && flutter pub run easy_localization:generate -f keys -o locale_keys.g.dart` 52 | 53 | ### 已经支持语言 54 | - 简体中文(zh-CN) 55 | - English(en) 56 | -------------------------------------------------------------------------------- /app/build.yaml: -------------------------------------------------------------------------------- 1 | builders: 2 | provider_builder: 3 | import: 'lib/builder/provider_builder.dart' 4 | builder_factories: [ 'providerBuilder' ] 5 | build_extensions: { '.dart': [ '.provider.dart' ] } 6 | auto_apply: root_package 7 | build_to: source 8 | defaults: 9 | generate_for: 10 | include: 11 | - lib/models/** 12 | -------------------------------------------------------------------------------- /app/build_deb.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd ../api || exit 4 | bash source_gen.sh 5 | 6 | cd ../app || exit 7 | bash source_gen.sh 8 | 9 | 10 | VERSION=$(dart version.dart) 11 | 12 | if [ -e pubspec.yaml.bak ]; then 13 | mv pubspec.yaml.bak pubspec.yaml 14 | fi 15 | 16 | flutter clean 17 | 18 | cp pubspec.yaml pubspec.yaml.bak 19 | ln -s /usr/share/fonts/opentype/noto/ noto_fonts 20 | 21 | cat >> pubspec.yaml << EOF 22 | fonts: 23 | - family: NotoSansSC 24 | fonts: 25 | - asset: noto_fonts/NotoSansCJK-Regular.ttc 26 | weight: 400 27 | - asset: noto_fonts/NotoSansCJK-Bold.ttc 28 | weight: 700 29 | 30 | EOF 31 | 32 | flutter build linux 33 | 34 | rm pubspec.yaml 35 | rm noto_fonts 36 | mv pubspec.yaml.bak pubspec.yaml 37 | 38 | if [ -e deb_builder ]; then 39 | rm -rf deb_builder 40 | fi 41 | 42 | 43 | mkdir "deb_builder" 44 | 45 | cp -r debian deb_builder/DEBIAN 46 | chmod -R 755 deb_builder/DEBIAN 47 | 48 | cp ../LICENSE deb_builder/DEBIAN/copyright 49 | 50 | echo "设置版本号为: $VERSION" 51 | 52 | echo Version: "$VERSION" >> deb_builder/DEBIAN/control 53 | 54 | mkdir -p deb_builder/opt/apps/com.debuggerx.dde-gesture-manager/ 55 | 56 | cp -r dde_package_info/* deb_builder/opt/apps/com.debuggerx.dde-gesture-manager/ 57 | 58 | ARCH="x64" 59 | 60 | if [[ $(uname -m) == aarch64 ]]; then 61 | ARCH="arm64" 62 | sed -i "s/amd64/$ARCH/g" deb_builder/opt/apps/com.debuggerx.dde-gesture-manager/info 63 | sed -i "s/amd64/$ARCH/g" deb_builder/DEBIAN/control 64 | fi 65 | 66 | cp -r build/linux/"$ARCH"/release/bundle deb_builder/opt/apps/com.debuggerx.dde-gesture-manager/files 67 | 68 | rm -rf deb_builder/opt/apps/com.debuggerx.dde-gesture-manager/files/data/flutter_assets/noto_fonts/ 69 | 70 | ln -s /usr/share/fonts/opentype/noto/ deb_builder/opt/apps/com.debuggerx.dde-gesture-manager/files/data/flutter_assets/noto_fonts 71 | 72 | mkdir -p deb_builder/opt/apps/com.debuggerx.dde-gesture-manager/entries/icons/hicolor/scalable/apps/ 73 | 74 | cp web/icons/Icon-512.png deb_builder/opt/apps/com.debuggerx.dde-gesture-manager/entries/icons/hicolor/scalable/apps/dgm.png 75 | 76 | sed -i "s/VERSION/$VERSION/g" deb_builder/opt/apps/com.debuggerx.dde-gesture-manager/info 77 | 78 | sed -i "s/VERSION/$VERSION/g" deb_builder/opt/apps/com.debuggerx.dde-gesture-manager/entries/applications/com.debuggerx.dde-gesture-manager.desktop 79 | 80 | echo "开始打包 $ARCH deb" 81 | 82 | fakeroot dpkg-deb -b deb_builder 83 | 84 | if [[ $ARCH == "x64" ]]; then 85 | ARCH="amd64" 86 | fi 87 | 88 | mv deb_builder.deb com.debuggerx.dde-gesture-manager_"$VERSION"_"$ARCH".deb 89 | 90 | echo "打包完成!" -------------------------------------------------------------------------------- /app/build_web.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Downloads WASM locally and use local fonts 3 | # Temporary solution until https://github.com/flutter/flutter/issues/70101 and 77580 provide a better way 4 | flutter clean 5 | flutter build web 6 | wasmLocation=$(grep canvaskit-wasm build/web/main.dart.js | sed -e 's/.*https/https/' -e 's/\/bin.*/\/bin/' | uniq) 7 | echo "Downloading WASM from $wasmLocation" 8 | curl -o build/web/canvaskit.js "$wasmLocation/canvaskit.js" 9 | curl -o build/web/canvaskit.wasm "$wasmLocation/canvaskit.wasm" 10 | sed -i -e "s!$wasmLocation!.!" \ 11 | -e "s!https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Me5WZLCzYlKw.ttf!./google_fonts/Roboto-Regular.ttf!" \ 12 | -e "s!https://fonts.googleapis.com/css2?family=Noto+Sans+Symbols!./assets/assets/css/Noto-Sans-Symbols.css!" \ 13 | -e "s!https://fonts.googleapis.com/css2?family=Noto+Color+Emoji+Compat!./assets/assets/css/Noto-Color-Emoji-Compat.css!" \ 14 | build/web/main.dart.js 15 | 16 | 17 | # 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 -------------------------------------------------------------------------------- /app/dde_package_info/entries/applications/com.debuggerx.dde-gesture-manager.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Categories=Utility; 3 | Comment=专为 DDE 桌面环境打造的触摸板手势管理工具(缩写:dgm),使用 Flutter 构建。 4 | Exec=/opt/apps/com.debuggerx.dde-gesture-manager/files/dde-gesture-manager 5 | Icon=dgm 6 | Name=DDE Gesture Manager 7 | Name[zh_CN]=DDE手势管理器 8 | Type=Application 9 | Version=VERSION 10 | X-Deepin-Vendor=user-custom 11 | -------------------------------------------------------------------------------- /app/dde_package_info/info: -------------------------------------------------------------------------------- 1 | { 2 | "appid": "com.debuggerx.dde-gesture-manager", 3 | "name": "dde-gesture-manager", 4 | "version": "VERSION", 5 | "arch": ["amd64"], 6 | "permissions": { 7 | "autostart": true, 8 | "notification": true, 9 | "trayicon": false, 10 | "clipboard": true, 11 | "account": false, 12 | "bluetooth": false, 13 | "camera": false, 14 | "audio_record": false, 15 | "installed_apps": false 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/debian/changelog: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debuggerx01/dde_gesture_manager/d2c699a4fe4b06bca67b16b8d785e69be8f406d1/app/debian/changelog -------------------------------------------------------------------------------- /app/debian/control: -------------------------------------------------------------------------------- 1 | Source: dde-gesture-manager 2 | Section: utils 3 | Priority: optional 4 | Maintainer: DebuggerX 5 | Build-Depends: 6 | clang, 7 | cmake, 8 | libgtk-3-dev, 9 | ninja-build, 10 | Homepage: https://github.com/debuggerx01/dde_gesture_manager 11 | Package: com.debuggerx.dde-gesture-manager 12 | Architecture: amd64 13 | Depends: fonts-noto-cjk 14 | Description: 专为 DDE 桌面环境打造的触摸板手势管理工具(缩写:dgm),使用 Flutter 构建。 15 | -------------------------------------------------------------------------------- /app/lib/builder/provider_annotation.dart: -------------------------------------------------------------------------------- 1 | class ProviderModel { 2 | const ProviderModel({this.copyable = false}); 3 | 4 | final bool copyable; 5 | } 6 | 7 | class ProviderModelProp { 8 | const ProviderModelProp({this.nullable = true}); 9 | 10 | final bool nullable; 11 | } 12 | -------------------------------------------------------------------------------- /app/lib/builder/provider_builder.dart: -------------------------------------------------------------------------------- 1 | import 'package:dde_gesture_manager/builder/provider_generator.dart'; 2 | import 'package:source_gen/source_gen.dart'; 3 | import 'package:build/build.dart'; 4 | 5 | Builder providerBuilder(BuilderOptions options) => 6 | LibraryBuilder(ProviderGenerator(), generatedExtension: '.provider.dart'); 7 | -------------------------------------------------------------------------------- /app/lib/builder/provider_generator.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/element/element.dart'; 2 | import 'package:build/src/builder/build_step.dart'; 3 | import 'package:dde_gesture_manager/builder/provider_annotation.dart'; 4 | import 'package:source_gen/source_gen.dart'; 5 | import 'package:collection/collection.dart'; 6 | 7 | class AnnotationField { 8 | String name; 9 | String type; 10 | 11 | AnnotationField(this.name, this.type); 12 | } 13 | 14 | class ProviderGenerator extends GeneratorForAnnotation { 15 | var _preClassName; 16 | 17 | @override 18 | generateForAnnotatedElement(Element element, ConstantReader annotation, BuildStep buildStep) { 19 | var className = (element as ClassElement).source.shortName; 20 | var needImports = className != _preClassName; 21 | _preClassName = className; 22 | List fields = []; 23 | element.fields.forEach((field) { 24 | var annotation = field.metadata.firstWhereOrNull( 25 | (m) => m.computeConstantValue()?.type?.getDisplayString(withNullability: true) == 'ProviderModelProp') ?? 26 | field.getter?.metadata.firstWhereOrNull( 27 | (m) => m.computeConstantValue()?.type?.getDisplayString(withNullability: true) == 'ProviderModelProp'); 28 | if (annotation != null) 29 | fields.add( 30 | AnnotationField( 31 | field.displayName, 32 | field.type.getDisplayString( 33 | withNullability: annotation.computeConstantValue()?.getField('nullable')?.toBoolValue() ?? true, 34 | ), 35 | ), 36 | ); 37 | }); 38 | return [ 39 | _genImports(className, needImports), 40 | _genClassDefine(element.displayName), 41 | _genNamedConstructors(element.constructors, element.displayName), 42 | _genCopyFunc(element.displayName, fields, annotation.read('copyable').boolValue), 43 | _genSetPropsFunc(fields), 44 | ].whereNotNull(); 45 | } 46 | } 47 | 48 | String? _genImports(String className, bool needImports) => needImports 49 | ? ''' 50 | import 'package:flutter/foundation.dart'; 51 | import 'package:dde_gesture_manager/extensions/compare_extension.dart'; 52 | import '$className'; 53 | ''' 54 | : null; 55 | 56 | String _genClassDefine(String displayName) => ''' 57 | class ${displayName}Provider extends $displayName with ChangeNotifier { 58 | '''; 59 | 60 | String? _genNamedConstructors(List constructors, String displayName) { 61 | String _genCallSuperParamStr(ParameterElement param) => param.isNamed ? '${param.name}: ${param.name}' : param.name; 62 | List _constructors = []; 63 | if (constructors.length > 0) { 64 | constructors.forEach((constructor) { 65 | if (constructor.name.length > 0) { 66 | var params = constructor.getDisplayString(withNullability: true).split('$displayName.${constructor.name}').last; 67 | _constructors.add(''' 68 | ${displayName}Provider.${constructor.name}${params.replaceAll('dynamic ', '')} 69 | : super.${constructor.name}(${constructor.parameters.map(_genCallSuperParamStr).join(',')}); 70 | '''); 71 | } 72 | }); 73 | } 74 | return _constructors.length > 0 ? _constructors.join('\n') : null; 75 | } 76 | 77 | String? _genCopyFunc(String displayName, List fields, bool copyable) { 78 | if (!copyable) return null; 79 | return ''' 80 | void copyFrom(${displayName} other) { 81 | bool changed = false; 82 | ${fields.map((f) => 'if (other.${f.name} != this.${f.name}) {this.${f.name} = other.${f.name}; changed = true; }').join('\n')} 83 | if (changed) notifyListeners(); 84 | } 85 | '''; 86 | } 87 | 88 | String _genSetPropsFunc(List fields) => ''' 89 | void setProps({ 90 | ${fields.map((f) => '${f.type.endsWith('?') ? '' : 'required '}${f.type} ${f.name},').join('\n')} 91 | }) { 92 | bool changed = false; 93 | ${fields.map((f) => 'if (${f.name}.diff(this.${f.name})) {this.${f.name} = ${f.name}; changed = true; }').join('\n')} 94 | if (changed) notifyListeners(); 95 | } 96 | } 97 | '''; 98 | -------------------------------------------------------------------------------- /app/lib/constants/constants.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | /// [UOS设计指南](https://docs.uniontech.com/zh/content/t_dbG3kBK9iDf9B963ok) 4 | const double localManagerPanelWidth = 260; 5 | 6 | const double marketOrMePanelWidth = 400; 7 | 8 | const shortDuration = const Duration(milliseconds: 100); 9 | 10 | const mediumDuration = const Duration(milliseconds: 300); 11 | 12 | const longDuration = const Duration(milliseconds: 500); 13 | 14 | const minWindowSize = const Size(800, 600); 15 | 16 | const defaultWindowSize = const Size(1120, 720); 17 | 18 | const double defaultBorderRadius = 8; 19 | 20 | const double defaultButtonHeight = 36; 21 | 22 | const userGestureConfigFilePath = 'deepin/dde-daemon/gesture.json'; 23 | 24 | const defaultFontFamily = 'NotoSansSC'; 25 | 26 | const deepinLogoutCommands = [ 27 | 'dbus-send', 28 | '--type=method_call', 29 | '--dest=com.deepin.SessionManager', 30 | '/com/deepin/SessionManager', 31 | 'com.deepin.SessionManager.RequestLogout' 32 | ]; 33 | 34 | const List builtInCommands = [ 35 | 'ShowWorkspace', 36 | 'Handle4Or5FingersSwipeUp', 37 | 'Handle4Or5FingersSwipeDown', 38 | 'ToggleMaximize', 39 | 'Minimize', 40 | 'ShowWindow', 41 | 'ShowAllWindow', 42 | 'SwitchApplication', 43 | 'ReverseSwitchApplication', 44 | 'SwitchWorkspace', 45 | 'ReverseSwitchWorkspace', 46 | 'SplitWindowLeft', 47 | 'SplitWindowRight', 48 | 'MoveWindow', 49 | ]; 50 | 51 | enum PanelType { 52 | local_manager, 53 | market_or_me, 54 | } 55 | 56 | enum UploadRespStatus { 57 | done, 58 | name_occupied, 59 | error, 60 | } 61 | -------------------------------------------------------------------------------- /app/lib/constants/sp_keys.dart: -------------------------------------------------------------------------------- 1 | class SPKeys { 2 | static final String brightnessMode = 'BRIGHTNESS_MODE'; 3 | static final String appliedSchemeId = 'APPLIED_SCHEME_ID'; 4 | static final String userLanguage = 'USER_LANGUAGE'; 5 | static final String accessToken = 'USER_ACCESS_TOKEN'; 6 | static final String loginEmail = 'USER_LOGIN_EMAIL'; 7 | static final String ignoredUpdateVersion = 'IGNORED_UPDATE_VERSION'; 8 | static final String readBulletinId = 'READ_BULLETIN_ID'; 9 | } 10 | -------------------------------------------------------------------------------- /app/lib/constants/supported_locales.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | enum SupportedLocale { 4 | zh_CN, 5 | en, 6 | } 7 | 8 | const zh_CN = Locale('zh', 'CN'); 9 | const en = Locale('en'); 10 | 11 | const supportedLocales = [ 12 | zh_CN, 13 | en, 14 | ]; 15 | 16 | const supportedLocaleNames = { 17 | SupportedLocale.zh_CN: '简体中文', 18 | SupportedLocale.en: 'English', 19 | }; 20 | 21 | Locale transformSupportedLocale(SupportedLocale supportedLocale) => supportedLocales[supportedLocale.index]; 22 | 23 | SupportedLocale? getSupportedLocale(Locale? locale) => 24 | supportedLocales.contains(locale) ? SupportedLocale.values[supportedLocales.indexOf(locale!)] : null; 25 | -------------------------------------------------------------------------------- /app/lib/extensions.dart: -------------------------------------------------------------------------------- 1 | library extensions; 2 | 3 | export 'package:dde_gesture_manager/extensions/sout_extension.dart'; 4 | export 'package:dde_gesture_manager/extensions/string_extension.dart'; 5 | export 'package:dde_gesture_manager/extensions/context_extension.dart'; 6 | export 'package:dde_gesture_manager/extensions/shared_preferences_extension.dart'; 7 | 8 | export 'package:dde_gesture_manager/generated/locale_keys.g.dart'; 9 | export 'package:easy_localization/easy_localization.dart'; 10 | 11 | export 'package:provider/provider.dart'; -------------------------------------------------------------------------------- /app/lib/extensions/compare_extension.dart: -------------------------------------------------------------------------------- 1 | extension CompareExtension on Object? { 2 | bool diff(other) => this != null && this != other; 3 | } 4 | -------------------------------------------------------------------------------- /app/lib/extensions/context_extension.dart: -------------------------------------------------------------------------------- 1 | import 'package:dde_gesture_manager/models/configs.provider.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:dde_gesture_manager/extensions.dart'; 4 | 5 | extension ContextExtension on BuildContext { 6 | ThemeData get t => Theme.of(this); 7 | 8 | NavigatorState get n => Navigator.of(this); 9 | 10 | bool get hasToken => this.read().accessToken.notNull; 11 | 12 | bool get watchHasToken => this.watch().accessToken.notNull; 13 | } 14 | -------------------------------------------------------------------------------- /app/lib/extensions/shared_preferences_extension.dart: -------------------------------------------------------------------------------- 1 | import 'package:shared_preferences/shared_preferences.dart'; 2 | 3 | extension SharedPreferencesExtenstion on SharedPreferences { 4 | Future updateInt(String key, int value) { 5 | if (this.getInt(key) == value) return Future.value(false); 6 | return this.setInt(key, value); 7 | } 8 | 9 | Future updateDouble(String key, double value) { 10 | if (this.getDouble(key) == value) return Future.value(false); 11 | return this.setDouble(key, value); 12 | } 13 | 14 | Future updateString(String key, String value) { 15 | if (this.getString(key) == value) return Future.value(false); 16 | return this.setString(key, value); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/lib/extensions/sout_extension.dart: -------------------------------------------------------------------------------- 1 | extension SoutExtension on Object? { 2 | void sout() { 3 | switch (this.runtimeType) { 4 | case String: 5 | return print(this); 6 | case Null: 7 | return print(null); 8 | case List: 9 | return print('[${(this as List).join(', ')}]'); 10 | default: 11 | return print(this.toString()); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/lib/extensions/string_extension.dart: -------------------------------------------------------------------------------- 1 | extension StringNotNull on String? { 2 | bool get notNull => this != null && this != ''; 3 | 4 | bool get isNull => !notNull; 5 | } -------------------------------------------------------------------------------- /app/lib/models/configs.dart: -------------------------------------------------------------------------------- 1 | import 'package:dde_gesture_manager/builder/provider_annotation.dart'; 2 | import 'package:dde_gesture_manager/constants/sp_keys.dart'; 3 | import 'package:dde_gesture_manager/extensions.dart'; 4 | import 'package:dde_gesture_manager/utils/helper.dart'; 5 | import 'package:sentry_flutter/sentry_flutter.dart'; 6 | 7 | enum BrightnessMode { 8 | system, 9 | light, 10 | dark, 11 | } 12 | 13 | @ProviderModel() 14 | class Configs { 15 | @ProviderModelProp() 16 | BrightnessMode? brightnessMode; 17 | 18 | @ProviderModelProp() 19 | String? get appliedSchemeId => _appliedSchemeId; 20 | 21 | set appliedSchemeId(String? schemeId) { 22 | _appliedSchemeId = schemeId; 23 | if (schemeId.notNull) 24 | H().sp.updateString(SPKeys.appliedSchemeId, schemeId!); 25 | else 26 | H().sp.remove(SPKeys.appliedSchemeId); 27 | } 28 | 29 | String? _appliedSchemeId; 30 | 31 | @ProviderModelProp() 32 | String? get accessToken => _accessToken; 33 | 34 | set accessToken(String? token) { 35 | _accessToken = token; 36 | if (token.notNull) 37 | H().sp.updateString(SPKeys.accessToken, token!); 38 | else 39 | H().sp.remove(SPKeys.accessToken); 40 | } 41 | 42 | String? _accessToken; 43 | 44 | @ProviderModelProp() 45 | String? get email => _email; 46 | 47 | set email(String? emailAddress) { 48 | _email = emailAddress; 49 | if (emailAddress.notNull) { 50 | H().sp.updateString(SPKeys.loginEmail, emailAddress!); 51 | Sentry.configureScope( 52 | (scope) => scope.user = SentryUser(email: emailAddress), 53 | ); 54 | } else 55 | H().sp.remove(SPKeys.loginEmail); 56 | } 57 | 58 | String? _email; 59 | 60 | Configs() { 61 | this.brightnessMode = 62 | BrightnessMode.values[H().sp.getInt(SPKeys.brightnessMode)?.clamp(0, BrightnessMode.values.length - 1) ?? 0]; 63 | this.appliedSchemeId = H().sp.getString(SPKeys.appliedSchemeId); 64 | this.accessToken = H().sp.getString(SPKeys.accessToken); 65 | this.email = H().sp.getString(SPKeys.loginEmail); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/lib/models/content_layout.dart: -------------------------------------------------------------------------------- 1 | import 'package:dde_gesture_manager/builder/provider_annotation.dart'; 2 | import 'package:dde_gesture_manager/constants/sp_keys.dart'; 3 | import 'package:dde_gesture_manager/utils/helper.dart'; 4 | import 'package:dde_gesture_manager/extensions.dart'; 5 | 6 | @ProviderModel() 7 | class ContentLayout { 8 | @ProviderModelProp() 9 | bool? localManagerOpened; 10 | 11 | @ProviderModelProp() 12 | bool? marketOrMeOpened; 13 | 14 | @ProviderModelProp() 15 | bool? currentIsMarket = H().sp.getString(SPKeys.accessToken).isNull; 16 | 17 | bool get isMarket => currentIsMarket ?? true; 18 | } 19 | -------------------------------------------------------------------------------- /app/lib/models/local_schemes.dart: -------------------------------------------------------------------------------- 1 | import 'package:dde_gesture_manager/models/scheme.dart'; 2 | 3 | import 'local_schemes_provider.dart'; 4 | 5 | export 'local_schemes_web.dart' if (dart.library.io) 'local_schemes_linux.dart'; 6 | 7 | abstract class LocalSchemeEntry implements Comparable { 8 | Scheme scheme; 9 | DateTime lastModifyTime; 10 | String path; 11 | 12 | LocalSchemeEntry({ 13 | required this.path, 14 | required this.scheme, 15 | required this.lastModifyTime, 16 | }); 17 | 18 | LocalSchemeEntry.systemDefault() 19 | : this.path = '', 20 | this.scheme = Scheme.systemDefault(), 21 | 22 | /// max value of DateTime ![Time Values and Time Range](https://262.ecma-international.org/11.0/#sec-time-values-and-time-range) 23 | this.lastModifyTime = DateTime.fromMillisecondsSinceEpoch(8640000000000000); 24 | 25 | save(LocalSchemesProvider provider); 26 | } 27 | 28 | abstract class LocalSchemesInterface { 29 | Future> get schemeEntries; 30 | 31 | Future create(); 32 | 33 | void remove(String path); 34 | } 35 | -------------------------------------------------------------------------------- /app/lib/models/local_schemes_linux.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:dde_gesture_manager/builder/provider_annotation.dart'; 5 | import 'package:dde_gesture_manager/extensions.dart'; 6 | import 'package:dde_gesture_manager/models/scheme.dart'; 7 | import 'package:path/path.dart' show join; 8 | import 'package:path_provider/path_provider.dart'; 9 | import 'package:uuid/uuid.dart'; 10 | 11 | import 'local_schemes.dart'; 12 | import 'local_schemes_provider.dart'; 13 | 14 | export 'local_schemes.dart'; 15 | 16 | @ProviderModel() 17 | class LocalSchemes implements LocalSchemesInterface { 18 | LocalSchemes() { 19 | schemeEntries.then((value) => schemes = [LocalSchemeEntryLinux.systemDefault(), ...value..sort()]); 20 | } 21 | 22 | @override 23 | Future> get schemeEntries async { 24 | var _supportDirectory = await getApplicationSupportDirectory(); 25 | var directory = Directory(join(_supportDirectory.path, 'schemes')); 26 | if (!directory.existsSync()) directory.createSync(); 27 | return directory 28 | .list() 29 | .map((f) { 30 | LocalSchemeEntryLinux? entry; 31 | try { 32 | var content = File(f.path).readAsStringSync(); 33 | entry = LocalSchemeEntryLinux( 34 | path: f.path, scheme: Scheme.parse(content), lastModifyTime: f.statSync().modified); 35 | } catch (e) { 36 | e.sout(); 37 | } 38 | return entry; 39 | }) 40 | .where((e) => e != null) 41 | .cast() 42 | .toList(); 43 | } 44 | 45 | @ProviderModelProp() 46 | List? schemes; 47 | 48 | @override 49 | Future create() async { 50 | var _supportDirectory = await getApplicationSupportDirectory(); 51 | return LocalSchemeEntryLinux( 52 | path: join(_supportDirectory.path, 'schemes', '${Uuid().v1()}.json'), 53 | scheme: Scheme.create(), 54 | lastModifyTime: DateTime.now(), 55 | ); 56 | } 57 | 58 | @override 59 | void remove(String path) { 60 | var file = File(path); 61 | if (file.existsSync()) file.delete(); 62 | } 63 | } 64 | 65 | class LocalSchemeEntryLinux implements LocalSchemeEntry { 66 | @override 67 | String path; 68 | 69 | @override 70 | Scheme scheme; 71 | 72 | @override 73 | DateTime lastModifyTime; 74 | 75 | LocalSchemeEntryLinux({ 76 | required this.path, 77 | required this.scheme, 78 | required this.lastModifyTime, 79 | }); 80 | 81 | LocalSchemeEntryLinux.systemDefault() 82 | : this.path = '', 83 | this.scheme = Scheme.systemDefault(), 84 | 85 | /// max value of DateTime ![Time Values and Time Range](https://262.ecma-international.org/11.0/#sec-time-values-and-time-range) 86 | this.lastModifyTime = DateTime.fromMillisecondsSinceEpoch(8640000000000000); 87 | 88 | @override 89 | save(LocalSchemesProvider provider) { 90 | var file = File(path); 91 | file.writeAsStringSync(JsonEncoder.withIndent(' ' * 4).convert(scheme)); 92 | provider.schemes!.firstWhere((ele) => ele.scheme.id == scheme.id).lastModifyTime = DateTime.now(); 93 | provider.setProps(schemes: [...provider.schemes!]..sort()); 94 | } 95 | 96 | @override 97 | int compareTo(other) { 98 | assert(other is LocalSchemeEntry); 99 | return lastModifyTime.isAfter(other.lastModifyTime) ? -1 : 1; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /app/lib/models/local_schemes_provider.dart: -------------------------------------------------------------------------------- 1 | export 'local_schemes_web.provider.dart' if (dart.library.io) 'local_schemes_linux.provider.dart'; -------------------------------------------------------------------------------- /app/lib/models/local_schemes_web.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:html'; 3 | 4 | import 'package:dde_gesture_manager/builder/provider_annotation.dart'; 5 | import 'package:dde_gesture_manager/extensions.dart'; 6 | import 'package:dde_gesture_manager/models/local_schemes_provider.dart'; 7 | import 'package:dde_gesture_manager/models/scheme.dart'; 8 | import 'package:uuid/uuid.dart'; 9 | 10 | import 'local_schemes.dart'; 11 | 12 | export 'local_schemes.dart'; 13 | 14 | @ProviderModel() 15 | class LocalSchemes implements LocalSchemesInterface { 16 | LocalSchemes() { 17 | schemeEntries.then((value) => schemes = [LocalSchemeEntryWeb.systemDefault(), ...value..sort()]); 18 | } 19 | 20 | @override 21 | Future> get schemeEntries async { 22 | List _localeSchemes = []; 23 | for (var key in window.localStorage.keys) { 24 | if (key.startsWith('schemes.')) { 25 | var content = window.localStorage[key] ?? ''; 26 | var schemeJson; 27 | try { 28 | schemeJson = json.decode(content); 29 | } catch (e) { 30 | e.sout(); 31 | } 32 | if (schemeJson != null) { 33 | _localeSchemes.add(LocalSchemeEntryWeb( 34 | path: key, 35 | scheme: Scheme.parse(schemeJson), 36 | lastModifyTime: DateTime.parse(schemeJson['modified_at']), 37 | )); 38 | } 39 | } 40 | } 41 | return Future.value(_localeSchemes); 42 | } 43 | 44 | @ProviderModelProp() 45 | List? schemes; 46 | 47 | @override 48 | Future create() => Future.value( 49 | LocalSchemeEntryWeb( 50 | path: 'schemes.${Uuid().v1()}', 51 | scheme: Scheme.create(), 52 | lastModifyTime: DateTime.now(), 53 | ), 54 | ); 55 | 56 | @override 57 | remove(String path) => window.localStorage.remove(path); 58 | } 59 | 60 | class LocalSchemeEntryWeb implements LocalSchemeEntry { 61 | @override 62 | String path; 63 | 64 | @override 65 | Scheme scheme; 66 | 67 | @override 68 | DateTime lastModifyTime; 69 | 70 | LocalSchemeEntryWeb({ 71 | required this.path, 72 | required this.scheme, 73 | required this.lastModifyTime, 74 | }); 75 | 76 | LocalSchemeEntryWeb.systemDefault() 77 | : this.path = '', 78 | this.scheme = Scheme.systemDefault(), 79 | 80 | /// max value of DateTime ![Time Values and Time Range](https://262.ecma-international.org/11.0/#sec-time-values-and-time-range) 81 | this.lastModifyTime = DateTime.fromMillisecondsSinceEpoch(8640000000000000); 82 | 83 | @override 84 | save(LocalSchemesProvider provider) { 85 | var schemeMap = scheme.toJson(); 86 | schemeMap['modified_at'] = DateTime.now().toIso8601String(); 87 | window.localStorage[path] = JsonEncoder.withIndent(' ' * 4).convert(schemeMap); 88 | provider.schemes!.firstWhere((ele) => ele.scheme.id == scheme.id).lastModifyTime = DateTime.now(); 89 | provider.setProps(schemes: [...provider.schemes!]..sort()); 90 | } 91 | 92 | @override 93 | int compareTo(other) { 94 | assert(other is LocalSchemeEntry); 95 | return lastModifyTime.isAfter(other.lastModifyTime) ? -1 : 1; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /app/lib/models/scheme_list_refresh_key.dart: -------------------------------------------------------------------------------- 1 | import 'package:dde_gesture_manager/builder/provider_annotation.dart'; 2 | 3 | @ProviderModel() 4 | class SchemeListRefreshKey { 5 | @ProviderModelProp(nullable: false) 6 | int refreshKey; 7 | 8 | SchemeListRefreshKey() : refreshKey = DateTime.now().millisecondsSinceEpoch; 9 | } 10 | -------------------------------------------------------------------------------- /app/lib/models/settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:dde_gesture_manager/builder/provider_annotation.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | export 'package:flutter/material.dart' show Color; 5 | 6 | @ProviderModel() 7 | class Settings { 8 | @ProviderModelProp() 9 | bool? isDarkMode; 10 | 11 | @ProviderModelProp() 12 | Color? activeColor; 13 | 14 | Color get currentActiveColor => activeColor ?? const Color(0xff0069cc); 15 | } 16 | -------------------------------------------------------------------------------- /app/lib/pages/content.dart: -------------------------------------------------------------------------------- 1 | import 'package:dde_gesture_manager/extensions.dart'; 2 | import 'package:dde_gesture_manager/models/content_layout.provider.dart'; 3 | import 'package:dde_gesture_manager/models/scheme.provider.dart'; 4 | import 'package:dde_gesture_manager/models/scheme_list_refresh_key.provider.dart'; 5 | import 'package:dde_gesture_manager/pages/gesture_editor.dart'; 6 | import 'package:dde_gesture_manager/pages/local_manager.dart'; 7 | import 'package:dde_gesture_manager/pages/market_or_me.dart'; 8 | import 'package:dde_gesture_manager/utils/helper.dart'; 9 | import 'package:flutter/material.dart'; 10 | 11 | class Content extends StatefulWidget { 12 | const Content({Key? key}) : super(key: key); 13 | 14 | @override 15 | _ContentState createState() => _ContentState(); 16 | } 17 | 18 | class CopiedGesturePropProvider extends GesturePropProvider { 19 | CopiedGesturePropProvider.empty() : super.empty(); 20 | } 21 | 22 | class _ContentState extends State { 23 | double? preWindowWidth; 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | var windowWidth = MediaQuery.of(context).size.width; 28 | var preferredPanelsStatus = H.getPreferredPanelsStatus(windowWidth); 29 | var widthChanged = preWindowWidth != null && preWindowWidth != windowWidth; 30 | var widget = MultiProvider( 31 | providers: [ 32 | ChangeNotifierProvider( 33 | create: (context) => ContentLayoutProvider() 34 | ..localManagerOpened = preferredPanelsStatus.localManagerPanelOpened 35 | ..marketOrMeOpened = preferredPanelsStatus.marketOrMePanelOpened, 36 | ), 37 | ChangeNotifierProvider( 38 | create: (context) => CopiedGesturePropProvider.empty(), 39 | ), 40 | ChangeNotifierProvider( 41 | create: (context) => SchemeListRefreshKeyProvider(), 42 | ), 43 | ], 44 | builder: (context, child) { 45 | if (widthChanged && mounted) { 46 | Future.microtask( 47 | () => context.read().setProps( 48 | localManagerOpened: preferredPanelsStatus.localManagerPanelOpened, 49 | marketOrMeOpened: preferredPanelsStatus.marketOrMePanelOpened, 50 | ), 51 | ); 52 | } 53 | return Row( 54 | mainAxisSize: MainAxisSize.max, 55 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 56 | children: [ 57 | LocalManager(key: H.localManagerKey), 58 | GestureEditor(), 59 | MarketOrMe(), 60 | ], 61 | ); 62 | }, 63 | ); 64 | preWindowWidth = windowWidth; 65 | return widget; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/lib/pages/footer.dart: -------------------------------------------------------------------------------- 1 | import 'package:dde_gesture_manager/extensions.dart'; 2 | import 'package:dde_gesture_manager/widgets/help_button.dart'; 3 | import 'package:dde_gesture_manager/widgets/language_switcher.dart'; 4 | import 'package:dde_gesture_manager/widgets/theme_switcher.dart'; 5 | import 'package:dde_gesture_manager/widgets/version_checker.dart'; 6 | import 'package:flutter/cupertino.dart'; 7 | import 'package:flutter/material.dart'; 8 | 9 | class Footer extends StatefulWidget { 10 | const Footer({Key? key}) : super(key: key); 11 | 12 | @override 13 | _FooterState createState() => _FooterState(); 14 | } 15 | 16 | class _FooterState extends State