把项目移至 Flutter 目录下
179
summer-ospp/2024/Flutter/changelog.md
Normal file
@ -0,0 +1,179 @@
|
||||
2024-09-01T21:01:16+08:00
|
||||
- [Update .gitignore](https://gitee.com/biyanci/MaxKey/commit/1643f9608d61e59b0eaf6fb76b2e610b81e6b250)
|
||||
|
||||
2024-09-01T20:30:30+08:00
|
||||
- [Update README.md](https://gitee.com/biyanci/MaxKey/commit/66d9fc38c901a52e76a91fb886c6e691f32dfc21)
|
||||
|
||||
2024-09-01T20:29:51+08:00
|
||||
- [提供多语言支持。包括中文和英文](https://gitee.com/biyanci/MaxKey/commit/ab8517bf723062d1428476adeade4a1a9bb2833a)
|
||||
|
||||
2024-09-01T17:31:21+08:00
|
||||
- [在切换账户时读取 totp](https://gitee.com/biyanci/MaxKey/commit/4b96be9e28d945bff6454b607c6479fc8de14763)
|
||||
- [v1.0.0](https://gitee.com/biyanci/MaxKey/commit/41d2e10bf3771960e427a401bc27eb2bd25aa737)
|
||||
|
||||
2024-09-01T17:23:14+08:00
|
||||
- [在 user card 加载时也显示箭头图标](https://gitee.com/biyanci/MaxKey/commit/302fcedcf99067cbf23cb7c3eaf95a545465f7b9)
|
||||
|
||||
2024-09-01T17:21:05+08:00
|
||||
- [不再把 MaxKey 的 totp 特别对待](https://gitee.com/biyanci/MaxKey/commit/93f7e71f17844253dc6e4e0bded70a469e467b37)
|
||||
|
||||
2024-09-01T16:41:46+08:00
|
||||
- [use auth_totp instead of totp.](https://gitee.com/biyanci/MaxKey/commit/a8854c12ad9b097fdf96a144dd9d51ade1b9f037)
|
||||
|
||||
2024-09-01T16:07:47+08:00
|
||||
- [避免重复构造 totp](https://gitee.com/biyanci/MaxKey/commit/d4fa845c19ccb034c158c6ca519e5e7a2170b4b2)
|
||||
- [upgrade flutter sdk to 3.24](https://gitee.com/biyanci/MaxKey/commit/014a3b8e0dbdb522826c5e3206cfeac23c7067c2)
|
||||
|
||||
2024-08-29T22:07:18+08:00
|
||||
- [保证 normalTotps 的返回值不为空](https://gitee.com/biyanci/MaxKey/commit/5048e521160fe01213ab5afa56699b9d0e9ec312)
|
||||
|
||||
2024-08-29T21:51:51+08:00
|
||||
- [更新 Readme](https://gitee.com/biyanci/MaxKey/commit/be96e50066b8faf9c769ba844d4243ff073f9fa7)
|
||||
|
||||
2024-08-29T21:51:19+08:00
|
||||
- [添加查看 Log 页面](https://gitee.com/biyanci/MaxKey/commit/f846e8806ab20e03f1a360d56f74c800f612b35e)
|
||||
|
||||
2024-08-29T21:35:15+08:00
|
||||
- [提取函数](https://gitee.com/biyanci/MaxKey/commit/f782adead1ec04e91f4d8ee705d9d7cb8a946b32)
|
||||
- [在登陆状态过期时提示重新登陆](https://gitee.com/biyanci/MaxKey/commit/29b907b14c74a8c06059fab873311d2d4111dabc)
|
||||
|
||||
2024-08-29T21:04:35+08:00
|
||||
- [dart format](https://gitee.com/biyanci/MaxKey/commit/0d62f3fc612c8880c911714917cacce33614d7c3)
|
||||
|
||||
2024-08-29T20:54:34+08:00
|
||||
- [支持第三方时间令牌](https://gitee.com/biyanci/MaxKey/commit/356251c0b424af39318c8ddb6f5fc6accadf9cb6)
|
||||
- [优化时间令牌标签内容;修复一些兼容性问题](https://gitee.com/biyanci/MaxKey/commit/edcee1ef5f122dffb4e7e4eb35a8dbf63c61edd1)
|
||||
|
||||
2024-08-29T15:41:23+08:00
|
||||
- [优化设置页代码目录结构](https://gitee.com/biyanci/MaxKey/commit/7c1880434df5c45893d61ce41626bbb2a2a3befb)
|
||||
- [优化登录页代码目录结构;删除无用的 otp_count_down_view](https://gitee.com/biyanci/MaxKey/commit/dd740fe4221c5a3066ccd3ad80023cbf318f3ad6)
|
||||
|
||||
2024-08-29T15:21:06+08:00
|
||||
- [去除 log 浮窗](https://gitee.com/biyanci/MaxKey/commit/b22214982d5356738048e49943ebb55412ebd2ad)
|
||||
|
||||
2024-08-29T14:41:07+08:00
|
||||
- [把 MaxKey 改造成单例;提供更新 baseurl 的方法;更新 baseurl 后通知组件刷新](https://gitee.com/biyanci/MaxKey/commit/69a3ce9144a1d8eec9beb6f04224582b311152d3)
|
||||
- [新增设置页;把修改主机功能移至设置](https://gitee.com/biyanci/MaxKey/commit/684ac1cd00f946d701c1775d2b1c0c53bf8172bf)
|
||||
|
||||
2024-08-29T00:32:22+08:00
|
||||
- [允许修改主题模式;修复一些热重载时的奇怪问题](https://gitee.com/biyanci/MaxKey/commit/91e5121cf1f6f8ea40dc67b2728739b43a6cac73)
|
||||
|
||||
2024-08-28T20:16:39+08:00
|
||||
- [新增用户页;提供查看用户详细信息的功能;把退出账号移至用户页](https://gitee.com/biyanci/MaxKey/commit/e1c023caaf08404a0b3fdfd9be61a1ff49028963)
|
||||
|
||||
2024-08-27T21:14:04+08:00
|
||||
- [修复热重载时可能出现的异常情况](https://gitee.com/biyanci/MaxKey/commit/843e9c5c95f0ee3f1760a42675c3181dd0f2a5c8)
|
||||
|
||||
2024-08-27T21:00:54+08:00
|
||||
- [Delete app_list.service.dart](https://gitee.com/biyanci/MaxKey/commit/b0951d69995b45928fadcdd0b73e21c8b3aa3585)
|
||||
- [优化应用数据持久化逻辑](https://gitee.com/biyanci/MaxKey/commit/92b42e78a7314155dd442214f0f269b036f16b50)
|
||||
|
||||
2024-08-26T23:33:04+08:00
|
||||
- [更新 ReadMe](https://gitee.com/biyanci/MaxKey/commit/7df25ba58033018d71dc12b85e90725d7b6c982a)
|
||||
|
||||
2024-08-26T22:39:12+08:00
|
||||
- [让主题跟随系统日夜间模式](https://gitee.com/biyanci/MaxKey/commit/7c2c59eab0c7151652a6369d82977dea352d152e)
|
||||
|
||||
2024-08-26T22:31:37+08:00
|
||||
- [去除手机验证码登陆功能](https://gitee.com/biyanci/MaxKey/commit/ea9b6410ee649deb8732321671545aba421a23c6)
|
||||
- [去除访问 web app 功能](https://gitee.com/biyanci/MaxKey/commit/b963998066c42dc81cb9ba5bbd3a6d54c7357dbd)
|
||||
|
||||
2024-08-26T22:09:06+08:00
|
||||
- [新增验证 MaxKey totp 的方法;在添加 MaxKey 的 totp 时自动验证正确性](https://gitee.com/biyanci/MaxKey/commit/311348f6be356426c74c066dc063d7b8cc18bb23)
|
||||
|
||||
2024-08-26T20:30:29+08:00
|
||||
- [去除不需要的访问 web app 的功能](https://gitee.com/biyanci/MaxKey/commit/85933bbbb93c5386eccc5c73d5f32bf5d9a14ecc)
|
||||
|
||||
2024-08-23T00:31:26+08:00
|
||||
- [提供修改主机地址的功能](https://gitee.com/biyanci/MaxKey/commit/b62f60c4e24b6128a31357fd2cbb0d6e21d19eaf)
|
||||
- [完善错误处理和日志输出;跟进 MaxKey Api](https://gitee.com/biyanci/MaxKey/commit/f1625f521951c202820396514b53dc8c38a9d74e)
|
||||
|
||||
2024-08-22T16:36:07+08:00
|
||||
- [使用 MaxKey LOGO 作为应用图标](https://gitee.com/biyanci/MaxKey/commit/7d8d808bbfe67b6a64750544d9bb0c6ab87d7fc1)
|
||||
|
||||
2024-07-22T13:25:26+08:00
|
||||
- [去掉没用的大小限制;使用圆角矩形作为加载中的 app icon](https://gitee.com/biyanci/MaxKey/commit/63fa5708b858279dc93a77e13eea7bcd81194f50)
|
||||
- [更正 添加时间令牌 按钮的圆角](https://gitee.com/biyanci/MaxKey/commit/83bf46ee2edacc4be7271afa0e705e60f1a234d1)
|
||||
|
||||
2024-07-20T17:14:14+08:00
|
||||
- [Create repeat_tween_animation_builder.dart](https://gitee.com/biyanci/MaxKey/commit/f39031c3e6027ec35bd50a8c0b909ee45ba3e3ba)
|
||||
- [新增 loading user card 组件,在加载时替代 user card 使用](https://gitee.com/biyanci/MaxKey/commit/03dde4c16b1c67e324b9176b9c9e6ef45976b2ff)
|
||||
|
||||
2024-07-20T15:21:53+08:00
|
||||
- [减少不必要的重新请求](https://gitee.com/biyanci/MaxKey/commit/cecab3e68d2673ea9ea07f3c30c8e0e10dccf2b2)
|
||||
- [保存 dio 获取到的 cookie](https://gitee.com/biyanci/MaxKey/commit/4d843a3b06ef3e94f245c75196de2886873ba78c)
|
||||
|
||||
2024-07-17T17:14:22+08:00
|
||||
- [更新待做](https://gitee.com/biyanci/MaxKey/commit/8c6f3855b886a5f607c490332295471b7e2e14bd)
|
||||
|
||||
2024-07-17T17:11:55+08:00
|
||||
- [等待logout结束](https://gitee.com/biyanci/MaxKey/commit/c50cd8fe12d96acc292621747d882a55e9dad2fb)
|
||||
|
||||
2024-07-17T17:01:24+08:00
|
||||
- [完善没有摄像头权限时的提示](https://gitee.com/biyanci/MaxKey/commit/2dd9e98d495705f002a16792204d6a24abd6c581)
|
||||
- [在重试时复用之前的 header](https://gitee.com/biyanci/MaxKey/commit/cab0b37234259b40375aa87c7ba3e66b93d19040)
|
||||
|
||||
2024-07-16T23:21:00+08:00
|
||||
- [debug manifest 允许 http](https://gitee.com/biyanci/MaxKey/commit/c9dddf095630386de41d3141daea18240c84f697)
|
||||
- [release manifest 应用名称、允许 http、允许联网](https://gitee.com/biyanci/MaxKey/commit/f963d77ae9ab83fb13c1fa732627c8ab9e1c4678)
|
||||
|
||||
2024-07-14T15:30:29+08:00
|
||||
- [更正拼写](https://gitee.com/biyanci/MaxKey/commit/5b695c0f488f47ea0dbd3cd34616a9d4f2bd0aa7)
|
||||
|
||||
2024-07-14T13:56:10+08:00
|
||||
- [删除多余的函数](https://gitee.com/biyanci/MaxKey/commit/4ac10a408b6abdd99d9b3c41be5746ae0906f1fa)
|
||||
- [更正设置 header 的方法](https://gitee.com/biyanci/MaxKey/commit/24f1da2a685dd2a0fc71a9365475e17ea7552274)
|
||||
|
||||
2024-07-14T13:22:32+08:00
|
||||
- [修改函数命名](https://gitee.com/biyanci/MaxKey/commit/59659a35fbb9ea736256912125957a3f0edf08fb)
|
||||
|
||||
2024-07-14T13:21:35+08:00
|
||||
- [默认使用手动设置的 host ip](https://gitee.com/biyanci/MaxKey/commit/53abfe3cdcf17016908d9a6645ddbb10df49497a)
|
||||
|
||||
2024-07-09T17:35:28+08:00
|
||||
- [去掉无意义的写入操作](https://gitee.com/biyanci/MaxKey/commit/c983871f26c2016901f1f9b77f3aa8bf9e4a827b)
|
||||
- [更新待做](https://gitee.com/biyanci/MaxKey/commit/13042adafc2e054b97927bb63a92e5a56e183c02)
|
||||
|
||||
2024-07-07T15:07:34+08:00
|
||||
- [问候](https://gitee.com/biyanci/MaxKey/commit/afc70d2209ac03e2d5b78a011ce5cedb892fe410)
|
||||
- [录入时间令牌时判断是否属于当前用户](https://gitee.com/biyanci/MaxKey/commit/7c46d0b5b77037a7872ef756155d342ebbc4b51c)
|
||||
|
||||
2024-07-07T14:25:20+08:00
|
||||
- [删除不必要的引用](https://gitee.com/biyanci/MaxKey/commit/c9a5cc9cf5ddc8551bbe04aa9e4b8cc3dd624e58)
|
||||
- [更新待做](https://gitee.com/biyanci/MaxKey/commit/2c5dccd940bc52248cc1f5fc5728a01ddaf5e40b)
|
||||
|
||||
2024-07-06T14:19:22+08:00
|
||||
- [手机号登录](https://gitee.com/biyanci/MaxKey/commit/332fda74c77f2e5958b03a765cf2b27746014a8e)
|
||||
|
||||
2024-07-06T13:35:12+08:00
|
||||
- [更正拼写](https://gitee.com/biyanci/MaxKey/commit/66a942e369da9af42458e47f519bb554f8952646)
|
||||
|
||||
2024-07-06T13:12:08+08:00
|
||||
- [提取 needlogin 函数](https://gitee.com/biyanci/MaxKey/commit/5cfb0830837813db9f7866c241670ad783684079)
|
||||
- [优化读取偏好的方式](https://gitee.com/biyanci/MaxKey/commit/161df2830f02deacd8a4194afc5339d6ea9b7a67)
|
||||
|
||||
2024-07-06T12:59:30+08:00
|
||||
- [取消设置 host 弹窗](https://gitee.com/biyanci/MaxKey/commit/79cddaf6e022082d67c46e981c02a9bfde6f161a)
|
||||
- [保持登录状态](https://gitee.com/biyanci/MaxKey/commit/024daa301749a4e649696bab343fac25fd81c8c4)
|
||||
|
||||
2024-07-06T12:43:46+08:00
|
||||
- [Revert "提取 setHostDialog"](https://gitee.com/biyanci/MaxKey/commit/9e2f277be61ad78cb8ec4cddbbdac665ca1129e1)
|
||||
|
||||
2024-07-06T12:31:50+08:00
|
||||
- [公开 setToken 方法](https://gitee.com/biyanci/MaxKey/commit/85c9680fd7882ed54791f8008a99c8a9e34bf841)
|
||||
- [提取 setHostDialog](https://gitee.com/biyanci/MaxKey/commit/b160c57c92ed451a37d4e6861056b7826b816e06)
|
||||
|
||||
2024-07-05T23:27:10+08:00
|
||||
- [只重绘密码输入框](https://gitee.com/biyanci/MaxKey/commit/0202e4413f4748ad34d18a5df37e8c0175c5d236)
|
||||
|
||||
2024-07-05T16:11:08+08:00
|
||||
- [scan page](https://gitee.com/biyanci/MaxKey/commit/d8384b42addfe478d65f547a785228fa28747391)
|
||||
- [统一命名](https://gitee.com/biyanci/MaxKey/commit/c915df66a3577dc37066c2be73d8a01004730c57)
|
||||
|
||||
2024-07-04T22:58:28+08:00
|
||||
- [整合到 utils](https://gitee.com/biyanci/MaxKey/commit/70814dccaaaf99fe272e506f6c79872e815e9879)
|
||||
- [优化 appList 接口](https://gitee.com/biyanci/MaxKey/commit/6f3473e365c24011372497807ed97225ab85ca74)
|
||||
|
||||
2024-07-04T18:05:19+08:00
|
||||
- [init](https://gitee.com/biyanci/MaxKey/commit/80a77f9750d255cd4a69155912e10fbd8dcd1ac2)
|
||||
- [generate color scheme](https://gitee.com/biyanci/MaxKey/commit/091b44d28e3836119a568548c326369419ad96c5)
|
||||
36
summer-ospp/2024/Flutter/maxkey_flutter/.metadata
Normal file
@ -0,0 +1,36 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "5dcb86f68f239346676ceb1ed1ea385bd215fba1"
|
||||
channel: "stable"
|
||||
|
||||
project_type: app
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1
|
||||
base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1
|
||||
- platform: android
|
||||
create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1
|
||||
base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1
|
||||
- platform: ios
|
||||
create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1
|
||||
base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1
|
||||
- platform: windows
|
||||
create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1
|
||||
base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||
3
summer-ospp/2024/Flutter/maxkey_flutter/.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"java.compile.nullAnalysis.mode": "disabled"
|
||||
}
|
||||
46
summer-ospp/2024/Flutter/maxkey_flutter/README.md
Normal file
@ -0,0 +1,46 @@
|
||||
# maxkey_flutter
|
||||
|
||||
MaxKey Flutter project.
|
||||
|
||||
### 待做:
|
||||
- [x] 账密登录
|
||||
- [x] 手机号登陆
|
||||
- [x] 保留登陆状态
|
||||
- [x] 账号信息
|
||||
- [x] 扫码登录
|
||||
- [x] 登出
|
||||
- [x] TOTP 录入
|
||||
- [x] TOTP 展示
|
||||
- [x] TOTP 与账号绑定的持久化
|
||||
- [x] 支持多个 TOTP
|
||||
- [x] 检测 token 是否有效
|
||||
- [x] 完善错误处理和提示
|
||||
- [x] 优化用户界面
|
||||
- [x] 多语言
|
||||
|
||||
登陆页:
|
||||
- MaxKey LOGO
|
||||
- 用户名、密码、验证码
|
||||
- 登陆
|
||||
- 设置
|
||||
|
||||
主页面:
|
||||
- 用户卡片(包括扫码登陆按钮)
|
||||
- 添加 TOTP 按钮
|
||||
- TOTP 列表(可删除某个 TOTP)
|
||||
|
||||
账号页:
|
||||
- 用户卡片
|
||||
- 详细信息
|
||||
- 登出
|
||||
- 设置
|
||||
|
||||
设置页:
|
||||
- 主题模式(跟随系统、日间、夜间)
|
||||
- 地址、端口(提供请求 sign/get 以验证连接性的功能)
|
||||
- 查看日志
|
||||
|
||||
持久化:
|
||||
- token
|
||||
- host ip
|
||||
- user's totp list
|
||||
@ -0,0 +1,4 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
analyzer:
|
||||
errors:
|
||||
unused_element: ignore
|
||||
@ -0,0 +1,58 @@
|
||||
plugins {
|
||||
id "com.android.application"
|
||||
id "kotlin-android"
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id "dev.flutter.flutter-gradle-plugin"
|
||||
}
|
||||
|
||||
def localProperties = new Properties()
|
||||
def localPropertiesFile = rootProject.file("local.properties")
|
||||
if (localPropertiesFile.exists()) {
|
||||
localPropertiesFile.withReader("UTF-8") { reader ->
|
||||
localProperties.load(reader)
|
||||
}
|
||||
}
|
||||
|
||||
def flutterVersionCode = localProperties.getProperty("flutter.versionCode")
|
||||
if (flutterVersionCode == null) {
|
||||
flutterVersionCode = "1"
|
||||
}
|
||||
|
||||
def flutterVersionName = localProperties.getProperty("flutter.versionName")
|
||||
if (flutterVersionName == null) {
|
||||
flutterVersionName = "1.0"
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "org.dromara.maxkey.maxkey_flutter"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "org.dromara.maxkey.maxkey_flutter"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||
minSdk = flutter.minSdkVersion
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutterVersionCode.toInteger()
|
||||
versionName = flutterVersionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig = signingConfigs.debug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<application android:usesCleartextTraffic="true"></application>
|
||||
</manifest>
|
||||
@ -0,0 +1,47 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<application
|
||||
android:label="MaxKey"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:usesCleartextTraffic="true">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility and
|
||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||
|
||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
@ -0,0 +1,5 @@
|
||||
package org.dromara.maxkey.maxkey_flutter
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity: FlutterActivity()
|
||||
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 27 KiB |
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
18
summer-ospp/2024/Flutter/maxkey_flutter/android/build.gradle
Normal file
@ -0,0 +1,18 @@
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.buildDir = "../build"
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
tasks.register("clean", Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
5
summer-ospp/2024/Flutter/maxkey_flutter/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
|
||||
@ -0,0 +1,25 @@
|
||||
pluginManagement {
|
||||
def flutterSdkPath = {
|
||||
def properties = new Properties()
|
||||
file("local.properties").withInputStream { properties.load(it) }
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
return flutterSdkPath
|
||||
}()
|
||||
|
||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version "7.3.0" apply false
|
||||
id "org.jetbrains.kotlin.android" version "1.7.10" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
||||
BIN
summer-ospp/2024/Flutter/maxkey_flutter/assets/logo.jpg
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
summer-ospp/2024/Flutter/maxkey_flutter/assets/logo_maxkey.png
Normal file
|
After Width: | Height: | Size: 142 KiB |
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>App</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>io.flutter.flutter.app</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>App</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>12.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -0,0 +1 @@
|
||||
#include "Generated.xcconfig"
|
||||
@ -0,0 +1 @@
|
||||
#include "Generated.xcconfig"
|
||||
@ -0,0 +1,616 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
|
||||
remoteInfo = Runner;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
331C8082294A63A400263BE5 /* RunnerTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */,
|
||||
);
|
||||
path = RunnerTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||
);
|
||||
name = Flutter;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146E51CF9000F007C117D = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9740EEB11CF90186004384FC /* Flutter */,
|
||||
97C146F01CF9000F007C117D /* Runner */,
|
||||
97C146EF1CF9000F007C117D /* Products */,
|
||||
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146EF1CF9000F007C117D /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146F01CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||
97C147021CF9000F007C117D /* Info.plist */,
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||
);
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
331C8080294A63A400263BE5 /* RunnerTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||
buildPhases = (
|
||||
331C807D294A63A400263BE5 /* Sources */,
|
||||
331C807F294A63A400263BE5 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
331C8086294A63A400263BE5 /* PBXTargetDependency */,
|
||||
);
|
||||
name = RunnerTests;
|
||||
productName = RunnerTests;
|
||||
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
9740EEB61CF901F6004384FC /* Run Script */,
|
||||
97C146EA1CF9000F007C117D /* Sources */,
|
||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = Runner;
|
||||
productName = Runner;
|
||||
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
97C146E61CF9000F007C117D /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
LastUpgradeCheck = 1510;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
331C8080294A63A400263BE5 = {
|
||||
CreatedOnToolsVersion = 14.0;
|
||||
TestTargetID = 97C146ED1CF9000F007C117D;
|
||||
};
|
||||
97C146ED1CF9000F007C117D = {
|
||||
CreatedOnToolsVersion = 7.3.1;
|
||||
LastSwiftMigration = 1100;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 97C146E51CF9000F007C117D;
|
||||
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
97C146ED1CF9000F007C117D /* Runner */,
|
||||
331C8080294A63A400263BE5 /* RunnerTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
331C807F294A63A400263BE5 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
97C146EC1CF9000F007C117D /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
|
||||
);
|
||||
name = "Thin Binary";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||
};
|
||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Run Script";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
331C807D294A63A400263BE5 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 97C146ED1CF9000F007C117D /* Runner */;
|
||||
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97C146FB1CF9000F007C117D /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97C147001CF9000F007C117D /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
249021D4217E4FDB00AE95B9 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.dromara.maxkey.maxkeyFlutter;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
331C8088294A63A400263BE5 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.dromara.maxkey.maxkeyFlutter.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
331C8089294A63A400263BE5 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.dromara.maxkey.maxkeyFlutter.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
331C808A294A63A400263BE5 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.dromara.maxkey.maxkeyFlutter.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
97C147031CF9000F007C117D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147041CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
97C147061CF9000F007C117D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.dromara.maxkey.maxkeyFlutter;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147071CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.dromara.maxkey.maxkeyFlutter;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
331C8088294A63A400263BE5 /* Debug */,
|
||||
331C8089294A63A400263BE5 /* Release */,
|
||||
331C808A294A63A400263BE5 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
97C147031CF9000F007C117D /* Debug */,
|
||||
97C147041CF9000F007C117D /* Release */,
|
||||
249021D3217E4FDB00AE95B9 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
97C147061CF9000F007C117D /* Debug */,
|
||||
97C147071CF9000F007C117D /* Release */,
|
||||
249021D4217E4FDB00AE95B9 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -0,0 +1,98 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1510"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO"
|
||||
parallelizable = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "331C8080294A63A400263BE5"
|
||||
BuildableName = "RunnerTests.xctest"
|
||||
BlueprintName = "RunnerTests"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Profile"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
7
summer-ospp/2024/Flutter/maxkey_flutter/ios/Runner.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -0,0 +1,13 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
|
||||
@UIApplicationMain
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,120 @@
|
||||
{
|
||||
"images": [
|
||||
{
|
||||
"size": "20x20",
|
||||
"idiom": "universal",
|
||||
"filename": "icon-20@2x.png",
|
||||
"scale": "2x",
|
||||
"platform": "ios"
|
||||
},
|
||||
{
|
||||
"size": "20x20",
|
||||
"idiom": "universal",
|
||||
"filename": "icon-20@3x.png",
|
||||
"scale": "3x",
|
||||
"platform": "ios"
|
||||
},
|
||||
{
|
||||
"size": "29x29",
|
||||
"idiom": "universal",
|
||||
"filename": "icon-29@2x.png",
|
||||
"scale": "2x",
|
||||
"platform": "ios"
|
||||
},
|
||||
{
|
||||
"size": "29x29",
|
||||
"idiom": "universal",
|
||||
"filename": "icon-29@3x.png",
|
||||
"scale": "3x",
|
||||
"platform": "ios"
|
||||
},
|
||||
{
|
||||
"size": "38x38",
|
||||
"idiom": "universal",
|
||||
"filename": "icon-38@2x.png",
|
||||
"scale": "2x",
|
||||
"platform": "ios"
|
||||
},
|
||||
{
|
||||
"size": "38x38",
|
||||
"idiom": "universal",
|
||||
"filename": "icon-38@3x.png",
|
||||
"scale": "3x",
|
||||
"platform": "ios"
|
||||
},
|
||||
{
|
||||
"size": "40x40",
|
||||
"idiom": "universal",
|
||||
"filename": "icon-40@2x.png",
|
||||
"scale": "2x",
|
||||
"platform": "ios"
|
||||
},
|
||||
{
|
||||
"size": "40x40",
|
||||
"idiom": "universal",
|
||||
"filename": "icon-40@3x.png",
|
||||
"scale": "3x",
|
||||
"platform": "ios"
|
||||
},
|
||||
{
|
||||
"size": "60x60",
|
||||
"idiom": "universal",
|
||||
"filename": "icon-60@2x.png",
|
||||
"scale": "2x",
|
||||
"platform": "ios"
|
||||
},
|
||||
{
|
||||
"size": "60x60",
|
||||
"idiom": "universal",
|
||||
"filename": "icon-60@3x.png",
|
||||
"scale": "3x",
|
||||
"platform": "ios"
|
||||
},
|
||||
{
|
||||
"size": "64x64",
|
||||
"idiom": "universal",
|
||||
"filename": "icon-64@2x.png",
|
||||
"scale": "2x",
|
||||
"platform": "ios"
|
||||
},
|
||||
{
|
||||
"size": "64x64",
|
||||
"idiom": "universal",
|
||||
"filename": "icon-64@3x.png",
|
||||
"scale": "3x",
|
||||
"platform": "ios"
|
||||
},
|
||||
{
|
||||
"size": "68x68",
|
||||
"idiom": "universal",
|
||||
"filename": "icon-68@2x.png",
|
||||
"scale": "2x",
|
||||
"platform": "ios"
|
||||
},
|
||||
{
|
||||
"size": "76x76",
|
||||
"idiom": "universal",
|
||||
"filename": "icon-76@2x.png",
|
||||
"scale": "2x",
|
||||
"platform": "ios"
|
||||
},
|
||||
{
|
||||
"size": "83.5x83.5",
|
||||
"idiom": "universal",
|
||||
"filename": "icon-83.5@2x.png",
|
||||
"scale": "2x",
|
||||
"platform": "ios"
|
||||
},
|
||||
{
|
||||
"size": "1024x1024",
|
||||
"idiom": "universal",
|
||||
"filename": "icon-1024.png",
|
||||
"scale": "1x",
|
||||
"platform": "ios"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"version": 1,
|
||||
"author": "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 68 B |
|
After Width: | Height: | Size: 68 B |
|
After Width: | Height: | Size: 68 B |
@ -0,0 +1,5 @@
|
||||
# Launch Screen Assets
|
||||
|
||||
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
|
||||
|
||||
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
|
||||
@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="LaunchImage" width="168" height="185"/>
|
||||
</resources>
|
||||
</document>
|
||||
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Flutter View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Maxkey Flutter</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>maxkey_flutter</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -0,0 +1 @@
|
||||
#import "GeneratedPluginRegistrant.h"
|
||||
@ -0,0 +1,12 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
import XCTest
|
||||
|
||||
class RunnerTests: XCTestCase {
|
||||
|
||||
func testExample() {
|
||||
// If you add code to the Runner application, consider adding tests here.
|
||||
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
|
||||
}
|
||||
|
||||
}
|
||||
3
summer-ospp/2024/Flutter/maxkey_flutter/l10n.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
arb-dir: lib/l10n
|
||||
template-arb-file: app_en.arb
|
||||
output-localization-file: app_localizations.dart
|
||||
@ -0,0 +1,105 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AppColorScheme {
|
||||
static ColorScheme lightScheme() {
|
||||
return const ColorScheme(
|
||||
brightness: Brightness.light,
|
||||
primary: Color(0xff585992),
|
||||
surfaceTint: Color(0xff585992),
|
||||
onPrimary: Color(0xffffffff),
|
||||
primaryContainer: Color(0xffe1dfff),
|
||||
onPrimaryContainer: Color(0xff14134a),
|
||||
secondary: Color(0xff5d5c72),
|
||||
onSecondary: Color(0xffffffff),
|
||||
secondaryContainer: Color(0xffe2e0f9),
|
||||
onSecondaryContainer: Color(0xff1a1a2c),
|
||||
tertiary: Color(0xff795369),
|
||||
onTertiary: Color(0xffffffff),
|
||||
tertiaryContainer: Color(0xffffd8ec),
|
||||
onTertiaryContainer: Color(0xff2f1124),
|
||||
error: Color(0xffba1a1a),
|
||||
onError: Color(0xffffffff),
|
||||
errorContainer: Color(0xffffdad6),
|
||||
onErrorContainer: Color(0xff410002),
|
||||
surface: Color(0xfffcf8ff),
|
||||
onSurface: Color(0xff1b1b21),
|
||||
onSurfaceVariant: Color(0xff47464f),
|
||||
outline: Color(0xff777680),
|
||||
outlineVariant: Color(0xffc8c5d0),
|
||||
shadow: Color(0xff000000),
|
||||
scrim: Color(0xff000000),
|
||||
inverseSurface: Color(0xff303036),
|
||||
inversePrimary: Color(0xffc1c1ff),
|
||||
primaryFixed: Color(0xffe1dfff),
|
||||
onPrimaryFixed: Color(0xff14134a),
|
||||
primaryFixedDim: Color(0xffc1c1ff),
|
||||
onPrimaryFixedVariant: Color(0xff404178),
|
||||
secondaryFixed: Color(0xffe2e0f9),
|
||||
onSecondaryFixed: Color(0xff1a1a2c),
|
||||
secondaryFixedDim: Color(0xffc6c4dd),
|
||||
onSecondaryFixedVariant: Color(0xff454559),
|
||||
tertiaryFixed: Color(0xffffd8ec),
|
||||
onTertiaryFixed: Color(0xff2f1124),
|
||||
tertiaryFixedDim: Color(0xffe9b9d3),
|
||||
onTertiaryFixedVariant: Color(0xff5f3c51),
|
||||
surfaceDim: Color(0xffdcd9e0),
|
||||
surfaceBright: Color(0xfffcf8ff),
|
||||
surfaceContainerLowest: Color(0xffffffff),
|
||||
surfaceContainerLow: Color(0xfff6f2fa),
|
||||
surfaceContainer: Color(0xfff0ecf4),
|
||||
surfaceContainerHigh: Color(0xffeae7ef),
|
||||
surfaceContainerHighest: Color(0xffe4e1e9),
|
||||
);
|
||||
}
|
||||
|
||||
static ColorScheme darkScheme() {
|
||||
return const ColorScheme(
|
||||
brightness: Brightness.dark,
|
||||
primary: Color(0xffc1c1ff),
|
||||
surfaceTint: Color(0xffc1c1ff),
|
||||
onPrimary: Color(0xff2a2a60),
|
||||
primaryContainer: Color(0xff404178),
|
||||
onPrimaryContainer: Color(0xffe1dfff),
|
||||
secondary: Color(0xffc6c4dd),
|
||||
onSecondary: Color(0xff2f2f42),
|
||||
secondaryContainer: Color(0xff454559),
|
||||
onSecondaryContainer: Color(0xffe2e0f9),
|
||||
tertiary: Color(0xffe9b9d3),
|
||||
onTertiary: Color(0xff46263a),
|
||||
tertiaryContainer: Color(0xff5f3c51),
|
||||
onTertiaryContainer: Color(0xffffd8ec),
|
||||
error: Color(0xffffb4ab),
|
||||
onError: Color(0xff690005),
|
||||
errorContainer: Color(0xff93000a),
|
||||
onErrorContainer: Color(0xffffdad6),
|
||||
surface: Color(0xff131318),
|
||||
onSurface: Color(0xffe4e1e9),
|
||||
onSurfaceVariant: Color(0xffc8c5d0),
|
||||
outline: Color(0xff918f9a),
|
||||
outlineVariant: Color(0xff47464f),
|
||||
shadow: Color(0xff000000),
|
||||
scrim: Color(0xff000000),
|
||||
inverseSurface: Color(0xffe4e1e9),
|
||||
inversePrimary: Color(0xff585992),
|
||||
primaryFixed: Color(0xffe1dfff),
|
||||
onPrimaryFixed: Color(0xff14134a),
|
||||
primaryFixedDim: Color(0xffc1c1ff),
|
||||
onPrimaryFixedVariant: Color(0xff404178),
|
||||
secondaryFixed: Color(0xffe2e0f9),
|
||||
onSecondaryFixed: Color(0xff1a1a2c),
|
||||
secondaryFixedDim: Color(0xffc6c4dd),
|
||||
onSecondaryFixedVariant: Color(0xff454559),
|
||||
tertiaryFixed: Color(0xffffd8ec),
|
||||
onTertiaryFixed: Color(0xff2f1124),
|
||||
tertiaryFixedDim: Color(0xffe9b9d3),
|
||||
onTertiaryFixedVariant: Color(0xff5f3c51),
|
||||
surfaceDim: Color(0xff131318),
|
||||
surfaceBright: Color(0xff39383f),
|
||||
surfaceContainerLowest: Color(0xff0e0e13),
|
||||
surfaceContainerLow: Color(0xff1b1b21),
|
||||
surfaceContainer: Color(0xff1f1f25),
|
||||
surfaceContainerHigh: Color(0xff2a292f),
|
||||
surfaceContainerHighest: Color(0xff35343a),
|
||||
);
|
||||
}
|
||||
}
|
||||
66
summer-ospp/2024/Flutter/maxkey_flutter/lib/l10n/app_en.arb
Normal file
@ -0,0 +1,66 @@
|
||||
{
|
||||
"homePageNewTotpBtnScanPage": "Scan TOTP",
|
||||
"homePagenewTotpBtnErr": "Unsupported QR code",
|
||||
"homePagenewTotpBtn": "Scan TOTP",
|
||||
|
||||
"homePageTotpListViewNoTOTP": "No TOTP",
|
||||
"homePageTotpListViewConfirmDialog": "Confirm to delete",
|
||||
"homePageTotpListViewConfirmDialogCancelBtn": "Cancel",
|
||||
"homePageTotpListViewConfirmDialogConfirmBtn": "Confirm",
|
||||
|
||||
"homePageUserCardGreetingMorning": "Morning",
|
||||
"homePageUserCardGreetingNoon": "Noon",
|
||||
"homePageUserCardGreetingAfternoon": "Afternoon",
|
||||
"homePageUserCardGreetingEvening": "Evening",
|
||||
"homePageUserCardScanPage": "Scan to login",
|
||||
|
||||
"loginPageSettingBtn": "Settings",
|
||||
"loginPageLoginViewUsername": "Username",
|
||||
"loginPageLoginViewPassword": "Password",
|
||||
"loginPageLoginViewCaptcha": "Captcha",
|
||||
"loginPageLoginViewLoginBtn": "Login",
|
||||
|
||||
"settingsPageTitle": "Settings",
|
||||
"settingsPageHostSettingDialog": "Specify host",
|
||||
"settingsPageHostSettingDialogHost": "Host",
|
||||
"settingsPageHostSettingDialogTestSucceed": "Succeed",
|
||||
"settingsPageHostSettingDialogTestFail": "Fail",
|
||||
"settingsPageHostSettingDialogTestBtn": "Test",
|
||||
"settingsPageHostSettingDialogCancleBtn": "Cancle",
|
||||
"settingsPageHostSettingDialogConfirmBtn": "Confirm",
|
||||
"settingsPageHostSettingTitle": "Specify host",
|
||||
"settingsPageHostSettingDesc": "If MaxKey is not working properly, you may need to change this",
|
||||
|
||||
"settingsPageShowLogBtnTitle": "Check log",
|
||||
"settingsPageLogDisplayPageTitle": "Check log",
|
||||
|
||||
"settingsPageThemeModeSwitchTitle": "Theme",
|
||||
|
||||
"userPageFullUserInfoDialogTitle": "User info",
|
||||
"userPageFullUserInfoDialogDisplayName": "Name",
|
||||
"userPageFullUserInfoDialogUsername": "Username",
|
||||
"userPageFullUserInfoDialogGender": "Gender",
|
||||
"userPageFullUserInfoDialogEmployeeNumber": "Employee ID",
|
||||
"userPageFullUserInfoDialogMobile": "Mobile",
|
||||
"userPageFullUserInfoDialogEmail": "Email",
|
||||
"userPageFullUserInfoDialogUserType": "User type",
|
||||
"userPageFullUserInfoDialogUserState": "User state",
|
||||
"userPageFullUserInfoDialogIdType": "ID type",
|
||||
"userPageFullUserInfoDialogIdCardNo": "ID card Nunber",
|
||||
"userPageFullUserInfoDialogMarried": "Married",
|
||||
"userPageFullUserInfoDialogBirth": "Birth date",
|
||||
"userPageFullUserInfoDialogOrganization": "Organization",
|
||||
"userPageFullUserInfoDialogDivision": "Division",
|
||||
"userPageFullUserInfoDialogDepartmentId": "Department ID",
|
||||
"userPageFullUserInfoDialogDepartment": "Department",
|
||||
"userPageFullUserInfoDialogJobTitle": "Job title",
|
||||
"userPageFullUserInfoDialogJobLevel": "Job level",
|
||||
"userPageFullUserInfoDialogManager": "Manager",
|
||||
|
||||
"userPageTitle": "User",
|
||||
"userPageUserInfoBtn": "User info",
|
||||
"userPageSettingsBtn": "Settings",
|
||||
"userPageLogoutBtn": "Logout",
|
||||
|
||||
"scanPagePermissionDeniedMsg": "Camera permissions are not granted.MaxKey requires camera permissions to scan the QR code and obtain a TOTP."
|
||||
}
|
||||
66
summer-ospp/2024/Flutter/maxkey_flutter/lib/l10n/app_zh.arb
Normal file
@ -0,0 +1,66 @@
|
||||
{
|
||||
"homePageNewTotpBtnScanPage": "录入时间令牌",
|
||||
"homePagenewTotpBtnErr": "不支持的二维码",
|
||||
"homePagenewTotpBtn": "录入时间令牌",
|
||||
|
||||
"homePageTotpListViewNoTOTP": "没有时间令牌",
|
||||
"homePageTotpListViewConfirmDialog": "删除选中的时间令牌?",
|
||||
"homePageTotpListViewConfirmDialogCancelBtn": "取消",
|
||||
"homePageTotpListViewConfirmDialogConfirmBtn": "确认",
|
||||
|
||||
"homePageUserCardGreetingMorning": "早上好",
|
||||
"homePageUserCardGreetingNoon": "中午好",
|
||||
"homePageUserCardGreetingAfternoon": "下午好",
|
||||
"homePageUserCardGreetingEvening": "晚上好",
|
||||
"homePageUserCardScanPage": "扫码登陆",
|
||||
|
||||
"loginPageSettingBtn": "设置",
|
||||
"loginPageLoginViewUsername": "用户名",
|
||||
"loginPageLoginViewPassword": "密码",
|
||||
"loginPageLoginViewCaptcha": "验证码",
|
||||
"loginPageLoginViewLoginBtn": "登录",
|
||||
|
||||
"settingsPageTitle": "设置",
|
||||
"settingsPageHostSettingDialog": "指定主机",
|
||||
"settingsPageHostSettingDialogHost": "主机地址",
|
||||
"settingsPageHostSettingDialogTestSucceed": "连接成功",
|
||||
"settingsPageHostSettingDialogTestFail": "连接失败",
|
||||
"settingsPageHostSettingDialogTestBtn": "验证",
|
||||
"settingsPageHostSettingDialogCancleBtn": "取消",
|
||||
"settingsPageHostSettingDialogConfirmBtn": "确认",
|
||||
"settingsPageHostSettingTitle": "指定主机",
|
||||
"settingsPageHostSettingDesc": "如果网络状态良好但 MaxKey 仍无法正常工作,可能需要修改此项设置",
|
||||
|
||||
"settingsPageShowLogBtnTitle": "查看日志",
|
||||
"settingsPageLogDisplayPageTitle": "查看日志",
|
||||
|
||||
"settingsPageThemeModeSwitchTitle": "主题模式",
|
||||
|
||||
"userPageFullUserInfoDialogTitle": "详细信息",
|
||||
"userPageFullUserInfoDialogDisplayName": "姓名",
|
||||
"userPageFullUserInfoDialogUsername": "登陆账号",
|
||||
"userPageFullUserInfoDialogGender": "性别",
|
||||
"userPageFullUserInfoDialogEmployeeNumber": "员工编号",
|
||||
"userPageFullUserInfoDialogMobile": "手机号码",
|
||||
"userPageFullUserInfoDialogEmail": "邮箱",
|
||||
"userPageFullUserInfoDialogUserType": "用户类型",
|
||||
"userPageFullUserInfoDialogUserState": "用户状态",
|
||||
"userPageFullUserInfoDialogIdType": "证件类型",
|
||||
"userPageFullUserInfoDialogIdCardNo": "证件号码",
|
||||
"userPageFullUserInfoDialogMarried": "婚姻状态",
|
||||
"userPageFullUserInfoDialogBirth": "出生日期",
|
||||
"userPageFullUserInfoDialogOrganization": "所属组织",
|
||||
"userPageFullUserInfoDialogDivision": "分支机构",
|
||||
"userPageFullUserInfoDialogDepartmentId": "部门编号",
|
||||
"userPageFullUserInfoDialogDepartment": "部门名称",
|
||||
"userPageFullUserInfoDialogJobTitle": "职位",
|
||||
"userPageFullUserInfoDialogJobLevel": "级别",
|
||||
"userPageFullUserInfoDialogManager": "上级经理",
|
||||
|
||||
"userPageTitle": "用户",
|
||||
"userPageUserInfoBtn": "详细信息",
|
||||
"userPageSettingsBtn": "设置",
|
||||
"userPageLogoutBtn": "退出账号",
|
||||
|
||||
"scanPagePermissionDeniedMsg": "未授予摄像头权限。\nMaxKey 需要摄像头权限以扫描二维码并获取时间令牌。"
|
||||
}
|
||||
97
summer-ospp/2024/Flutter/maxkey_flutter/lib/main.dart
Normal file
@ -0,0 +1,97 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:maxkey_flutter/app_color_scheme.dart';
|
||||
import 'package:maxkey_flutter/maxkey/maxkey.dart';
|
||||
import 'package:maxkey_flutter/maxkey/services/users.service.dart';
|
||||
import 'package:maxkey_flutter/pages/home_page/page.dart';
|
||||
import 'package:maxkey_flutter/pages/login_page/page.dart';
|
||||
import 'package:maxkey_flutter/pages/scan_page.dart';
|
||||
import 'package:maxkey_flutter/pages/settings_page/page.dart';
|
||||
import 'package:maxkey_flutter/pages/user_page/page.dart';
|
||||
import 'package:maxkey_flutter/persistent.dart';
|
||||
import 'package:maxkey_flutter/utils.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await MaxKeyPersistent.init();
|
||||
runApp(const AppEntry());
|
||||
}
|
||||
|
||||
class AppEntry extends StatelessWidget {
|
||||
const AppEntry({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: MaxKeyPersistent.instance.themeModeListenable,
|
||||
builder: (context, value, _) {
|
||||
return MaterialApp.router(
|
||||
scaffoldMessengerKey: SCAFFOLD_MESSENGER_KEY,
|
||||
themeMode: value,
|
||||
theme: ThemeData.from(colorScheme: AppColorScheme.lightScheme()),
|
||||
darkTheme: ThemeData.from(colorScheme: AppColorScheme.darkScheme()),
|
||||
localizationsDelegates: const <LocalizationsDelegate<dynamic>>[
|
||||
AppLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
routerConfig: _routerConfig,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
GoRouter _routerConfig = GoRouter(
|
||||
navigatorKey: NAVIGATOR_KEY,
|
||||
initialLocation: !MaxKey.instance.authnService.localAuth()
|
||||
? RoutePath.loginPage
|
||||
: RoutePath.homePage,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: RoutePath.loginPage,
|
||||
pageBuilder: (context, state) => const CupertinoPage(child: LoginPage()),
|
||||
),
|
||||
GoRoute(
|
||||
path: RoutePath.homePage,
|
||||
pageBuilder: (context, state) => const CupertinoPage(child: HomePage()),
|
||||
),
|
||||
GoRoute(
|
||||
path: RoutePath.scanPage,
|
||||
pageBuilder: (context, state) => CupertinoPage(
|
||||
child: ScanPage(title: state.extra as String),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: RoutePath.userPage,
|
||||
pageBuilder: (context, state) => CupertinoPage(
|
||||
child: UserPage(user: state.extra as MaxKeyUser?),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: RoutePath.settingsPage,
|
||||
pageBuilder: (context, state) =>
|
||||
const CupertinoPage(child: SettingsPage()),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
// const _supportedLocales = <Locale>[
|
||||
// Locale('en', 'US'),
|
||||
// // generic Chinese 'zh'
|
||||
// Locale.fromSubtags(languageCode: 'zh'),
|
||||
// // generic simplified Chinese 'zh_Hans'
|
||||
// Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans'),
|
||||
// // generic traditional Chinese 'zh_Hant'
|
||||
// Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'),
|
||||
// // 'zh_Hans_CN'
|
||||
// Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'CN'),
|
||||
// // 'zh_Hant_TW'
|
||||
// Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'TW'),
|
||||
// // 'zh_Hant_HK'
|
||||
// Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'HK'),
|
||||
// ];
|
||||
@ -0,0 +1,90 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:maxkey_flutter/maxkey/services/authn.service.dart';
|
||||
import 'package:maxkey_flutter/maxkey/services/image_captcha.service.dart';
|
||||
import 'package:maxkey_flutter/maxkey/services/time_based.service.dart';
|
||||
import 'package:maxkey_flutter/maxkey/services/users.service.dart';
|
||||
import 'package:maxkey_flutter/persistent.dart';
|
||||
import 'package:maxkey_flutter/utils.dart';
|
||||
|
||||
/// 会在需要刷新的时候通知
|
||||
class MaxKey with ChangeNotifier {
|
||||
final _dio = Dio(BaseOptions(
|
||||
baseUrl: MaxKeyPersistent.instance.baseUrl,
|
||||
connectTimeout: const Duration(seconds: 10),
|
||||
));
|
||||
late final authnService = AuthnService(_dio);
|
||||
late final imageCaptchaService = ImageCaptchaService(_dio);
|
||||
late final timeBasedService = TimeBasedService(_dio);
|
||||
late final usersService = UsersService(_dio);
|
||||
|
||||
static MaxKey? _instance;
|
||||
static MaxKey get instance {
|
||||
_instance ??= MaxKey._();
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
MaxKey._() {
|
||||
_dio.interceptors.add(InterceptorsWrapper(
|
||||
onResponse: (response, handler) {
|
||||
final cookies = response.headers[HttpHeaders.setCookieHeader];
|
||||
if (cookies != null) {
|
||||
maxKeyCookies = List.from(cookies);
|
||||
}
|
||||
handler.next(response);
|
||||
},
|
||||
onError: (error, handler) {
|
||||
LOGGER.e(error.type);
|
||||
LOGGER.e(error.response);
|
||||
if (error.type == DioExceptionType.badResponse) {
|
||||
SCAFFOLD_MESSENGER_KEY.currentState?.showSnackBar(
|
||||
SnackBar(
|
||||
content: const Text("Please login again."),
|
||||
action: SnackBarAction(
|
||||
label: "Login",
|
||||
onPressed: () async {
|
||||
_dio.options.headers.remove(HttpHeaders.authorizationHeader);
|
||||
await MaxKeyPersistent.instance.clearToken();
|
||||
NAVIGATOR_KEY.currentContext?.pushReplacement(
|
||||
RoutePath.loginPage,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
handler.next(error);
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
void updateBaseUrl() {
|
||||
LOGGER.i("old baseUrl: ${_dio.options.baseUrl}");
|
||||
_dio.options.baseUrl = MaxKeyPersistent.instance.baseUrl;
|
||||
LOGGER.i("new baseUrl: ${_dio.options.baseUrl}");
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
List<String> maxKeyCookies = [];
|
||||
|
||||
Future<bool> maxKeyNetworkTest({String? host}) async {
|
||||
try {
|
||||
LOGGER.i(
|
||||
"[MaxKeyNetworkTest] GET: ${host == null ? MaxKeyPersistent.instance.baseUrl : "http://$host/sign"}",
|
||||
);
|
||||
|
||||
await _dio.get(
|
||||
host == null ? MaxKeyPersistent.instance.baseUrl : "http://$host/sign",
|
||||
);
|
||||
LOGGER.i("MaxKeyNetworkTest: true");
|
||||
return true;
|
||||
} catch (err) {
|
||||
LOGGER.e(err);
|
||||
}
|
||||
LOGGER.i("MaxKeyNetworkTest: false");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,161 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:maxkey_flutter/persistent.dart';
|
||||
import 'package:maxkey_flutter/utils.dart';
|
||||
|
||||
typedef ExpectedErrorHandler = void Function(String msg);
|
||||
|
||||
class AuthnService {
|
||||
final Dio _dio;
|
||||
|
||||
AuthnService(this._dio);
|
||||
|
||||
/// 获取 state 类型
|
||||
Future<String?> get() async {
|
||||
try {
|
||||
LOGGER.i("GET: /login/get?_allow_anonymous=true");
|
||||
|
||||
final res = await _dio.get(
|
||||
"/login/get?_allow_anonymous=true",
|
||||
queryParameters: {"remember_me": true},
|
||||
);
|
||||
return res.data["data"]["state"];
|
||||
} catch (err) {
|
||||
LOGGER.e(err);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 短信验证码
|
||||
Future<bool> produceOtp({
|
||||
required ExpectedErrorHandler expectedErrorHandler,
|
||||
required String mobile,
|
||||
}) async {
|
||||
try {
|
||||
LOGGER.i("GET: /login/sendotp/$mobile?_allow_anonymous=true");
|
||||
|
||||
final res = await _dio.get(
|
||||
"/login/sendotp/$mobile?_allow_anonymous=true",
|
||||
queryParameters: {"mobile": mobile},
|
||||
);
|
||||
|
||||
if (res.data["code"] != 0) {
|
||||
final msg = res.data["message"] ?? "验证码发送失败";
|
||||
expectedErrorHandler(msg);
|
||||
LOGGER.w(msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
LOGGER.e(err);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// 账密登录
|
||||
Future<bool> loginNormal({
|
||||
required ExpectedErrorHandler expectedErrorHandler,
|
||||
required String state,
|
||||
required String username,
|
||||
required String password,
|
||||
required String captcha,
|
||||
}) async {
|
||||
try {
|
||||
LOGGER.i("POST: /login/signin?_allow_anonymous=true");
|
||||
|
||||
final res = await _dio.post(
|
||||
"/login/signin?_allow_anonymous=true",
|
||||
data: {
|
||||
"authType": "app",
|
||||
"state": state,
|
||||
"username": username,
|
||||
"password": password,
|
||||
"captcha": captcha,
|
||||
"remeberMe": true,
|
||||
},
|
||||
);
|
||||
|
||||
if (res.data["code"] != 0) {
|
||||
final msg = res.data["message"] ?? "登陆失败";
|
||||
expectedErrorHandler(msg);
|
||||
LOGGER.w(msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
await onlineAuth(
|
||||
token: res.data["data"]["token"],
|
||||
onlineTicket: res.data["data"]["ticket"],
|
||||
username: res.data["data"]["username"],
|
||||
);
|
||||
return true;
|
||||
} catch (err) {
|
||||
LOGGER.e(err);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<bool> scanCode({
|
||||
required ExpectedErrorHandler expectedErrorHandler,
|
||||
required String code,
|
||||
}) async {
|
||||
final token = MaxKeyPersistent.instance.token;
|
||||
if (token == null) {
|
||||
expectedErrorHandler("未登录");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
LOGGER.i("POST: /login/scanCode");
|
||||
final res = await _dio.post(
|
||||
"/login/scanCode",
|
||||
data: {"jwtToken": token, "code": code},
|
||||
);
|
||||
|
||||
if (res.data["code"] != 0) {
|
||||
final msg = res.data["message"] ?? "扫码登陆失败";
|
||||
expectedErrorHandler(msg);
|
||||
LOGGER.w(msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
LOGGER.e(err);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<void> onlineAuth({
|
||||
required String token,
|
||||
required String onlineTicket,
|
||||
required String username,
|
||||
}) async {
|
||||
await MaxKeyPersistent.instance.setUser(username);
|
||||
await MaxKeyPersistent.instance.setToken(token);
|
||||
_dio.options.headers[HttpHeaders.authorizationHeader] = token;
|
||||
}
|
||||
|
||||
bool localAuth() {
|
||||
final token = MaxKeyPersistent.instance.token;
|
||||
if (token == null) return false;
|
||||
|
||||
_dio.options.headers[HttpHeaders.authorizationHeader] = token;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// 登出并清除本地缓存的 token
|
||||
Future<void> logout() async {
|
||||
try {
|
||||
LOGGER.i("GET: /logout");
|
||||
await _dio.get("/logout");
|
||||
} catch (err) {
|
||||
LOGGER.e(err);
|
||||
}
|
||||
|
||||
_dio.options.headers.remove(HttpHeaders.authorizationHeader);
|
||||
|
||||
await MaxKeyPersistent.instance.clearToken();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:maxkey_flutter/utils.dart';
|
||||
|
||||
class ImageCaptchaService {
|
||||
final Dio _dio;
|
||||
ImageCaptchaService(this._dio);
|
||||
|
||||
Future<Uint8List?> captcha({required String state}) async {
|
||||
try {
|
||||
LOGGER.i("GET: /captcha?_allow_anonymous=true");
|
||||
|
||||
final res = await _dio.get(
|
||||
"/captcha?_allow_anonymous=true",
|
||||
queryParameters: {"state": state},
|
||||
);
|
||||
|
||||
final String base64Image = res.data["data"]["image"];
|
||||
return base64.decode(base64Image.split(",")[1]);
|
||||
} catch (err) {
|
||||
LOGGER.e(err);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:maxkey_flutter/utils.dart';
|
||||
|
||||
class TimeBasedService {
|
||||
final Dio _dio;
|
||||
TimeBasedService(this._dio);
|
||||
|
||||
Future<bool> verify({required String totpCode}) async {
|
||||
try {
|
||||
LOGGER.i("GET: /config/verify?otp=$totpCode");
|
||||
|
||||
final res = await _dio.get(
|
||||
"/config/verify",
|
||||
queryParameters: {"otp": totpCode},
|
||||
);
|
||||
|
||||
if (res.data["code"] != 0) {
|
||||
LOGGER.w("验证失败:${res.data["message"]}");
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGGER.i("验证成功");
|
||||
return true;
|
||||
} catch (err) {
|
||||
LOGGER.e(err);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,245 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:maxkey_flutter/utils.dart';
|
||||
|
||||
class MaxKeyUser {
|
||||
final String displayName;
|
||||
final Uint8List? picture;
|
||||
|
||||
MaxKeyUser(this.displayName, this.picture);
|
||||
|
||||
factory MaxKeyUser.fromMap(Map map) {
|
||||
final String displayName = map["data"]["displayName"];
|
||||
final String? pictureBase64 = map["data"]["pictureBase64"];
|
||||
final picture = pictureBase64?.split(",")[1].base64ToBuf();
|
||||
return MaxKeyUser(displayName, picture);
|
||||
}
|
||||
}
|
||||
|
||||
class MaxKeyUserInfo {
|
||||
MaxKeyUserInfo({
|
||||
/// 姓名
|
||||
required this.displayName,
|
||||
|
||||
/// 登陆账号
|
||||
required this.username,
|
||||
|
||||
/// 性别
|
||||
required this.gender,
|
||||
|
||||
/// 员工编号
|
||||
required this.employeeNumber,
|
||||
|
||||
/// 手机号码
|
||||
required this.mobile,
|
||||
|
||||
/// 邮箱
|
||||
required this.email,
|
||||
|
||||
/// 用户类型
|
||||
required this.userType,
|
||||
|
||||
/// 用户状态
|
||||
required this.userState,
|
||||
|
||||
/// 状态
|
||||
required this.status,
|
||||
|
||||
/// 证件类型
|
||||
required this.idType,
|
||||
|
||||
/// 证件号码
|
||||
required this.idCardNo,
|
||||
|
||||
/// 婚姻状态
|
||||
required this.married,
|
||||
|
||||
/// 出生日期
|
||||
required this.birthDate,
|
||||
|
||||
/// 所属组织
|
||||
required this.organization,
|
||||
|
||||
/// 分支机构
|
||||
required this.division,
|
||||
|
||||
/// 部门编号
|
||||
required this.departmentId,
|
||||
|
||||
/// 部门名称
|
||||
required this.department,
|
||||
|
||||
/// 职位
|
||||
required this.jobTitle,
|
||||
|
||||
/// 级别
|
||||
required this.jobLevel,
|
||||
|
||||
/// 上级经理
|
||||
required this.manager,
|
||||
});
|
||||
|
||||
static MaxKeyUserInfo? fromMap(Map map) {
|
||||
return MaxKeyUserInfo(
|
||||
displayName: map["displayName"].toString(),
|
||||
username: map["username"].toString(),
|
||||
gender: switch (map["gender"]) { 1 => "女", 2 => "男", _ => _unknwon },
|
||||
employeeNumber: map["employeeNumber"]?.toString() ?? _unknwon,
|
||||
mobile: map["mobile"]?.toString() ?? _unknwon,
|
||||
email: map["email"]?.toString() ?? _unknwon,
|
||||
userType: switch (map["userType"]) {
|
||||
"EMPLOYEE" => "内部员工",
|
||||
"CONTRACTOR" => "承包商",
|
||||
"CUSTOMER" => "客户",
|
||||
"SUPPLIER" => "供应商",
|
||||
"PARTNER" => "合作伙伴",
|
||||
"EXTERNAL" => "外部用户",
|
||||
"INTERN" => "实习生",
|
||||
"TEMP" => "临时用户",
|
||||
"DEALER" => "经销商",
|
||||
_ => _unknwon,
|
||||
},
|
||||
userState: switch (map["userState"]) {
|
||||
"RESIDENT" => "在职",
|
||||
"WITHDRAWN" => "离职",
|
||||
"INACTIVE" => "停薪留职",
|
||||
"RETIREE" => "退休",
|
||||
_ => _unknwon,
|
||||
},
|
||||
status: switch (map["status"]) {
|
||||
1 => "活动",
|
||||
2 => "不活动",
|
||||
4 => "禁用",
|
||||
5 => "锁定",
|
||||
9 => "已删除",
|
||||
_ => _unknwon,
|
||||
},
|
||||
idType: switch (map["idType"]) {
|
||||
0 => "未知",
|
||||
1 => "身份证",
|
||||
2 => "护照",
|
||||
3 => "学生证",
|
||||
4 => "军人证",
|
||||
_ => _unknwon,
|
||||
},
|
||||
idCardNo: map["idCardNo"]?.toString() ?? _unknwon,
|
||||
married: switch (map["married"]) {
|
||||
0 => "未知",
|
||||
1 => "单身",
|
||||
2 => "已婚",
|
||||
3 => "离异",
|
||||
4 => "丧偶",
|
||||
_ => _unknwon,
|
||||
},
|
||||
birthDate: map["birthDate"]?.toString() ?? _unknwon,
|
||||
organization: map["organization"]?.toString() ?? _unknwon,
|
||||
division: map["division"]?.toString() ?? _unknwon,
|
||||
departmentId: map["departmentId"]?.toString() ?? _unknwon,
|
||||
department: map["deparment"]?.toString() ?? _unknwon,
|
||||
jobTitle: map["jobTitle"]?.toString() ?? _unknwon,
|
||||
jobLevel: map["jobLevel"]?.toString() ?? _unknwon,
|
||||
manager: map["manager"]?.toString() ?? _unknwon,
|
||||
);
|
||||
}
|
||||
|
||||
/// 姓名
|
||||
final String displayName;
|
||||
|
||||
/// 登陆账号
|
||||
final String username;
|
||||
|
||||
/// 性别
|
||||
final String gender;
|
||||
|
||||
/// 员工编号
|
||||
final String employeeNumber;
|
||||
|
||||
/// 手机号码
|
||||
final String mobile;
|
||||
|
||||
/// 邮箱
|
||||
final String email;
|
||||
|
||||
/// 用户类型
|
||||
final String userType;
|
||||
|
||||
/// 用户状态
|
||||
final String userState;
|
||||
|
||||
/// 状态
|
||||
final String status;
|
||||
|
||||
/// 证件类型
|
||||
final String idType;
|
||||
|
||||
/// 证件号码
|
||||
final String idCardNo;
|
||||
|
||||
/// 婚姻状态
|
||||
final String married;
|
||||
|
||||
/// 出生日期
|
||||
final String birthDate;
|
||||
|
||||
/// 所属组织
|
||||
final String organization;
|
||||
|
||||
/// 分支机构
|
||||
final String division;
|
||||
|
||||
/// 部门编号
|
||||
final String departmentId;
|
||||
|
||||
/// 部门名称
|
||||
final String department;
|
||||
|
||||
/// 职位
|
||||
final String jobTitle;
|
||||
|
||||
/// 级别
|
||||
final String jobLevel;
|
||||
|
||||
/// 上级经理
|
||||
final String manager;
|
||||
|
||||
static const String _unknwon = "未知";
|
||||
}
|
||||
|
||||
class UsersService {
|
||||
final Dio _dio;
|
||||
UsersService(this._dio);
|
||||
|
||||
Future<MaxKeyUser?> getBasicUserInfo() async {
|
||||
try {
|
||||
LOGGER.i("GET: /users/profile/get");
|
||||
|
||||
final res = await _dio.get("/users/profile/get");
|
||||
if (res.data["code"] != 0) {
|
||||
LOGGER.w(res.data["message"]);
|
||||
return null;
|
||||
}
|
||||
return MaxKeyUser.fromMap(res.data);
|
||||
} catch (err) {
|
||||
LOGGER.e(err);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<MaxKeyUserInfo?> getFullUserInfo() async {
|
||||
try {
|
||||
LOGGER.i("GET: /users/profile/get");
|
||||
|
||||
final res = await _dio.get("/users/profile/get");
|
||||
if (res.data["code"] != 0) {
|
||||
LOGGER.w(res.data["message"]);
|
||||
return null;
|
||||
}
|
||||
|
||||
return MaxKeyUserInfo.fromMap(res.data["data"]);
|
||||
} catch (err) {
|
||||
LOGGER.e(err);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
part of 'package:maxkey_flutter/pages/home_page/page.dart';
|
||||
|
||||
class _NewTotpBtn extends StatelessWidget {
|
||||
const _NewTotpBtn({super.key, required this.controller});
|
||||
|
||||
final _TotpListViewController controller;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
|
||||
return Ink(
|
||||
decoration: BoxDecoration(
|
||||
color: scheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
onTap: () async {
|
||||
final qrCodeValue = await context.push<String?>(
|
||||
RoutePath.scanPage,
|
||||
extra: AppLocalizations.of(context)!.homePageNewTotpBtnScanPage,
|
||||
);
|
||||
|
||||
if (qrCodeValue == null) return;
|
||||
|
||||
final newTotp = Totp.fromUri(qrCodeValue);
|
||||
if (newTotp == null) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(AppLocalizations.of(context)!.homePagenewTotpBtnErr)),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final totps = MaxKeyPersistent.instance.totps;
|
||||
totps.add(newTotp);
|
||||
await MaxKeyPersistent.instance.saveTotps(totps);
|
||||
|
||||
controller.update();
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||
child: Center(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.homePagenewTotpBtn,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,250 @@
|
||||
part of 'package:maxkey_flutter/pages/home_page/page.dart';
|
||||
|
||||
class _TotpListViewController with ChangeNotifier {
|
||||
List<Totp> get totps => MaxKeyPersistent.instance.totps;
|
||||
|
||||
bool multiSelect = false;
|
||||
final Set<Totp> selected = {};
|
||||
|
||||
void setMultiSelect(bool value) {
|
||||
multiSelect = value;
|
||||
selected.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _select(Totp totp) {
|
||||
if (!multiSelect) return;
|
||||
|
||||
selected.add(totp);
|
||||
}
|
||||
|
||||
void _unselect(Totp totp) {
|
||||
if (!multiSelect) return;
|
||||
|
||||
selected.remove(totp);
|
||||
}
|
||||
|
||||
void selectOrNot(Totp totp) {
|
||||
if (!multiSelect) return;
|
||||
|
||||
if (selected.contains(totp)) {
|
||||
_unselect(totp);
|
||||
} else {
|
||||
_select(totp);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
bool hasSelected(Totp totp) {
|
||||
if (!multiSelect) return false;
|
||||
|
||||
return selected.contains(totp);
|
||||
}
|
||||
|
||||
Future<void> deleteSelected() async {
|
||||
final modifiedTotps = totps;
|
||||
for (var selectedItem in selected) {
|
||||
modifiedTotps.remove(selectedItem);
|
||||
}
|
||||
await MaxKeyPersistent.instance.saveTotps(modifiedTotps);
|
||||
setMultiSelect(false);
|
||||
}
|
||||
|
||||
void update() => notifyListeners();
|
||||
}
|
||||
|
||||
class _TotpListView extends StatelessWidget {
|
||||
const _TotpListView({super.key, required this.controller});
|
||||
|
||||
final _TotpListViewController controller;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
return Expanded(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
child: Ink(
|
||||
decoration: BoxDecoration(
|
||||
color: scheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
child: ListenableBuilder(
|
||||
listenable: controller,
|
||||
builder: (context, _) {
|
||||
if (controller.totps.isEmpty) {
|
||||
return Center(child: Text(AppLocalizations.of(context)!.homePageTotpListViewNoTOTP));
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: controller.totps.length,
|
||||
itemBuilder: (context, i) => _TotpCodeCard(
|
||||
totp: controller.totps[i],
|
||||
controller: controller,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (controller.multiSelect) ...[
|
||||
Ink(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
controller.setMultiSelect(false);
|
||||
},
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||
child: Icon(
|
||||
Icons.cancel_outlined,
|
||||
color: scheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
Ink(
|
||||
decoration: BoxDecoration(
|
||||
color: scheme.errorContainer,
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
final confirm = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(AppLocalizations.of(context)!.homePageTotpListViewConfirmDialog),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(false);
|
||||
},
|
||||
child: Text(AppLocalizations.of(context)!.homePageTotpListViewConfirmDialogCancelBtn),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
child: Text(AppLocalizations.of(context)!.homePageTotpListViewConfirmDialogConfirmBtn),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (confirm == true) {
|
||||
await controller.deleteSelected();
|
||||
}
|
||||
},
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||
child: Icon(Icons.delete, color: scheme.error),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
]
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TotpCodeCard extends StatefulWidget {
|
||||
const _TotpCodeCard(
|
||||
{super.key, required this.totp, required this.controller});
|
||||
|
||||
final Totp totp;
|
||||
final _TotpListViewController controller;
|
||||
|
||||
@override
|
||||
State<_TotpCodeCard> createState() => _TotpCodeCardState();
|
||||
}
|
||||
|
||||
class _TotpCodeCardState extends State<_TotpCodeCard> {
|
||||
late String password;
|
||||
late Timer updater;
|
||||
int validity = 0;
|
||||
|
||||
int getValidity() {
|
||||
final utc = DateTime.timestamp();
|
||||
return widget.totp.interval - (utc.millisecondsSinceEpoch ~/ 1000 % 30);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
password = widget.totp.now;
|
||||
validity = getValidity();
|
||||
|
||||
updater = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
validity = getValidity();
|
||||
|
||||
setState(() {
|
||||
validity = getValidity();
|
||||
if (validity == widget.totp.interval) {
|
||||
LOGGER.i(DateTime.now());
|
||||
password = widget.totp.now;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
onLongPress: widget.controller.multiSelect
|
||||
? null
|
||||
: () {
|
||||
widget.controller.setMultiSelect(!widget.controller.multiSelect);
|
||||
},
|
||||
onTap: widget.controller.multiSelect
|
||||
? () {
|
||||
widget.controller.selectOrNot(widget.totp);
|
||||
}
|
||||
: null,
|
||||
leading: widget.controller.multiSelect
|
||||
? Checkbox(
|
||||
value: widget.controller.hasSelected(widget.totp),
|
||||
onChanged: (_) {
|
||||
widget.controller.selectOrNot(widget.totp);
|
||||
},
|
||||
)
|
||||
: null,
|
||||
title: Text(widget.totp.issuer),
|
||||
subtitle: Text(
|
||||
password,
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
trailing: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
value: validity / widget.totp.interval,
|
||||
),
|
||||
Text("$validity"),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
updater.cancel();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,206 @@
|
||||
part of 'package:maxkey_flutter/pages/home_page/page.dart';
|
||||
|
||||
enum _TimeOfDay {
|
||||
morning,
|
||||
noon,
|
||||
afternoon,
|
||||
evening;
|
||||
|
||||
const _TimeOfDay();
|
||||
|
||||
String greeting(BuildContext context) => switch(this) {
|
||||
_TimeOfDay.morning => AppLocalizations.of(context)!.homePageUserCardGreetingMorning,
|
||||
_TimeOfDay.noon => AppLocalizations.of(context)!.homePageUserCardGreetingNoon,
|
||||
_TimeOfDay.afternoon => AppLocalizations.of(context)!.homePageUserCardGreetingAfternoon,
|
||||
_TimeOfDay.evening => AppLocalizations.of(context)!.homePageUserCardGreetingEvening,
|
||||
};
|
||||
|
||||
static const _table = [
|
||||
evening,
|
||||
evening,
|
||||
evening,
|
||||
evening,
|
||||
evening,
|
||||
morning,
|
||||
morning,
|
||||
morning,
|
||||
morning,
|
||||
morning,
|
||||
morning,
|
||||
noon,
|
||||
noon,
|
||||
afternoon,
|
||||
afternoon,
|
||||
afternoon,
|
||||
afternoon,
|
||||
afternoon,
|
||||
afternoon,
|
||||
evening,
|
||||
evening,
|
||||
evening,
|
||||
evening,
|
||||
evening,
|
||||
];
|
||||
|
||||
factory _TimeOfDay.fromDateTime(DateTime dateTime) {
|
||||
return _table[dateTime.hour];
|
||||
}
|
||||
}
|
||||
|
||||
class _UserCard extends StatelessWidget {
|
||||
const _UserCard({super.key, required this.user});
|
||||
|
||||
final MaxKeyUser user;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
|
||||
return InkWell(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
onTap: () {
|
||||
context.push(RoutePath.userPage, extra: user);
|
||||
},
|
||||
child: Ink(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0),
|
||||
decoration: BoxDecoration(
|
||||
color: scheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
ClipOval(
|
||||
child: user.picture == null
|
||||
? Image.asset(
|
||||
"assets/logo.jpg",
|
||||
width: 64,
|
||||
height: 64,
|
||||
)
|
||||
: Image.memory(
|
||||
user.picture!,
|
||||
width: 64,
|
||||
height: 64,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(_TimeOfDay.fromDateTime(DateTime.now()).greeting(context)),
|
||||
Text(
|
||||
user.displayName,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.qr_code_scanner),
|
||||
onPressed: () async {
|
||||
final qrCodeValue = await context.push<String?>(
|
||||
RoutePath.scanPage,
|
||||
extra: AppLocalizations.of(context)!.homePageUserCardScanPage,
|
||||
);
|
||||
|
||||
if (qrCodeValue == null) return;
|
||||
|
||||
await MaxKey.instance.authnService.scanCode(
|
||||
expectedErrorHandler: (msg) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(msg)),
|
||||
);
|
||||
},
|
||||
code: qrCodeValue,
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const SizedBox(
|
||||
height: 40,
|
||||
width: 40,
|
||||
child: Icon(Icons.keyboard_arrow_right),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 加载时 [_UserCard] 的替代组件,头像、问候语和用户名用透明度渐变的圆角矩形代替。
|
||||
class _LoadingUserCard extends StatelessWidget {
|
||||
const _LoadingUserCard({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
|
||||
return InkWell(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
onTap: () {
|
||||
context.push(RoutePath.userPage);
|
||||
},
|
||||
child: Ink(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0),
|
||||
decoration: BoxDecoration(
|
||||
color: scheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
child: RepeatTweenAnimationBuilder(
|
||||
tween: Tween(begin: 0.3, end: 0.7),
|
||||
duration: const Duration(seconds: 3),
|
||||
builder: (context, value, _) => Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 64,
|
||||
height: 64,
|
||||
decoration: BoxDecoration(
|
||||
color: scheme.outline.withOpacity(value),
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
height: 14,
|
||||
width: 56,
|
||||
decoration: BoxDecoration(
|
||||
color: scheme.outline.withOpacity(value),
|
||||
borderRadius: BorderRadius.circular(4.0),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4.0),
|
||||
Container(
|
||||
height: 18,
|
||||
width: 72,
|
||||
decoration: BoxDecoration(
|
||||
color: scheme.outline.withOpacity(value),
|
||||
borderRadius: BorderRadius.circular(4.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const SizedBox(
|
||||
height: 40,
|
||||
width: 40,
|
||||
child: Icon(Icons.keyboard_arrow_right),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:maxkey_flutter/maxkey/maxkey.dart';
|
||||
import 'package:maxkey_flutter/maxkey/services/users.service.dart';
|
||||
import 'package:maxkey_flutter/persistent.dart';
|
||||
import 'package:maxkey_flutter/repeat_tween_animation_builder.dart';
|
||||
import 'package:maxkey_flutter/totp.dart';
|
||||
import 'package:maxkey_flutter/utils.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
part 'package:maxkey_flutter/pages/home_page/components/new_totp_btn.dart';
|
||||
part 'package:maxkey_flutter/pages/home_page/components/totp_list_view.dart';
|
||||
part 'package:maxkey_flutter/pages/home_page/components/user_card.dart';
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
const HomePage({super.key});
|
||||
|
||||
@override
|
||||
State<HomePage> createState() => _HomePageState();
|
||||
}
|
||||
|
||||
class _HomePageState extends State<HomePage> {
|
||||
var profile = MaxKey.instance.usersService.getBasicUserInfo();
|
||||
final controller = _TotpListViewController();
|
||||
|
||||
void _update() {
|
||||
setState(() {
|
||||
profile = MaxKey.instance.usersService.getBasicUserInfo();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
MaxKey.instance.addListener(_update);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
MaxKey.instance.removeListener(_update);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
FutureBuilder(
|
||||
future: profile,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.data == null) {
|
||||
return const _LoadingUserCard();
|
||||
}
|
||||
|
||||
return _UserCard(user: snapshot.data!);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_NewTotpBtn(controller: controller),
|
||||
const SizedBox(height: 8),
|
||||
_TotpListView(controller: controller),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
part of 'package:maxkey_flutter/pages/login_page/page.dart';
|
||||
|
||||
class _CaptchaViewController extends ChangeNotifier {
|
||||
final String loginState;
|
||||
late Future<Uint8List?> captcha;
|
||||
|
||||
_CaptchaViewController(this.loginState) {
|
||||
captcha = MaxKey.instance.imageCaptchaService.captcha(state: loginState);
|
||||
}
|
||||
|
||||
void getNewCaptcha() {
|
||||
captcha = MaxKey.instance.imageCaptchaService.captcha(state: loginState);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
class _CaptchaView extends StatelessWidget {
|
||||
const _CaptchaView({super.key, required this.controller});
|
||||
|
||||
final _CaptchaViewController controller;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: controller.getNewCaptcha,
|
||||
child: ListenableBuilder(
|
||||
listenable: controller,
|
||||
builder: (context, _) {
|
||||
return FutureBuilder(
|
||||
future: controller.captcha,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.data == null) {
|
||||
return const Center(
|
||||
child: SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Image.memory(snapshot.data!, width: 80, height: 25);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,95 @@
|
||||
part of 'package:maxkey_flutter/pages/login_page/page.dart';
|
||||
|
||||
/// login by username and password
|
||||
class _NormalLoginView extends StatefulWidget {
|
||||
const _NormalLoginView({
|
||||
super.key,
|
||||
required this.loginState,
|
||||
});
|
||||
|
||||
final String loginState;
|
||||
|
||||
@override
|
||||
State<_NormalLoginView> createState() => _NormalLoginViewState();
|
||||
}
|
||||
|
||||
class _NormalLoginViewState extends State<_NormalLoginView> {
|
||||
final usernameEditingController = TextEditingController();
|
||||
final passwordEditingController = TextEditingController();
|
||||
final captchaEditingController = TextEditingController();
|
||||
late final captchaViewController = _CaptchaViewController(widget.loginState);
|
||||
final loginBtnController = WidgetStatesController();
|
||||
ValueNotifier<bool> obscureText = ValueNotifier(true);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
TextField(
|
||||
controller: usernameEditingController,
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context)!.loginPageLoginViewUsername,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
ListenableBuilder(
|
||||
listenable: obscureText,
|
||||
builder: (context, _) {
|
||||
return TextField(
|
||||
controller: passwordEditingController,
|
||||
obscureText: obscureText.value,
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context)!.loginPageLoginViewPassword,
|
||||
suffix: IconButton(
|
||||
onPressed: () {
|
||||
obscureText.value = !obscureText.value;
|
||||
},
|
||||
icon: Icon(
|
||||
obscureText.value ? Icons.visibility_off : Icons.visibility,
|
||||
),
|
||||
),
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
TextField(
|
||||
controller: captchaEditingController,
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context)!.loginPageLoginViewCaptcha,
|
||||
suffix: _CaptchaView(controller: captchaViewController),
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
FilledButton(
|
||||
statesController: loginBtnController,
|
||||
onPressed: () async {
|
||||
loginBtnController.update(WidgetState.disabled, true);
|
||||
final hasSucceed = await MaxKey.instance.authnService.loginNormal(
|
||||
expectedErrorHandler: (msg) {
|
||||
loginBtnController.update(WidgetState.disabled, false);
|
||||
captchaViewController.getNewCaptcha();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(msg)),
|
||||
);
|
||||
},
|
||||
state: widget.loginState,
|
||||
username: usernameEditingController.text,
|
||||
password: passwordEditingController.text,
|
||||
captcha: captchaEditingController.text,
|
||||
);
|
||||
|
||||
if (hasSucceed && context.mounted) {
|
||||
context.pushReplacement(RoutePath.homePage);
|
||||
}
|
||||
},
|
||||
child: Text(AppLocalizations.of(context)!.loginPageLoginViewLoginBtn),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:maxkey_flutter/maxkey/maxkey.dart';
|
||||
import 'package:maxkey_flutter/utils.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
part 'package:maxkey_flutter/pages/login_page/components/captcha_view.dart';
|
||||
part 'package:maxkey_flutter/pages/login_page/components/login_view.dart';
|
||||
|
||||
class LoginPage extends StatefulWidget {
|
||||
const LoginPage({super.key});
|
||||
|
||||
@override
|
||||
State<LoginPage> createState() => _LoginPageState();
|
||||
}
|
||||
|
||||
class _LoginPageState extends State<LoginPage> {
|
||||
var loginGetFuture = MaxKey.instance.authnService.get();
|
||||
|
||||
void _update() {
|
||||
setState(() {
|
||||
loginGetFuture = MaxKey.instance.authnService.get();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
MaxKey.instance.addListener(_update);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
MaxKey.instance.removeListener(_update);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Image.asset("assets/logo_maxkey.png"),
|
||||
const SizedBox(height: 32.0),
|
||||
FutureBuilder(
|
||||
future: loginGetFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.data == null) {
|
||||
return const SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
return _NormalLoginView(loginState: snapshot.data!);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
context.push(RoutePath.settingsPage);
|
||||
},
|
||||
child: Text(AppLocalizations.of(context)!.loginPageSettingBtn),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
145
summer-ospp/2024/Flutter/maxkey_flutter/lib/pages/scan_page.dart
Normal file
@ -0,0 +1,145 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
class ScanPage extends StatelessWidget {
|
||||
const ScanPage({super.key, required this.title});
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(title)),
|
||||
body: ScanView(
|
||||
resultHandler: (capture, resumeScanner) {
|
||||
context.pop(capture.barcodes.first.rawValue);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
typedef ScanViewResultHandler = void Function(
|
||||
BarcodeCapture capture,
|
||||
void Function() resumeScanner,
|
||||
);
|
||||
|
||||
class ScanView extends StatefulWidget {
|
||||
const ScanView({super.key, required this.resultHandler});
|
||||
|
||||
/// handle the QR code result.
|
||||
///
|
||||
/// The scanner will stop each time the result is complete.
|
||||
/// Process the result here and then resume the scanner by calling `resumeScanner`.
|
||||
final ScanViewResultHandler resultHandler;
|
||||
|
||||
@override
|
||||
State<ScanView> createState() => _ScanViewState();
|
||||
}
|
||||
|
||||
class _ScanViewState extends State<ScanView> with WidgetsBindingObserver {
|
||||
final scannerController = MobileScannerController(
|
||||
formats: [BarcodeFormat.qrCode],
|
||||
);
|
||||
|
||||
StreamSubscription? subscription;
|
||||
|
||||
bool hasResult = false;
|
||||
|
||||
void resumeScanner() {
|
||||
hasResult = false;
|
||||
scannerController.start();
|
||||
}
|
||||
|
||||
void handleQRcode(BarcodeCapture capture) {
|
||||
if (hasResult) return;
|
||||
hasResult = true;
|
||||
|
||||
scannerController.stop().then((_) {
|
||||
widget.resultHandler(capture, resumeScanner);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Start listening to lifecycle changes.
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
|
||||
// Start listening to the barcode events.
|
||||
subscription = scannerController.barcodes.listen(handleQRcode);
|
||||
|
||||
// Finally, start the scanner itself.
|
||||
scannerController.start();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
// If the controller is not ready, do not try to start or stop it.
|
||||
// Permission dialogs can trigger lifecycle changes before the controller is ready.
|
||||
if (!scannerController.value.isInitialized) return;
|
||||
|
||||
switch (state) {
|
||||
case AppLifecycleState.detached:
|
||||
case AppLifecycleState.hidden:
|
||||
case AppLifecycleState.paused:
|
||||
return;
|
||||
case AppLifecycleState.resumed:
|
||||
// Restart the scanner when the app is resumed.
|
||||
// Don't forget to resume listening to the barcode events.
|
||||
subscription = scannerController.barcodes.listen(handleQRcode);
|
||||
scannerController.start();
|
||||
break;
|
||||
case AppLifecycleState.inactive:
|
||||
// Stop the scanner when the app is paused.
|
||||
// Also stop the barcode events subscription.
|
||||
subscription?.cancel();
|
||||
subscription = null;
|
||||
scannerController.stop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MobileScanner(
|
||||
controller: scannerController,
|
||||
errorBuilder: (context, error, _) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
return ColoredBox(
|
||||
color: scheme.surface,
|
||||
child: Center(
|
||||
child: switch (error.errorCode) {
|
||||
MobileScannerErrorCode.controllerAlreadyInitialized ||
|
||||
MobileScannerErrorCode.controllerDisposed ||
|
||||
MobileScannerErrorCode.controllerUninitialized ||
|
||||
MobileScannerErrorCode.genericError ||
|
||||
MobileScannerErrorCode.unsupported =>
|
||||
Icon(Icons.error, color: scheme.error),
|
||||
MobileScannerErrorCode.permissionDenied => Text(
|
||||
AppLocalizations.of(context)!.scanPagePermissionDeniedMsg,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() async {
|
||||
// Stop listening to lifecycle changes.
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
// Stop listening to the barcode events.
|
||||
subscription?.cancel();
|
||||
subscription = null;
|
||||
super.dispose();
|
||||
// Finally, dispose the controller.
|
||||
scannerController.dispose();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,98 @@
|
||||
part of 'package:maxkey_flutter/pages/settings_page/page.dart';
|
||||
|
||||
class _SetHostDialog extends StatefulWidget {
|
||||
const _SetHostDialog({super.key});
|
||||
|
||||
@override
|
||||
State<_SetHostDialog> createState() => _SetHostDialogState();
|
||||
}
|
||||
|
||||
class _SetHostDialogState extends State<_SetHostDialog> {
|
||||
final hostEditingController =
|
||||
TextEditingController(text: MaxKeyPersistent.instance.host);
|
||||
|
||||
/// true: 连接成功;false:链接失败
|
||||
bool testResult = true;
|
||||
String? testDesc;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SimpleDialog(
|
||||
title: Text(AppLocalizations.of(context)!.settingsPageHostSettingDialog),
|
||||
contentPadding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 16.0),
|
||||
children: [
|
||||
TextField(
|
||||
controller: hostEditingController,
|
||||
decoration: InputDecoration(
|
||||
labelText:
|
||||
AppLocalizations.of(context)!.settingsPageHostSettingDialogHost,
|
||||
border: const OutlineInputBorder(),
|
||||
helperText: testResult ? testDesc : null,
|
||||
errorText: testResult ? null : testDesc,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
final result = await MaxKey.instance
|
||||
.maxKeyNetworkTest(host: hostEditingController.text);
|
||||
setState(() {
|
||||
testResult = result;
|
||||
testDesc = result
|
||||
? AppLocalizations.of(context)!
|
||||
.settingsPageHostSettingDialogTestSucceed
|
||||
: AppLocalizations.of(context)!
|
||||
.settingsPageHostSettingDialogTestFail;
|
||||
});
|
||||
},
|
||||
child: Text(AppLocalizations.of(context)!.settingsPageHostSettingDialogTestBtn),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: Navigator.of(context).pop,
|
||||
child: Text(AppLocalizations.of(context)!.settingsPageHostSettingDialogCancleBtn),
|
||||
),
|
||||
const SizedBox(width: 8.0),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await MaxKeyPersistent.instance
|
||||
.setHost(hostEditingController.text);
|
||||
MaxKey.instance.updateBaseUrl();
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
child: Text(AppLocalizations.of(context)!.settingsPageHostSettingDialogConfirmBtn),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SetHostButton extends StatelessWidget {
|
||||
const _SetHostButton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _SettingTile(
|
||||
title: AppLocalizations.of(context)!.settingsPageHostSettingTitle,
|
||||
desc: AppLocalizations.of(context)!.settingsPageHostSettingDesc,
|
||||
action: const Icon(Icons.arrow_outward),
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => const _SetHostDialog(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
part of 'package:maxkey_flutter/pages/settings_page/page.dart';
|
||||
|
||||
class _ShowLogBtn extends StatelessWidget {
|
||||
const _ShowLogBtn({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _SettingTile(
|
||||
title: AppLocalizations.of(context)!.settingsPageShowLogBtnTitle,
|
||||
action: const Icon(Icons.arrow_outward),
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
CupertinoPageRoute(builder: (context) => const _LogDisplayPage()),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LogDisplayPage extends StatefulWidget {
|
||||
const _LogDisplayPage({super.key});
|
||||
|
||||
@override
|
||||
State<_LogDisplayPage> createState() => __LogDisplayPageState();
|
||||
}
|
||||
|
||||
class __LogDisplayPageState extends State<_LogDisplayPage> {
|
||||
final logEditingController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final strbuf = StringBuffer();
|
||||
for (var item in LOGGER_MEMORY.buffer) {
|
||||
strbuf.writeAll(item.lines);
|
||||
strbuf.writeln();
|
||||
}
|
||||
logEditingController.text = strbuf.toString();
|
||||
strbuf.clear();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(AppLocalizations.of(context)!.settingsPageLogDisplayPageTitle)),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: TextField(
|
||||
controller: logEditingController,
|
||||
maxLines: null,
|
||||
decoration: const InputDecoration(border: InputBorder.none),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
part of 'package:maxkey_flutter/pages/settings_page/page.dart';
|
||||
|
||||
class _ThemeModeSwitch extends StatefulWidget {
|
||||
const _ThemeModeSwitch({super.key});
|
||||
|
||||
@override
|
||||
State<_ThemeModeSwitch> createState() => __ThemeModeSwitchState();
|
||||
}
|
||||
|
||||
class __ThemeModeSwitchState extends State<_ThemeModeSwitch> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _SettingTile(
|
||||
title: AppLocalizations.of(context)!.settingsPageThemeModeSwitchTitle,
|
||||
action: SegmentedButton(
|
||||
showSelectedIcon: false,
|
||||
segments: ThemeMode.values
|
||||
.map(
|
||||
(item) => ButtonSegment(
|
||||
value: item,
|
||||
icon: switch (item) {
|
||||
ThemeMode.system => const Icon(Icons.brightness_auto),
|
||||
ThemeMode.light => const Icon(Icons.light_mode),
|
||||
ThemeMode.dark => const Icon(Icons.dark_mode),
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
selected: {MaxKeyPersistent.instance.themeMode},
|
||||
onSelectionChanged: (selected) {
|
||||
setState(() {
|
||||
MaxKeyPersistent.instance.setThemeMode(selected.first);
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,66 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:maxkey_flutter/maxkey/maxkey.dart';
|
||||
import 'package:maxkey_flutter/persistent.dart';
|
||||
import 'package:maxkey_flutter/utils.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
part 'package:maxkey_flutter/pages/settings_page/components/theme_mode_setting.dart';
|
||||
part 'package:maxkey_flutter/pages/settings_page/components/host_setting.dart';
|
||||
part 'package:maxkey_flutter/pages/settings_page/components/show_log_btn.dart';
|
||||
|
||||
class SettingsPage extends StatelessWidget {
|
||||
const SettingsPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(AppLocalizations.of(context)!.settingsPageTitle)),
|
||||
body: const SafeArea(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
_ThemeModeSwitch(),
|
||||
SizedBox(height: 8.0),
|
||||
_SetHostButton(),
|
||||
SizedBox(height: 8.0),
|
||||
_ShowLogBtn(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SettingTile extends StatelessWidget {
|
||||
const _SettingTile({
|
||||
super.key,
|
||||
required this.title,
|
||||
this.desc,
|
||||
required this.action,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String? desc;
|
||||
final Widget action;
|
||||
final void Function()? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
return ListTile(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
tileColor: scheme.surfaceContainer,
|
||||
title: Text(title, maxLines: 1),
|
||||
subtitle: desc == null ? null : Text(desc!, maxLines: 2),
|
||||
isThreeLine: desc == null ? false : true,
|
||||
trailing: action,
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
part of 'package:maxkey_flutter/pages/user_page/page.dart';
|
||||
|
||||
class _FullUserInfoDialog extends StatelessWidget {
|
||||
const _FullUserInfoDialog({super.key, required this.userInfo});
|
||||
|
||||
final MaxKeyUserInfo userInfo;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SimpleDialog(
|
||||
title: Text(AppLocalizations.of(context)!.userPageFullUserInfoDialogTitle),
|
||||
children: [
|
||||
_UserInfoTile(infoDes: AppLocalizations.of(context)!.userPageFullUserInfoDialogDisplayName, infoValue: userInfo.displayName),
|
||||
_UserInfoTile(infoDes: AppLocalizations.of(context)!.userPageFullUserInfoDialogUsername, infoValue: userInfo.username),
|
||||
_UserInfoTile(infoDes: AppLocalizations.of(context)!.userPageFullUserInfoDialogGender, infoValue: userInfo.gender),
|
||||
_UserInfoTile(infoDes: AppLocalizations.of(context)!.userPageFullUserInfoDialogEmployeeNumber, infoValue: userInfo.employeeNumber),
|
||||
_UserInfoTile(infoDes: AppLocalizations.of(context)!.userPageFullUserInfoDialogMobile, infoValue: userInfo.mobile),
|
||||
_UserInfoTile(infoDes: AppLocalizations.of(context)!.userPageFullUserInfoDialogEmail, infoValue: userInfo.email),
|
||||
_UserInfoTile(infoDes: AppLocalizations.of(context)!.userPageFullUserInfoDialogUserType, infoValue: userInfo.userType),
|
||||
_UserInfoTile(infoDes: AppLocalizations.of(context)!.userPageFullUserInfoDialogUserState, infoValue: userInfo.userState),
|
||||
_UserInfoTile(infoDes: AppLocalizations.of(context)!.userPageFullUserInfoDialogIdType, infoValue: userInfo.idType),
|
||||
_UserInfoTile(infoDes: AppLocalizations.of(context)!.userPageFullUserInfoDialogIdCardNo, infoValue: userInfo.idCardNo),
|
||||
_UserInfoTile(infoDes: AppLocalizations.of(context)!.userPageFullUserInfoDialogMarried, infoValue: userInfo.married),
|
||||
_UserInfoTile(infoDes: AppLocalizations.of(context)!.userPageFullUserInfoDialogBirth, infoValue: userInfo.birthDate),
|
||||
_UserInfoTile(infoDes: AppLocalizations.of(context)!.userPageFullUserInfoDialogOrganization, infoValue: userInfo.organization),
|
||||
_UserInfoTile(infoDes: AppLocalizations.of(context)!.userPageFullUserInfoDialogDivision, infoValue: userInfo.division),
|
||||
_UserInfoTile(infoDes: AppLocalizations.of(context)!.userPageFullUserInfoDialogDepartmentId, infoValue: userInfo.departmentId),
|
||||
_UserInfoTile(infoDes: AppLocalizations.of(context)!.userPageFullUserInfoDialogDepartment, infoValue: userInfo.department),
|
||||
_UserInfoTile(infoDes: AppLocalizations.of(context)!.userPageFullUserInfoDialogJobTitle, infoValue: userInfo.jobTitle),
|
||||
_UserInfoTile(infoDes: AppLocalizations.of(context)!.userPageFullUserInfoDialogJobLevel, infoValue: userInfo.jobLevel),
|
||||
_UserInfoTile(infoDes: AppLocalizations.of(context)!.userPageFullUserInfoDialogManager, infoValue: userInfo.manager),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _UserInfoTile extends StatelessWidget {
|
||||
const _UserInfoTile({
|
||||
super.key,
|
||||
required this.infoDes,
|
||||
required this.infoValue,
|
||||
});
|
||||
|
||||
final String infoDes;
|
||||
final String infoValue;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(title: Text(infoDes), subtitle: Text(infoValue));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,136 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:maxkey_flutter/maxkey/maxkey.dart';
|
||||
import 'package:maxkey_flutter/maxkey/services/users.service.dart';
|
||||
import 'package:maxkey_flutter/utils.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
part 'package:maxkey_flutter/pages/user_page/full_user_info_dialog.dart';
|
||||
|
||||
class UserPage extends StatelessWidget {
|
||||
const UserPage({super.key, this.user});
|
||||
|
||||
final MaxKeyUser? user;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(AppLocalizations.of(context)!.userPageTitle)),
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Column(children: [
|
||||
if (user != null)
|
||||
Ink(
|
||||
decoration: BoxDecoration(
|
||||
color: scheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(children: [
|
||||
_UserCard(user: user!),
|
||||
_UserPageButtonTile(
|
||||
title: AppLocalizations.of(context)!.userPageUserInfoBtn,
|
||||
trailing: Icons.info,
|
||||
onTap: () async {
|
||||
final userInfo = await MaxKey.instance.usersService
|
||||
.getFullUserInfo();
|
||||
if (userInfo == null) return;
|
||||
|
||||
if (context.mounted) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
_FullUserInfoDialog(userInfo: userInfo),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
]),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
_UserPageButtonTile(
|
||||
title: AppLocalizations.of(context)!.userPageSettingsBtn,
|
||||
trailing: Icons.settings,
|
||||
onTap: () {
|
||||
context.push(RoutePath.settingsPage);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
_UserPageButtonTile(
|
||||
title: AppLocalizations.of(context)!.userPageLogoutBtn,
|
||||
trailing: Icons.logout,
|
||||
onTap: () async {
|
||||
await MaxKey.instance.authnService.logout();
|
||||
|
||||
if (context.mounted) {
|
||||
context.pushReplacement(RoutePath.loginPage);
|
||||
}
|
||||
},
|
||||
)
|
||||
]),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _UserPageButtonTile extends StatelessWidget {
|
||||
const _UserPageButtonTile({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.trailing,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final IconData trailing;
|
||||
final void Function() onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
return ListTile(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
tileColor: scheme.surfaceContainer,
|
||||
title: Text(title),
|
||||
trailing: Icon(trailing),
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _UserCard extends StatelessWidget {
|
||||
const _UserCard({super.key, required this.user});
|
||||
|
||||
final MaxKeyUser user;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
ClipOval(
|
||||
child: user.picture == null
|
||||
? Image.asset("assets/logo.jpg", width: 64, height: 64)
|
||||
: Image.memory(user.picture!, width: 64, height: 64),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
user.displayName,
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
87
summer-ospp/2024/Flutter/maxkey_flutter/lib/persistent.dart
Normal file
@ -0,0 +1,87 @@
|
||||
// ignore_for_file: constant_identifier_names, non_constant_identifier_names
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:maxkey_flutter/totp.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class MaxKeyPersistent {
|
||||
static const String _CURR_USER_KEY = "CurrUser";
|
||||
|
||||
static const String _THEME_MODE_KEY = "ThemeMode";
|
||||
|
||||
/// 和用户绑定
|
||||
String get _TOKEN_KEY => "$_currUser.Token";
|
||||
|
||||
/// 不和用户绑定
|
||||
static const String _HOST_KEY = "Host";
|
||||
static const String _DEFAULT_HOST = "192.168.1.66:9527";
|
||||
|
||||
/// 和用户绑定
|
||||
String get _TOTP_LIST_KEY => "$_currUser.TotpList";
|
||||
|
||||
/// must call [init] first
|
||||
static final MaxKeyPersistent instance = MaxKeyPersistent();
|
||||
|
||||
late SharedPreferences _sp;
|
||||
|
||||
static Future<void> init() async {
|
||||
instance._sp = await SharedPreferences.getInstance();
|
||||
instance._readTotps();
|
||||
}
|
||||
|
||||
ThemeMode get themeMode => switch (_sp.getString(_THEME_MODE_KEY)) {
|
||||
"light" => ThemeMode.light,
|
||||
"dark" => ThemeMode.dark,
|
||||
_ => ThemeMode.system,
|
||||
};
|
||||
|
||||
late ValueNotifier<ThemeMode> themeModeListenable = ValueNotifier(themeMode);
|
||||
|
||||
String? get _currUser => _sp.getString(_CURR_USER_KEY);
|
||||
|
||||
String? get token => _sp.getString(_TOKEN_KEY);
|
||||
|
||||
/// example: 192.168.220.26:9527
|
||||
String get host => _sp.getString(_HOST_KEY) ?? _DEFAULT_HOST;
|
||||
String get baseUrl => "http://$host/sign";
|
||||
|
||||
final List<Totp> _totps = [];
|
||||
void _readTotps() {
|
||||
final totpUris = _sp.getStringList(_TOTP_LIST_KEY);
|
||||
if (totpUris == null) return;
|
||||
|
||||
for (final uri in totpUris) {
|
||||
final parsed = Totp.fromUri(uri);
|
||||
if (parsed == null) continue;
|
||||
_totps.add(parsed);
|
||||
}
|
||||
}
|
||||
|
||||
List<Totp> get totps => _totps;
|
||||
|
||||
Future<bool> setThemeMode(ThemeMode themeMode) {
|
||||
themeModeListenable.value = themeMode;
|
||||
return _sp.setString(_THEME_MODE_KEY, themeMode.name);
|
||||
}
|
||||
|
||||
Future<bool> setUser(String username) async {
|
||||
final result = await _sp.setString(_CURR_USER_KEY, username);
|
||||
_totps.clear();
|
||||
if (result) {
|
||||
instance._readTotps();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Future<bool> setToken(String token) => _sp.setString(_TOKEN_KEY, token);
|
||||
|
||||
/// example: 192.168.220.26:9527
|
||||
Future<bool> setHost(String host) => _sp.setString(_HOST_KEY, host);
|
||||
|
||||
Future<bool> saveTotps(List<Totp> totps) => _sp.setStringList(
|
||||
_TOTP_LIST_KEY,
|
||||
List.generate(totps.length, (i) => totps[i].uri),
|
||||
);
|
||||
|
||||
Future<bool> clearToken() => _sp.remove(_TOKEN_KEY);
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// 由 [TweenAnimationBuilder] 改造的组件,区别在于这个会重复动画。
|
||||
class RepeatTweenAnimationBuilder<T extends Object?> extends ImplicitlyAnimatedWidget {
|
||||
const RepeatTweenAnimationBuilder({
|
||||
super.key,
|
||||
required this.tween,
|
||||
required super.duration,
|
||||
super.curve,
|
||||
required this.builder,
|
||||
super.onEnd,
|
||||
this.child,
|
||||
});
|
||||
|
||||
final Tween<T> tween;
|
||||
final ValueWidgetBuilder<T> builder;
|
||||
final Widget? child;
|
||||
|
||||
@override
|
||||
ImplicitlyAnimatedWidgetState<ImplicitlyAnimatedWidget> createState() {
|
||||
return _TweenAnimationBuilderState<T>();
|
||||
}
|
||||
}
|
||||
|
||||
class _TweenAnimationBuilderState<T extends Object?> extends AnimatedWidgetBaseState<RepeatTweenAnimationBuilder<T>> {
|
||||
Tween<T>? _currentTween;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_currentTween = widget.tween;
|
||||
_currentTween!.begin ??= _currentTween!.end;
|
||||
super.initState();
|
||||
if (_currentTween!.begin != _currentTween!.end) {
|
||||
// 使用 repeat 来重复绘制动画。
|
||||
controller.repeat(reverse: true);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void forEachTween(TweenVisitor<dynamic> visitor) {
|
||||
assert(
|
||||
widget.tween.end != null,
|
||||
'Tween provided to TweenAnimationBuilder must have non-null Tween.end value.',
|
||||
);
|
||||
_currentTween = visitor(_currentTween, widget.tween.end, (dynamic value) {
|
||||
assert(false);
|
||||
throw StateError('Constructor will never be called because null is never provided as current tween.');
|
||||
}) as Tween<T>?;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.builder(context, _currentTween!.evaluate(animation), widget.child);
|
||||
}
|
||||
}
|
||||
55
summer-ospp/2024/Flutter/maxkey_flutter/lib/totp.dart
Normal file
@ -0,0 +1,55 @@
|
||||
import 'package:auth_totp/auth_totp.dart';
|
||||
|
||||
/// otpauth://totp/{}?secret={}&issuer={}
|
||||
class Totp {
|
||||
/// for persistent
|
||||
final String uri;
|
||||
final String issuer;
|
||||
|
||||
final String secret;
|
||||
final int interval;
|
||||
|
||||
Totp({
|
||||
required this.uri,
|
||||
required this.issuer,
|
||||
required this.secret,
|
||||
required this.interval,
|
||||
});
|
||||
|
||||
String get now => AuthTOTP.generateTOTPCode(
|
||||
secretKey: secret,
|
||||
interval: interval,
|
||||
);
|
||||
|
||||
static Totp? fromUri(String uri) {
|
||||
final totpUri = Uri.tryParse(uri);
|
||||
if (totpUri == null) return null;
|
||||
|
||||
final query = totpUri.queryParameters;
|
||||
final secret = query["secret"];
|
||||
if (secret == null) return null;
|
||||
|
||||
// final digit = query["digits"];
|
||||
final period = query["period"];
|
||||
final issuerQuery = query["issuer"];
|
||||
|
||||
final lastPathSeg = totpUri.pathSegments.lastOrNull;
|
||||
final colonPos = lastPathSeg?.indexOf(":");
|
||||
final account = colonPos == null
|
||||
? null
|
||||
: colonPos == -1
|
||||
? lastPathSeg!
|
||||
: lastPathSeg!.substring(colonPos + 1);
|
||||
|
||||
final issuer = issuerQuery == null
|
||||
? lastPathSeg ?? "UNKNOWN"
|
||||
: "$issuerQuery${account == null ? "" : ":$account"}";
|
||||
|
||||
return Totp(
|
||||
uri: uri,
|
||||
issuer: issuer,
|
||||
secret: secret,
|
||||
interval: period == null ? 30 : int.tryParse(period) ?? 30,
|
||||
);
|
||||
}
|
||||
}
|
||||
34
summer-ospp/2024/Flutter/maxkey_flutter/lib/utils.dart
Normal file
@ -0,0 +1,34 @@
|
||||
// ignore_for_file: constant_identifier_names, non_constant_identifier_names
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
|
||||
final LOGGER_MEMORY = MemoryOutput();
|
||||
final LOGGER = Logger(
|
||||
filter: ProductionFilter(),
|
||||
printer: SimplePrinter(),
|
||||
output: kDebugMode
|
||||
? MultiOutput([LOGGER_MEMORY, ConsoleOutput()])
|
||||
: LOGGER_MEMORY,
|
||||
level: Level.all,
|
||||
);
|
||||
|
||||
final SCAFFOLD_MESSENGER_KEY = GlobalKey<ScaffoldMessengerState>();
|
||||
final NAVIGATOR_KEY = GlobalKey<NavigatorState>();
|
||||
|
||||
class RoutePath {
|
||||
static const loginPage = "/login";
|
||||
static const homePage = "/home";
|
||||
static const scanPage = "/scan";
|
||||
static const userPage = "/user";
|
||||
static const settingsPage = "/settings";
|
||||
}
|
||||
|
||||
extension Base64 on String {
|
||||
Uint8List base64ToBuf() {
|
||||
return base64.decode(this);
|
||||
}
|
||||
}
|
||||
431
summer-ospp/2024/Flutter/maxkey_flutter/pubspec.lock
Normal file
@ -0,0 +1,431 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.11.0"
|
||||
auth_totp:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: auth_totp
|
||||
sha256: "51bb03ed052baf09661e5c65b3ed4eaf8bcd1ed70696b96935fd560792ad9aee"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.5"
|
||||
dio:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dio
|
||||
sha256: "0dfb6b6a1979dac1c1245e17cef824d7b452ea29bd33d3467269f9bef3715fb0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.6.0"
|
||||
dio_web_adapter:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dio_web_adapter
|
||||
sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
flutter_localizations:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
go_router:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: go_router
|
||||
sha256: "2ddb88e9ad56ae15ee144ed10e33886777eb5ca2509a914850a5faa7b52ff459"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.2.7"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: intl
|
||||
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.19.0"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.5"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.5"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
logger:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: logger
|
||||
sha256: "697d067c60c20999686a0add96cf6aba723b3aa1f83ecf806a8097231529ec32"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: logging
|
||||
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.16+1"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.15.0"
|
||||
mobile_scanner:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: mobile_scanner
|
||||
sha256: "6ac2913ad98c83f558d2c8a55bc8f511bdcf28b86639701c04b04c16da1e9ee1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.2.1"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.0"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_linux
|
||||
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.5"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.2"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shared_preferences_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.99"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.2"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.2"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.2.5"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
sdks:
|
||||
dart: ">=3.5.1 <4.0.0"
|
||||
flutter: ">=3.22.0"
|
||||
33
summer-ospp/2024/Flutter/maxkey_flutter/pubspec.yaml
Normal file
@ -0,0 +1,33 @@
|
||||
name: maxkey_flutter
|
||||
description: "MaxKey Flutter project."
|
||||
publish_to: 'none'
|
||||
version: 1.0.0
|
||||
|
||||
environment:
|
||||
sdk: ^3.5.1
|
||||
|
||||
dependencies:
|
||||
dio: ^5.4.3+1
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
go_router: ^14.2.0
|
||||
mobile_scanner: ^5.1.1
|
||||
# permission_handler: ^11.3.1
|
||||
shared_preferences: ^2.2.3
|
||||
auth_totp: ^1.0.1
|
||||
logger: ^2.4.0
|
||||
intl: ^0.19.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^3.0.0
|
||||
|
||||
flutter:
|
||||
generate: true
|
||||
uses-material-design: true
|
||||
assets:
|
||||
- assets/logo.jpg
|
||||
- assets/logo_maxkey.png
|
||||
108
summer-ospp/2024/Flutter/maxkey_flutter/windows/CMakeLists.txt
Normal file
@ -0,0 +1,108 @@
|
||||
# Project-level configuration.
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(maxkey_flutter LANGUAGES CXX)
|
||||
|
||||
# The name of the executable created for the application. Change this to change
|
||||
# the on-disk name of your application.
|
||||
set(BINARY_NAME "maxkey_flutter")
|
||||
|
||||
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
|
||||
# versions of CMake.
|
||||
cmake_policy(VERSION 3.14...3.25)
|
||||
|
||||
# Define build configuration option.
|
||||
get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
|
||||
if(IS_MULTICONFIG)
|
||||
set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
|
||||
CACHE STRING "" FORCE)
|
||||
else()
|
||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
set(CMAKE_BUILD_TYPE "Debug" CACHE
|
||||
STRING "Flutter build mode" FORCE)
|
||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
|
||||
"Debug" "Profile" "Release")
|
||||
endif()
|
||||
endif()
|
||||
# Define settings for the Profile build mode.
|
||||
set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
|
||||
set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
|
||||
set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
|
||||
|
||||
# Use Unicode for all projects.
|
||||
add_definitions(-DUNICODE -D_UNICODE)
|
||||
|
||||
# Compilation settings that should be applied to most targets.
|
||||
#
|
||||
# Be cautious about adding new options here, as plugins use this function by
|
||||
# default. In most cases, you should add new options to specific targets instead
|
||||
# of modifying this function.
|
||||
function(APPLY_STANDARD_SETTINGS TARGET)
|
||||
target_compile_features(${TARGET} PUBLIC cxx_std_17)
|
||||
target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
|
||||
target_compile_options(${TARGET} PRIVATE /EHsc)
|
||||
target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
|
||||
target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
|
||||
endfunction()
|
||||
|
||||
# Flutter library and tool build rules.
|
||||
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
|
||||
add_subdirectory(${FLUTTER_MANAGED_DIR})
|
||||
|
||||
# Application build; see runner/CMakeLists.txt.
|
||||
add_subdirectory("runner")
|
||||
|
||||
|
||||
# Generated plugin build rules, which manage building the plugins and adding
|
||||
# them to the application.
|
||||
include(flutter/generated_plugins.cmake)
|
||||
|
||||
|
||||
# === Installation ===
|
||||
# Support files are copied into place next to the executable, so that it can
|
||||
# run in place. This is done instead of making a separate bundle (as on Linux)
|
||||
# so that building and running from within Visual Studio will work.
|
||||
set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
|
||||
# Make the "install" step default, as it's required to run.
|
||||
set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
|
||||
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
|
||||
endif()
|
||||
|
||||
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
|
||||
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
|
||||
|
||||
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
if(PLUGIN_BUNDLED_LIBRARIES)
|
||||
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
|
||||
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
endif()
|
||||
|
||||
# Copy the native assets provided by the build.dart from all packages.
|
||||
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/")
|
||||
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
|
||||
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
# Fully re-copy the assets directory on each build to avoid having stale files
|
||||
# from a previous install.
|
||||
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
|
||||
install(CODE "
|
||||
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
|
||||
" COMPONENT Runtime)
|
||||
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
|
||||
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
|
||||
|
||||
# Install the AOT library on non-Debug builds only.
|
||||
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
||||
CONFIGURATIONS Profile;Release
|
||||
COMPONENT Runtime)
|
||||
@ -0,0 +1,109 @@
|
||||
# This file controls Flutter-level build steps. It should not be edited.
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
|
||||
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
|
||||
|
||||
# Configuration provided via flutter tool.
|
||||
include(${EPHEMERAL_DIR}/generated_config.cmake)
|
||||
|
||||
# TODO: Move the rest of this into files in ephemeral. See
|
||||
# https://github.com/flutter/flutter/issues/57146.
|
||||
set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
|
||||
|
||||
# Set fallback configurations for older versions of the flutter tool.
|
||||
if (NOT DEFINED FLUTTER_TARGET_PLATFORM)
|
||||
set(FLUTTER_TARGET_PLATFORM "windows-x64")
|
||||
endif()
|
||||
|
||||
# === Flutter Library ===
|
||||
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
|
||||
|
||||
# Published to parent scope for install step.
|
||||
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
|
||||
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
|
||||
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
|
||||
set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
|
||||
|
||||
list(APPEND FLUTTER_LIBRARY_HEADERS
|
||||
"flutter_export.h"
|
||||
"flutter_windows.h"
|
||||
"flutter_messenger.h"
|
||||
"flutter_plugin_registrar.h"
|
||||
"flutter_texture_registrar.h"
|
||||
)
|
||||
list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
|
||||
add_library(flutter INTERFACE)
|
||||
target_include_directories(flutter INTERFACE
|
||||
"${EPHEMERAL_DIR}"
|
||||
)
|
||||
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
|
||||
add_dependencies(flutter flutter_assemble)
|
||||
|
||||
# === Wrapper ===
|
||||
list(APPEND CPP_WRAPPER_SOURCES_CORE
|
||||
"core_implementations.cc"
|
||||
"standard_codec.cc"
|
||||
)
|
||||
list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
|
||||
list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
|
||||
"plugin_registrar.cc"
|
||||
)
|
||||
list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
|
||||
list(APPEND CPP_WRAPPER_SOURCES_APP
|
||||
"flutter_engine.cc"
|
||||
"flutter_view_controller.cc"
|
||||
)
|
||||
list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
|
||||
|
||||
# Wrapper sources needed for a plugin.
|
||||
add_library(flutter_wrapper_plugin STATIC
|
||||
${CPP_WRAPPER_SOURCES_CORE}
|
||||
${CPP_WRAPPER_SOURCES_PLUGIN}
|
||||
)
|
||||
apply_standard_settings(flutter_wrapper_plugin)
|
||||
set_target_properties(flutter_wrapper_plugin PROPERTIES
|
||||
POSITION_INDEPENDENT_CODE ON)
|
||||
set_target_properties(flutter_wrapper_plugin PROPERTIES
|
||||
CXX_VISIBILITY_PRESET hidden)
|
||||
target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
|
||||
target_include_directories(flutter_wrapper_plugin PUBLIC
|
||||
"${WRAPPER_ROOT}/include"
|
||||
)
|
||||
add_dependencies(flutter_wrapper_plugin flutter_assemble)
|
||||
|
||||
# Wrapper sources needed for the runner.
|
||||
add_library(flutter_wrapper_app STATIC
|
||||
${CPP_WRAPPER_SOURCES_CORE}
|
||||
${CPP_WRAPPER_SOURCES_APP}
|
||||
)
|
||||
apply_standard_settings(flutter_wrapper_app)
|
||||
target_link_libraries(flutter_wrapper_app PUBLIC flutter)
|
||||
target_include_directories(flutter_wrapper_app PUBLIC
|
||||
"${WRAPPER_ROOT}/include"
|
||||
)
|
||||
add_dependencies(flutter_wrapper_app flutter_assemble)
|
||||
|
||||
# === Flutter tool backend ===
|
||||
# _phony_ is a non-existent file to force this command to run every time,
|
||||
# since currently there's no way to get a full input/output list from the
|
||||
# flutter tool.
|
||||
set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
|
||||
set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
|
||||
add_custom_command(
|
||||
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
|
||||
${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
|
||||
${CPP_WRAPPER_SOURCES_APP}
|
||||
${PHONY_OUTPUT}
|
||||
COMMAND ${CMAKE_COMMAND} -E env
|
||||
${FLUTTER_TOOL_ENVIRONMENT}
|
||||
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
|
||||
${FLUTTER_TARGET_PLATFORM} $<CONFIG>
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_target(flutter_assemble DEPENDS
|
||||
"${FLUTTER_LIBRARY}"
|
||||
${FLUTTER_LIBRARY_HEADERS}
|
||||
${CPP_WRAPPER_SOURCES_CORE}
|
||||
${CPP_WRAPPER_SOURCES_PLUGIN}
|
||||
${CPP_WRAPPER_SOURCES_APP}
|
||||
)
|
||||
@ -0,0 +1,11 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#ifndef GENERATED_PLUGIN_REGISTRANT_
|
||||
#define GENERATED_PLUGIN_REGISTRANT_
|
||||
|
||||
#include <flutter/plugin_registry.h>
|
||||
|
||||
// Registers Flutter plugins.
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry);
|
||||
|
||||
#endif // GENERATED_PLUGIN_REGISTRANT_
|
||||