把项目移至 Flutter 目录下

This commit is contained in:
Ferry-200 2024-09-01 22:34:05 +08:00
parent 1e9ee87736
commit 13877f0a76
113 changed files with 5650 additions and 0 deletions

View 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)

View 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'

View File

@ -0,0 +1,3 @@
{
"java.compile.nullAnalysis.mode": "disabled"
}

View 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

View File

@ -0,0 +1,4 @@
include: package:flutter_lints/flutter.yaml
analyzer:
errors:
unused_element: ignore

View File

@ -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 = "../.."
}

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,5 @@
package org.dromara.maxkey.maxkey_flutter
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity()

View File

@ -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>

View File

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -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>

View File

@ -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>

View File

@ -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>

View 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
}

View File

@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true

View 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

View File

@ -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"

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

View File

@ -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>

View File

@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@ -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 */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

View File

@ -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>

View File

@ -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>

View File

@ -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)
}
}

View File

@ -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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View File

@ -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.

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"

View File

@ -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.
}
}

View File

@ -0,0 +1,3 @@
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart

View File

@ -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),
);
}
}

View 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."
}

View 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 需要摄像头权限以扫描二维码并获取时间令牌。"
}

View 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'),
// ];

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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,
),
),
),
),
),
);
}
}

View File

@ -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();
}
}

View File

@ -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),
),
],
),
),
),
);
}
}

View File

@ -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),
],
),
),
),
);
}
}

View File

@ -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);
},
);
},
),
);
}
}

View File

@ -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),
),
],
);
}
}

View File

@ -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),
),
],
),
),
),
),
);
}
}

View 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();
}
}

View File

@ -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(),
);
},
);
}
}

View File

@ -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),
),
),
);
}
}

View File

@ -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);
});
},
),
);
}
}

View File

@ -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,
);
}
}

View File

@ -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));
}
}

View File

@ -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),
),
),
],
),
);
}
}

View 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);
}

View File

@ -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);
}
}

View 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,
);
}
}

View 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);
}
}

View 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"

View 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

View 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)

View File

@ -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}
)

View File

@ -0,0 +1,11 @@
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
void RegisterPlugins(flutter::PluginRegistry* registry) {
}

View File

@ -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_

Some files were not shown because too many files have changed in this diff Show More