Compare commits

..

42 Commits

Author SHA1 Message Date
dependabot[bot]
48fde00430 build(deps): bump anyhow from 1.0.100 to 1.0.101 in /rust
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.100 to 1.0.101.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.100...1.0.101)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-version: 1.0.101
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-09 00:35:05 +00:00
xkeyC
980a63668b fix: RsiLauncherEnhance
Some checks failed
Windows Nightly Build / build (push) Has been cancelled
Linux Nightly Build / build (push) Has been cancelled
2026-02-07 19:32:28 +08:00
dependabot[bot]
1ae03808dc build(deps): bump bytes from 1.11.0 to 1.11.1 in /rust (#199)
Bumps [bytes](https://github.com/tokio-rs/bytes) from 1.11.0 to 1.11.1.
- [Release notes](https://github.com/tokio-rs/bytes/releases)
- [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/bytes/compare/v1.11.0...v1.11.1)

---
updated-dependencies:
- dependency-name: bytes
  dependency-version: 1.11.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-07 19:14:01 +08:00
dependabot[bot]
8ca83305e8 build(deps): bump time from 0.3.44 to 0.3.47 in /rust (#200)
Bumps [time](https://github.com/time-rs/time) from 0.3.44 to 0.3.47.
- [Release notes](https://github.com/time-rs/time/releases)
- [Changelog](https://github.com/time-rs/time/blob/main/CHANGELOG.md)
- [Commits](https://github.com/time-rs/time/compare/v0.3.44...v0.3.47)

---
updated-dependencies:
- dependency-name: time
  dependency-version: 0.3.47
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-07 19:13:47 +08:00
xkeyC
9a56ccb4de bump: librqbit
Some checks failed
Windows Nightly Build / build (push) Has been cancelled
Linux Nightly Build / build (push) Has been cancelled
2026-02-03 16:00:19 +08:00
dependabot[bot]
8f76281fe3 build(deps): bump notify-rust from 4.11.7 to 4.12.0 in /rust (#195)
Bumps [notify-rust](https://github.com/hoodie/notify-rust) from 4.11.7 to 4.12.0.
- [Release notes](https://github.com/hoodie/notify-rust/releases)
- [Changelog](https://github.com/hoodie/notify-rust/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hoodie/notify-rust/compare/v4.11.7...v4.12.0)

---
updated-dependencies:
- dependency-name: notify-rust
  dependency-version: 4.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-02 20:07:06 +08:00
dependabot[bot]
c1fc848c92 build(deps): bump wmi from 0.18.0 to 0.18.1 in /rust (#196)
Bumps [wmi](https://github.com/ohadravid/wmi-rs) from 0.18.0 to 0.18.1.
- [Release notes](https://github.com/ohadravid/wmi-rs/releases)
- [Commits](https://github.com/ohadravid/wmi-rs/compare/v0.18.0...v0.18.1)

---
updated-dependencies:
- dependency-name: wmi
  dependency-version: 0.18.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-02 20:06:59 +08:00
dependabot[bot]
a74f0d62cc build(deps): bump file_picker from 10.3.8 to 10.3.10 (#197)
Bumps [file_picker](https://github.com/miguelpruivo/flutter_file_picker) from 10.3.8 to 10.3.10.
- [Release notes](https://github.com/miguelpruivo/flutter_file_picker/releases)
- [Changelog](https://github.com/miguelpruivo/flutter_file_picker/blob/master/CHANGELOG.md)
- [Commits](https://github.com/miguelpruivo/flutter_file_picker/compare/v10.3.8...v10.3.10)

---
updated-dependencies:
- dependency-name: file_picker
  dependency-version: 10.3.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-02 20:06:52 +08:00
xkeyC
74021dd8b6 feat: update cache clear ui 2026-02-02 20:06:07 +08:00
xkeyC
4503a0f099 fix: background 2026-02-02 19:11:42 +08:00
xkeyC
b6f0fb41aa bump: deps 2026-02-02 19:10:49 +08:00
xkeyC
43e7154ffc Revert "feat: bump deps"
This reverts commit 6263a0a5e6.
2026-02-02 19:05:34 +08:00
dependabot[bot]
9a0b97476e build(deps): bump dio from 5.9.0 to 5.9.1 (#198)
Bumps [dio](https://github.com/cfug/dio) from 5.9.0 to 5.9.1.
- [Release notes](https://github.com/cfug/dio/releases)
- [Commits](https://github.com/cfug/dio/compare/dio_v5.9.0...dio_v5.9.1)

---
updated-dependencies:
- dependency-name: dio
  dependency-version: 5.9.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-02 12:23:06 +08:00
xkeyC
68f42060ff fix: isInstalled
close: https://github.com/StarCitizenToolBox/app/issues/191
2026-01-27 15:23:28 +08:00
dependabot[bot]
f812bf9890 build(deps): bump uuid from 1.19.0 to 1.20.0 in /rust (#192)
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.19.0 to 1.20.0.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.19.0...v1.20.0)

---
updated-dependencies:
- dependency-name: uuid
  dependency-version: 1.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-26 09:29:42 +08:00
dependabot[bot]
2e8ec39752 build(deps): bump msix from 3.16.12 to 3.16.13 (#193)
Bumps [msix](https://github.com/YehudaKremer/msix) from 3.16.12 to 3.16.13.
- [Release notes](https://github.com/YehudaKremer/msix/releases)
- [Changelog](https://github.com/YehudaKremer/msix/blob/main/CHANGELOG.md)
- [Commits](https://github.com/YehudaKremer/msix/compare/v3.16.12...v3.16.13)

---
updated-dependencies:
- dependency-name: msix
  dependency-version: 3.16.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-26 09:29:32 +08:00
dependabot[bot]
a6c822ab85 build(deps): bump hive_ce from 2.18.0 to 2.19.1 (#194)
Bumps [hive_ce](https://github.com/IO-Design-Team/hive_ce) from 2.18.0 to 2.19.1.
- [Commits](https://github.com/IO-Design-Team/hive_ce/compare/hive_ce-2.18.0...hive_ce-2.19.1)

---
updated-dependencies:
- dependency-name: hive_ce
  dependency-version: 2.19.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-26 09:29:17 +08:00
xkeyC
f349b475e2 bump: 3.1.0 80 2026-01-19 15:10:53 +08:00
dependabot[bot]
01438b22c1 build(deps): bump build_runner from 2.10.4 to 2.10.5 (#190)
Bumps [build_runner](https://github.com/dart-lang/build) from 2.10.4 to 2.10.5.
- [Release notes](https://github.com/dart-lang/build/releases)
- [Commits](https://github.com/dart-lang/build/compare/build_runner-v2.10.4...build_runner-v2.10.5)

---
updated-dependencies:
- dependency-name: build_runner
  dependency-version: 2.10.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-19 14:45:49 +08:00
dependabot[bot]
5f21a61142 build(deps): bump hive_ce from 2.16.0 to 2.18.0 (#189)
Bumps [hive_ce](https://github.com/IO-Design-Team/hive_ce) from 2.16.0 to 2.18.0.
- [Commits](https://github.com/IO-Design-Team/hive_ce/compare/hive_ce-2.16.0...hive_ce-2.18.0)

---
updated-dependencies:
- dependency-name: hive_ce
  dependency-version: 2.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-19 14:45:40 +08:00
dependabot[bot]
18df2d93cc build(deps): bump wry from 0.53.5 to 0.54.1 in /rust (#188)
Bumps [wry](https://github.com/tauri-apps/wry) from 0.53.5 to 0.54.1.
- [Release notes](https://github.com/tauri-apps/wry/releases)
- [Changelog](https://github.com/tauri-apps/wry/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/tauri-apps/wry/compare/wry-v0.53.5...wry-v0.54.1)

---
updated-dependencies:
- dependency-name: wry
  dependency-version: 0.54.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-19 14:44:03 +08:00
xkeyC
70b6e29ad0 fix: datetime 2026-01-16 17:35:19 +08:00
dependabot[bot]
8f85d96488 build(deps): bump ndarray from 0.17.1 to 0.17.2 in /rust (#183)
Bumps [ndarray](https://github.com/rust-ndarray/ndarray) from 0.17.1 to 0.17.2.
- [Release notes](https://github.com/rust-ndarray/ndarray/releases)
- [Changelog](https://github.com/rust-ndarray/ndarray/blob/master/RELEASES.md)
- [Commits](https://github.com/rust-ndarray/ndarray/compare/0.17.1...0.17.2)

---
updated-dependencies:
- dependency-name: ndarray
  dependency-version: 0.17.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-13 18:14:31 +08:00
dependabot[bot]
c8a382c08f build(deps): bump serde_json from 1.0.148 to 1.0.149 in /rust (#182)
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.148 to 1.0.149.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.148...v1.0.149)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-version: 1.0.149
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-13 18:12:58 +08:00
dependabot[bot]
0f131203f2 build(deps): bump url from 2.5.7 to 2.5.8 in /rust (#184)
Bumps [url](https://github.com/servo/rust-url) from 2.5.7 to 2.5.8.
- [Release notes](https://github.com/servo/rust-url/releases)
- [Commits](https://github.com/servo/rust-url/compare/v2.5.7...v2.5.8)

---
updated-dependencies:
- dependency-name: url
  dependency-version: 2.5.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-13 18:12:45 +08:00
dependabot[bot]
bfbad50772 build(deps): bump ort from 2.0.0-rc.10 to 2.0.0-rc.11 in /rust (#185)
Bumps [ort](https://github.com/pykeio/ort) from 2.0.0-rc.10 to 2.0.0-rc.11.
- [Release notes](https://github.com/pykeio/ort/releases)
- [Commits](https://github.com/pykeio/ort/compare/v2.0.0-rc.10...v2.0.0-rc.11)

---
updated-dependencies:
- dependency-name: ort
  dependency-version: 2.0.0-rc.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-13 18:11:39 +08:00
dependabot[bot]
dc9816eb9e build(deps): bump ffi from 2.1.4 to 2.1.5 (#186)
Bumps [ffi](https://github.com/dart-lang/native/tree/main/pkgs) from 2.1.4 to 2.1.5.
- [Release notes](https://github.com/dart-lang/native/releases)
- [Commits](https://github.com/dart-lang/native/commits/ffi-v2.1.5/pkgs)

---
updated-dependencies:
- dependency-name: ffi
  dependency-version: 2.1.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-13 18:11:28 +08:00
dependabot[bot]
c6a0f5b8c0 build(deps): bump watcher from 1.2.0 to 1.2.1 (#187)
Bumps [watcher](https://github.com/dart-lang/tools/tree/main/pkgs) from 1.2.0 to 1.2.1.
- [Release notes](https://github.com/dart-lang/tools/releases)
- [Commits](https://github.com/dart-lang/tools/commits/watcher-v1.2.1/pkgs)

---
updated-dependencies:
- dependency-name: watcher
  dependency-version: 1.2.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-13 18:11:16 +08:00
xkeyC
91046e7f5e bump: librqbit
feat: add user_agent
2026-01-05 11:58:24 +08:00
dependabot[bot]
678f8d8cd7 build(deps): bump tokio from 1.48.0 to 1.49.0 in /rust (#181)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.48.0 to 1.49.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.48.0...tokio-1.49.0)

---
updated-dependencies:
- dependency-name: tokio
  dependency-version: 1.49.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-05 11:45:34 +08:00
xkeyC
6263a0a5e6 feat: bump deps
fix: Acrylic background Color
2025-12-30 18:16:26 +08:00
dependabot[bot]
45c0476636 build(deps): bump serde_json from 1.0.146 to 1.0.148 in /rust (#179)
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.146 to 1.0.148.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.146...v1.0.148)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-version: 1.0.148
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-29 10:05:19 +08:00
dependabot[bot]
d86d454f52 build(deps): bump riverpod_annotation, riverpod_lint, riverpod_generator, hooks_riverpod and flutter_riverpod (#180)
Bumps [riverpod_annotation](https://github.com/rrousselGit/riverpod), [riverpod_lint](https://github.com/rrousselGit/river_pod), [riverpod_generator](https://github.com/rrousselGit/riverpod), [hooks_riverpod](https://github.com/rrousselGit/riverpod) and [flutter_riverpod](https://github.com/rrousselGit/riverpod). These dependencies needed to be updated together.

Updates `riverpod_annotation` from 3.0.3 to 4.0.0
- [Commits](https://github.com/rrousselGit/riverpod/compare/riverpod_annotation-v3.0.3...riverpod_annotation-v4.0.0)

Updates `riverpod_lint` from 3.0.3 to 3.1.0
- [Commits](https://github.com/rrousselGit/river_pod/compare/riverpod_lint-v3.0.3...riverpod_lint-v3.1.0)

Updates `riverpod_generator` from 3.0.3 to 4.0.0+1
- [Commits](https://github.com/rrousselGit/riverpod/compare/riverpod_generator-v3.0.3...riverpod_generator-v4.0.0)

Updates `hooks_riverpod` from 3.0.3 to 3.1.0
- [Commits](https://github.com/rrousselGit/riverpod/compare/hooks_riverpod-v3.0.3...hooks_riverpod-v3.1.0)

Updates `flutter_riverpod` from 3.0.3 to 3.1.0
- [Commits](https://github.com/rrousselGit/riverpod/compare/flutter_riverpod-v3.0.3...flutter_riverpod-v3.1.0)

---
updated-dependencies:
- dependency-name: riverpod_annotation
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
- dependency-name: riverpod_lint
  dependency-version: 3.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: riverpod_generator
  dependency-version: 4.0.0+1
  dependency-type: direct:production
  update-type: version-update:semver-major
- dependency-name: hooks_riverpod
  dependency-version: 3.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: flutter_riverpod
  dependency-version: 3.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-29 10:05:05 +08:00
xkeyC
2be5441cf4 feat: translate-manager Beta 2025-12-27 11:47:25 +08:00
xkeyC
a673f70862 feat: update app_links 2025-12-26 23:03:41 +08:00
xkeyC
1d59acff2d feat: Add user profile refresh 2025-12-26 17:07:25 +08:00
xkeyC
724f7d8242 feat: oidc support 2025-12-26 16:39:12 +08:00
xkeyC
3135edad8e Add Linux Nightly Build badge to README 2025-12-24 16:33:44 +08:00
xkeyC
bba2dbd360 feat: linux support 2025-12-23 22:40:05 +08:00
xkeyC
66ead87d47 feat: Introduce shader cache cleaning options ('Keep Latest', 'Clean All') via a new dialog and refactor cleaning logic with new localization.
close: https://github.com/StarCitizenToolBox/app/issues/173
2025-12-23 17:54:15 +08:00
xkeyC
1a1f72a596 feat: Linux Path Basic Support
modified:   lib/ui/settings/settings_ui_model.dart
2025-12-23 16:45:22 +08:00
Copilot
062014f24a Add Linux Nightly Build workflow configuration (#178)
* Initial plan

* Add Linux Nightly Build workflow configuration

Co-authored-by: xkeyC <39891083+xkeyC@users.noreply.github.com>

* Add explicit permissions and comments to Linux workflow

Co-authored-by: xkeyC <39891083+xkeyC@users.noreply.github.com>

* Add NASM dependency and verify Linux build

Co-authored-by: xkeyC <39891083+xkeyC@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: xkeyC <39891083+xkeyC@users.noreply.github.com>
2025-12-23 12:52:26 +08:00
109 changed files with 5169 additions and 1200 deletions

View File

@@ -4,7 +4,7 @@
该工具为 星际公民玩家 提供 一键诊断,官网及工具网站汉化,游戏汉化,游戏性能优化 等功能,致力于带来更愉快的游戏体验。
[![Windows Nightly Build](https://github.com/StarCitizenToolBox/app/actions/workflows/windows_nightly.yml/badge.svg)](https://github.com/StarCitizenToolBox/app/actions/workflows/windows_nightly.yml) [![Translate](http://translate.42kit.com/widget/sctoolbox/CoreApp/svg-badge.svg)](http://translate.42kit.com/engage/sctoolbox/)
[![Windows Nightly Build](https://github.com/StarCitizenToolBox/app/actions/workflows/windows_nightly.yml/badge.svg)](https://github.com/StarCitizenToolBox/app/actions/workflows/windows_nightly.yml) [![Linux Nightly Build](https://github.com/StarCitizenToolBox/app/actions/workflows/linux_nightly.yml/badge.svg)](https://github.com/StarCitizenToolBox/app/actions/workflows/windows_nightly.yml) [![Translate](http://translate.42kit.com/widget/sctoolbox/CoreApp/svg-badge.svg)](http://translate.42kit.com/engage/sctoolbox/)
[![](https://get.microsoft.com/images/zh-cn%20dark.svg)](https://apps.microsoft.com/detail/9NF3SWFWNKL1?launch=true)

123
docs/AUTH_QUICK_START.md Normal file
View File

@@ -0,0 +1,123 @@
# SCToolBox OAuth 认证系统
## 快速开始
### 授权流程
```
┌─────────┐ ┌──────────┐ ┌────────────┐ ┌──────────┐
│ Web App │──1──▶│ Browser │──2──▶│ SCToolBox │──3──▶│ Server │
└─────────┘ └──────────┘ └────────────┘ └──────────┘
▲ │ │
│ ├──4──▶ 验证域名 │
│ │ 安全性 │
│ │ │
│ ├──5──▶ 生成 JWT │
│ │ 令牌 │
│ │ │
└─────────────────6─────────────────┘ │
返回令牌 │
```
### URL Scheme 格式
```
sctoolbox://auth/{domain}?callbackUrl={回调地址}
```
### 示例
```
sctoolbox://auth/example.com?callbackUrl=https%3A%2F%2Fexample.com%2Fauth%2Fcallback
```
### 回调格式
```
{callbackUrl}#access_token={jwt_token}&token_type=Bearer
```
## 功能特性
- ✅ 基于 JWT 的安全认证
- ✅ 域名白名单验证
- ✅ 跨平台支持Windows、macOS、Linux
- ✅ 两种授权方式(直接跳转 / 复制链接)
- ✅ 符合 OAuth 2.0 Implicit Flow 标准
## 实现文件
### 核心文件
- `lib/ui/auth/auth_page.dart` - 授权页面 UI
- `lib/ui/auth/auth_ui_model.dart` - 授权页面状态管理
- `lib/common/utils/url_scheme_handler.dart` - URL Scheme 处理器
### 平台配置
- `macos/Runner/Info.plist` - macOS URL Scheme 配置
- `windows/runner/main.cpp` - Windows Deep Link 处理
- `linux/my_application.cc` - Linux Deep Link 处理
- `linux/sctoolbox.desktop` - Linux MIME 类型注册
- `pubspec.yaml` - MSIX 协议激活配置
## 使用方法
### 初始化
URL Scheme handler 在 `IndexUI` 中自动初始化:
```dart
useEffect(() {
UrlSchemeHandler().initialize(context);
return () => UrlSchemeHandler().dispose();
}, const []);
```
### Web 应用集成
```javascript
// 发起授权
const authUrl = `sctoolbox://auth/example.com?callbackUrl=${encodeURIComponent(callbackUrl)}`;
window.location.href = authUrl;
// 处理回调
const params = new URLSearchParams(window.location.hash.substring(1));
const token = params.get('access_token');
```
## 平台要求
- **Windows**: 需要使用 MSIX 打包版本
- **macOS**: 需要配置 Info.plist
- **Linux**: 需要注册 .desktop 文件
## 安全性
- ✅ JWT 签名验证
- ✅ 域名白名单检查
- ✅ 令牌过期时间控制
- ✅ 使用 Fragment (#) 传递令牌(更安全)
## 详细文档
查看 [完整文档](./AUTH_SYSTEM.md) 了解更多信息,包括:
- 详细的授权流程
- API 接口说明
- Web 应用集成示例
- 安全最佳实践
- 常见问题解答
## API 端点
认证服务提供以下 gRPC 接口:
- `GenerateToken` - 生成 JWT 令牌
- `ValidateToken` - 验证令牌有效性
- `GetPublicKey` - 获取公钥用于验证
- `GetJWTDomainList` - 获取可信域名列表
## 测试
```bash
# macOS/Linux
open "sctoolbox://auth/test.example.com?callbackUrl=https%3A%2F%2Ftest.example.com%2Fcallback"
# Windows
start "sctoolbox://auth/test.example.com?callbackUrl=https%3A%2F%2Ftest.example.com%2Fcallback"
```

View File

@@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:io';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_acrylic/flutter_acrylic.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:go_router/go_router.dart';
@@ -24,6 +25,7 @@ import 'api/api.dart';
import 'common/conf/url_conf.dart';
import 'common/io/rs_http.dart';
import 'common/rust/frb_generated.dart';
import 'common/rust/api/applinks_api.dart' as applinks;
import 'common/rust/api/win32_api.dart' as win32;
import 'data/app_version_data.dart';
import 'generated/no_l10n_strings.dart';
@@ -125,6 +127,11 @@ class AppGlobalModel extends _$AppGlobalModel {
await RSHttp.init();
dPrint("---- rust bridge init -----");
// Register URL scheme
if ((!ConstConf.isMSE || kDebugMode) && Platform.isWindows) {
await _registerUrlScheme();
}
// init Hive
try {
Hive.init("$applicationSupportDir/db");
@@ -166,16 +173,19 @@ class AppGlobalModel extends _$AppGlobalModel {
windowManager.waitUntilReadyToShow().then((_) async {
await windowManager.setTitle("SCToolBox");
await windowManager.setSkipTaskbar(false);
await windowManager.show();
if (Platform.isWindows) {
await Window.initialize();
await Window.hideWindowControls();
if (windowsDeviceInfo?.productName.contains("Windows 11") ?? false) {
await Window.setEffect(effect: WindowEffect.acrylic);
// Apply acrylic effect before showing window
await Window.setEffect(effect: WindowEffect.acrylic, color: Colors.transparent, dark: true);
state = state.copyWith(windowsVersion: 11);
dPrint("---- Windows 11 Acrylic Effect init -----");
dPrint("---- Windows 11 Acrylic Effect applied -----");
} else {
state = state.copyWith(windowsVersion: 10);
await Window.setEffect(effect: WindowEffect.disabled);
}
}
// Show window after acrylic effect is applied
await windowManager.show();
});
dPrint("---- Window init -----");
@@ -318,6 +328,27 @@ class AppGlobalModel extends _$AppGlobalModel {
}
}
/// Register sctoolbox:// URL scheme for non-MSE builds
Future<void> _registerUrlScheme() async {
try {
const scheme = "sctoolbox";
const appName = "SCToolBox";
final result = await applinks.registerApplinks(scheme: scheme, appName: appName);
if (result.success) {
if (result.wasModified) {
dPrint("URL scheme '$scheme' registered successfully: ${result.message}");
} else {
dPrint("URL scheme '$scheme' already registered: ${result.message}");
}
} else {
dPrint("URL scheme '$scheme' registration check: ${result.message}");
// Even if check fails, the registration might have succeeded
}
} catch (e) {
dPrint("Failed to register URL scheme: $e");
}
}
Future<String> _initAppDir() async {
if (Platform.isWindows) {
final userProfileDir = Platform.environment["USERPROFILE"];

View File

@@ -10,12 +10,12 @@ part of 'app.dart';
// ignore_for_file: type=lint, type=warning
@ProviderFor(router)
const routerProvider = RouterProvider._();
final routerProvider = RouterProvider._();
final class RouterProvider
extends $FunctionalProvider<GoRouter, GoRouter, GoRouter>
with $Provider<GoRouter> {
const RouterProvider._()
RouterProvider._()
: super(
from: null,
argument: null,
@@ -51,11 +51,11 @@ final class RouterProvider
String _$routerHash() => r'e89f3f0277879147cdce5373cbe2554821e9cd31';
@ProviderFor(AppGlobalModel)
const appGlobalModelProvider = AppGlobalModelProvider._();
final appGlobalModelProvider = AppGlobalModelProvider._();
final class AppGlobalModelProvider
extends $NotifierProvider<AppGlobalModel, AppGlobalState> {
const AppGlobalModelProvider._()
AppGlobalModelProvider._()
: super(
from: null,
argument: null,
@@ -82,14 +82,13 @@ final class AppGlobalModelProvider
}
}
String _$appGlobalModelHash() => r'0e46d72594d94e2beb4d2ccb8616eb37facba288';
String _$appGlobalModelHash() => r'74128d2194d00a0e3dbb000dcaf6452e0b966d9c';
abstract class _$AppGlobalModel extends $Notifier<AppGlobalState> {
AppGlobalState build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<AppGlobalState, AppGlobalState>;
final element =
ref.element
@@ -99,6 +98,6 @@ abstract class _$AppGlobalModel extends $Notifier<AppGlobalState> {
Object?,
Object?
>;
element.handleValue(ref, created);
element.handleCreate(ref, build);
}
}

View File

@@ -1,7 +1,9 @@
import 'dart:io';
class ConstConf {
static const String appVersion = "3.0.0 Beta9";
static const int appVersionCode = 79;
static const String appVersionDate = "2025-12-22";
static const String appVersion = "3.1.0";
static const int appVersionCode = 80;
static const String appVersionDate = "2026-01-19";
static const _gameChannels = ["LIVE", "4.0_PREVIEW", "PTU", "EPTU", "TECH-PREVIEW", "HOTFIX"];
static const isMSE = String.fromEnvironment("MSE", defaultValue: "false") == "true";
static const win32AppId = isMSE
@@ -18,5 +20,12 @@ class AppConf {
_networkGameChannels = channels;
}
static List<String> get gameChannels => _networkGameChannels ?? ConstConf._gameChannels;
static List<String> get gameChannels {
final baseChannels = _networkGameChannels ?? ConstConf._gameChannels;
// On Linux, add lowercase variants for case-sensitive filesystem
if (Platform.isLinux) {
return [...baseChannels, ...baseChannels.map((c) => c.toLowerCase())];
}
return baseChannels;
}
}

View File

@@ -10,6 +10,8 @@ class URLConf {
static const String analyticsApiHome = "https://scbox.org";
/// PartyRoom Server
// static const String partyRoomServerAddress = "localhost";
// static const int partyRoomServerPort = 50051;
static const String partyRoomServerAddress = "ecdn.partyroom.grpc.scbox.xkeyc.cn";
static const int partyRoomServerPort = 443;
@@ -51,10 +53,7 @@ class URLConf {
gitApiHome = fasterGit;
}
final newsApiList = _genFinalList(await dnsLookupTxt("news.dns.scbox.org"));
final fasterNews = await rust_http.getFasterUrl(
urls: newsApiList,
pathSuffix: "/api/latest",
);
final fasterNews = await rust_http.getFasterUrl(urls: newsApiList, pathSuffix: "/api/latest");
dPrint("DNS newsApiList ==== $newsApiList");
dPrint("newsApiList.Faster ==== $fasterNews");
if (fasterNews != null) {

View File

@@ -3,11 +3,20 @@ import 'dart:io';
import 'package:hive_ce/hive.dart';
import 'package:starcitizen_doctor/common/conf/conf.dart';
import 'package:starcitizen_doctor/common/utils/base_utils.dart';
import 'package:starcitizen_doctor/common/utils/log.dart';
class SCLoggerHelper {
static Future<String?> getLogFilePath() async {
if (!Platform.isWindows) return null;
if (!Platform.isWindows) {
final wineUserPath = await getWineUserPath();
if (wineUserPath == null) return null;
// /home/xkeyc/Games/star-citizen/drive_c/users/xkeyc/AppData/Roaming/rsilauncher/
final rsiLauncherPath = "$wineUserPath/AppData/Roaming/rsilauncher";
dPrint("rsiLauncherPath Wine:$rsiLauncherPath");
final jsonLogPath = "$rsiLauncherPath/logs/log.log";
return jsonLogPath;
}
Map<String, String> envVars = Platform.environment;
final appDataPath = envVars["appdata"];
if (appDataPath == null) {
@@ -20,6 +29,14 @@ class SCLoggerHelper {
}
static Future<String?> getShaderCachePath() async {
if (!Platform.isWindows) {
final wineUserPath = await getWineUserPath();
if (wineUserPath == null) return null;
// /home/xkeyc/Games/star-citizen/drive_c/users/xkeyc/AppData/Local/star citizen/
final scCachePath = "$wineUserPath/AppData/Local/star citizen";
dPrint("getShaderCachePath Wine === $scCachePath");
return scCachePath;
}
Map<String, String> envVars = Platform.environment;
final appDataPath = envVars["LOCALAPPDATA"];
if (appDataPath == null) {
@@ -30,6 +47,23 @@ class SCLoggerHelper {
return scCachePath;
}
static Future<String?> getWineUserPath() async {
// get game path in hiveBox
final confBox = await Hive.openBox("app_conf");
final path = confBox.get("custom_game_path");
if (path?.isEmpty ?? true) return null;
// path eg: /home/xkeyc/Games/star-citizen/drive_c/Program Files/Roberts Space Industries/StarCitizen/LIVE/
// resolve wine c_drive path
final wineCDrivePath = path.toString().split('/drive_c/').first;
// scan wine user path == current_unix_user
final wineUserPath = "$wineCDrivePath/drive_c/users/${Platform.environment['USER']}";
// check exists
final wineUserDir = Directory(wineUserPath);
if (!await wineUserDir.exists()) return null;
dPrint("getWineUserPath === $wineUserPath");
return wineUserPath;
}
static Future<List?> getLauncherLogList() async {
if (!Platform.isWindows) return [];
try {
@@ -43,20 +77,25 @@ class SCLoggerHelper {
}
}
static Future<List<String>> getGameInstallPath(List listData,
{bool checkExists = true,
List<String> withVersion = const ["LIVE"]}) async {
static Future<List<String>> getGameInstallPath(
List listData, {
bool checkExists = true,
List<String> withVersion = const ["LIVE"],
}) async {
List<String> scInstallPaths = [];
checkAndAddPath(String path, bool checkExists) async {
// 将所有连续的 \\ 替换为 \
path = path.replaceAll(RegExp(r'\\+'), "\\");
if (path.isNotEmpty && !scInstallPaths.contains(path)) {
// Handle JSON-escaped backslashes (\\\\) -> single backslash (\\)
path = path.replaceAll(r'\\', r'\');
// Normalize path separators to current platform format
path = path.platformPath;
// Case-insensitive check for existing paths
if (path.isNotEmpty && !scInstallPaths.any((p) => p.toLowerCase() == path.toLowerCase())) {
if (!checkExists) {
dPrint("find installPath == $path");
scInstallPaths.add(path);
} else if (await File("$path/Bin64/StarCitizen.exe").exists() &&
await File("$path/Data.p4k").exists()) {
} else if (await File("$path/Bin64/StarCitizen.exe").exists() && await File("$path/Data.p4k").exists()) {
dPrint("find installPath == $path");
scInstallPaths.add(path);
}
@@ -67,14 +106,25 @@ class SCLoggerHelper {
final path = confBox.get("custom_game_path");
if (path != null && path != "") {
for (var v in withVersion) {
await checkAndAddPath("$path\\$v", checkExists);
await checkAndAddPath("$path\\$v".platformPath, checkExists);
}
}
try {
for (var v in withVersion) {
String pattern =
r'([a-zA-Z]:\\\\[^\\\\]*\\\\[^\\\\]*\\\\StarCitizen\\\\' + v + r')';
// Platform-specific regex patterns for game install path detection
// Uses restrictive character class to avoid matching across JSON delimiters
String pattern;
if (Platform.isWindows) {
// Windows: Match paths like C:\...\StarCitizen\LIVE
// Path segments can only contain: letters, numbers, space, dot, underscore, hyphen, parentheses
// Handles both single backslash, forward slash, and JSON-escaped double backslash
pattern =
r'([a-zA-Z]:(?:[/\\]|\\\\)(?:[a-zA-Z0-9 ._()-]+(?:[/\\]|\\\\))*StarCitizen(?:[/\\]|\\\\)' + v + r')';
} else {
// Unix (Wine): Match paths like /home/user/.../StarCitizen/LIVE
pattern = r'(/(?:[a-zA-Z0-9 ._()-]+/)*StarCitizen/' + v + r')';
}
RegExp regExp = RegExp(pattern, caseSensitive: false);
for (var i = listData.length - 1; i > 0; i--) {
final line = listData[i];
@@ -89,10 +139,14 @@ class SCLoggerHelper {
// 动态检测更多位置
for (var fileName in List.from(scInstallPaths)) {
for (var v in withVersion) {
if (fileName.toString().endsWith(v)) {
final suffix = '\\$v'.platformPath.toLowerCase();
if (fileName.toString().toLowerCase().endsWith(suffix)) {
for (var nv in withVersion) {
final nextName =
"${fileName.toString().replaceAll("\\$v", "")}\\$nv";
final basePath = fileName.toString().replaceAll(
RegExp('${RegExp.escape(suffix)}\$', caseSensitive: false),
'',
);
final nextName = "$basePath\\$nv".platformPath;
await checkAndAddPath(nextName, true);
}
}
@@ -108,9 +162,10 @@ class SCLoggerHelper {
}
static String getGameChannelID(String installPath) {
final pathLower = installPath.platformPath.toLowerCase();
for (var value in AppConf.gameChannels) {
if (installPath.endsWith("\\$value")) {
return value;
if (pathLower.endsWith('\\${value.toLowerCase()}'.platformPath)) {
return value.toUpperCase();
}
}
return "UNKNOWN";
@@ -121,8 +176,7 @@ class SCLoggerHelper {
if (!await logFile.exists()) {
return null;
}
return await logFile.readAsLines(
encoding: const Utf8Codec(allowMalformed: true));
return await logFile.readAsLines(encoding: const Utf8Codec(allowMalformed: true));
}
static MapEntry<String, String>? getGameRunningLogInfo(List<String> logs) {
@@ -138,47 +192,47 @@ class SCLoggerHelper {
static MapEntry<String, String>? _checkRunningLine(String line) {
if (line.contains("STATUS_CRYENGINE_OUT_OF_SYSMEM")) {
return MapEntry(S.current.doctor_game_error_low_memory,
S.current.doctor_game_error_low_memory_info);
return MapEntry(S.current.doctor_game_error_low_memory, S.current.doctor_game_error_low_memory_info);
}
if (line.contains("EXCEPTION_ACCESS_VIOLATION")) {
return MapEntry(S.current.doctor_game_error_generic_info,
"https://docs.qq.com/doc/DUURxUVhzTmZoY09Z");
return MapEntry(S.current.doctor_game_error_generic_info, "https://docs.qq.com/doc/DUURxUVhzTmZoY09Z");
}
if (line.contains("DXGI_ERROR_DEVICE_REMOVED")) {
return MapEntry(S.current.doctor_game_error_gpu_crash,
"https://www.bilibili.com/read/cv19335199");
return MapEntry(S.current.doctor_game_error_gpu_crash, "https://www.bilibili.com/read/cv19335199");
}
if (line.contains("Wakeup socket sendto error")) {
return MapEntry(S.current.doctor_game_error_socket_error,
S.current.doctor_game_error_socket_error_info);
return MapEntry(S.current.doctor_game_error_socket_error, S.current.doctor_game_error_socket_error_info);
}
if (line.contains("The requested operation requires elevated")) {
return MapEntry(S.current.doctor_game_error_permissions_error,
S.current.doctor_game_error_permissions_error_info);
return MapEntry(
S.current.doctor_game_error_permissions_error,
S.current.doctor_game_error_permissions_error_info,
);
}
if (line.contains(
"The process cannot access the file because is is being used by another process")) {
return MapEntry(S.current.doctor_game_error_game_process_error,
S.current.doctor_game_error_game_process_error_info);
if (line.contains("The process cannot access the file because is is being used by another process")) {
return MapEntry(
S.current.doctor_game_error_game_process_error,
S.current.doctor_game_error_game_process_error_info,
);
}
if (line.contains("0xc0000043")) {
return MapEntry(S.current.doctor_game_error_game_damaged_file,
S.current.doctor_game_error_game_damaged_file_info);
return MapEntry(
S.current.doctor_game_error_game_damaged_file,
S.current.doctor_game_error_game_damaged_file_info,
);
}
if (line.contains("option to verify the content of the Data.p4k file")) {
return MapEntry(S.current.doctor_game_error_game_damaged_p4k_file,
S.current.doctor_game_error_game_damaged_p4k_file_info);
return MapEntry(
S.current.doctor_game_error_game_damaged_p4k_file,
S.current.doctor_game_error_game_damaged_p4k_file_info,
);
}
if (line.contains("OUTOFMEMORY Direct3D could not allocate")) {
return MapEntry(S.current.doctor_game_error_low_gpu_memory,
S.current.doctor_game_error_low_gpu_memory_info);
return MapEntry(S.current.doctor_game_error_low_gpu_memory, S.current.doctor_game_error_low_gpu_memory_info);
}
if (line.contains(
"try disabling with r_vulkanDisableLayers = 1 in your user.cfg")) {
return MapEntry(S.current.doctor_game_error_gpu_vulkan_crash,
S.current.doctor_game_error_gpu_vulkan_crash_info);
if (line.contains("try disabling with r_vulkanDisableLayers = 1 in your user.cfg")) {
return MapEntry(S.current.doctor_game_error_gpu_vulkan_crash, S.current.doctor_game_error_gpu_vulkan_crash_info);
}
/// Unknown

View File

@@ -1,6 +1,7 @@
import 'dart:io';
import 'package:hive_ce/hive.dart';
import 'package:starcitizen_doctor/common/utils/base_utils.dart';
import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/common/rust/api/win32_api.dart' as win32;
@@ -41,7 +42,7 @@ class SystemHelper {
if (path != null && path != "") {
if (await File(path).exists()) {
if (skipEXE) {
return "${path.toString().replaceAll("\\RSI Launcher.exe", "")}\\";
return "${path.toString().replaceAll("\\RSI Launcher.exe".platformPath, "")}\\".platformPath;
}
return path;
}

View File

@@ -0,0 +1,68 @@
// This file is automatically generated, so please do not edit it.
// @generated by `flutter_rust_bridge`@ 2.11.1.
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
import '../frb_generated.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `fmt`
/// Check if the URL scheme is already registered with the correct executable path
Future<ApplinksRegistrationResult> checkApplinksRegistration({
required String scheme,
}) => RustLib.instance.api.crateApiApplinksApiCheckApplinksRegistration(
scheme: scheme,
);
/// Register URL scheme in Windows registry
/// This will create or update the registry keys for the custom URL scheme
///
/// # Arguments
/// * `scheme` - The URL scheme to register (e.g., "sctoolbox")
/// * `app_name` - Optional application display name (e.g., "SCToolBox"). If provided,
/// the registry will show "URL:{app_name} Protocol" as the scheme description.
Future<ApplinksRegistrationResult> registerApplinks({
required String scheme,
String? appName,
}) => RustLib.instance.api.crateApiApplinksApiRegisterApplinks(
scheme: scheme,
appName: appName,
);
/// Unregister URL scheme from Windows registry
Future<ApplinksRegistrationResult> unregisterApplinks({
required String scheme,
}) =>
RustLib.instance.api.crateApiApplinksApiUnregisterApplinks(scheme: scheme);
/// Applinks URL scheme registration result
class ApplinksRegistrationResult {
/// Whether registration was successful
final bool success;
/// Detailed message about the operation
final String message;
/// Whether the registry was modified (false if already configured correctly)
final bool wasModified;
const ApplinksRegistrationResult({
required this.success,
required this.message,
required this.wasModified,
});
@override
int get hashCode =>
success.hashCode ^ message.hashCode ^ wasModified.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is ApplinksRegistrationResult &&
runtimeType == other.runtimeType &&
success == other.success &&
message == other.message &&
wasModified == other.wasModified;
}

View File

@@ -6,6 +6,7 @@
import '../frb_generated.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
// These functions are ignored because they are not marked as `pub`: `get_process_path`
// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `clone`, `fmt`, `fmt`
Future<void> sendNotify({
@@ -20,21 +21,27 @@ Future<void> sendNotify({
appId: appId,
);
/// Get system memory size in GB
Future<BigInt> getSystemMemorySizeGb() =>
RustLib.instance.api.crateApiWin32ApiGetSystemMemorySizeGb();
/// Get number of logical processors
Future<int> getNumberOfLogicalProcessors() =>
RustLib.instance.api.crateApiWin32ApiGetNumberOfLogicalProcessors();
/// Get all system information at once
Future<SystemInfo> getSystemInfo() =>
RustLib.instance.api.crateApiWin32ApiGetSystemInfo();
/// Get GPU info from registry (more accurate VRAM)
Future<String> getGpuInfoFromRegistry() =>
RustLib.instance.api.crateApiWin32ApiGetGpuInfoFromRegistry();
/// Resolve shortcut (.lnk) file to get target path
Future<String> resolveShortcut({required String lnkPath}) =>
RustLib.instance.api.crateApiWin32ApiResolveShortcut(lnkPath: lnkPath);
/// Open file explorer and select file/folder
Future<void> openDirWithExplorer({
required String path,
required bool isFile,
@@ -58,16 +65,19 @@ Future<List<ProcessInfo>> getProcessListByName({required String processName}) =>
processName: processName,
);
/// Kill processes by name
Future<int> killProcessByName({required String processName}) => RustLib
.instance
.api
.crateApiWin32ApiKillProcessByName(processName: processName);
/// Get disk physical sector size for performance
Future<int> getDiskPhysicalSectorSize({required String driveLetter}) => RustLib
.instance
.api
.crateApiWin32ApiGetDiskPhysicalSectorSize(driveLetter: driveLetter);
/// Create a desktop shortcut
Future<void> createDesktopShortcut({
required String targetPath,
required String shortcutName,
@@ -76,12 +86,14 @@ Future<void> createDesktopShortcut({
shortcutName: shortcutName,
);
/// Run a program with admin privileges (UAC)
Future<void> runAsAdmin({required String program, required String args}) =>
RustLib.instance.api.crateApiWin32ApiRunAsAdmin(
program: program,
args: args,
);
/// Start a program (without waiting)
Future<void> startProcess({
required String program,
required List<String> args,
@@ -90,12 +102,15 @@ Future<void> startProcess({
args: args,
);
/// Check if NVME patch is applied
Future<bool> checkNvmePatchStatus() =>
RustLib.instance.api.crateApiWin32ApiCheckNvmePatchStatus();
/// Add NVME patch to registry
Future<void> addNvmePatch() =>
RustLib.instance.api.crateApiWin32ApiAddNvmePatch();
/// Remove NVME patch from registry
Future<void> removeNvmePatch() =>
RustLib.instance.api.crateApiWin32ApiRemoveNvmePatch();

View File

@@ -3,6 +3,7 @@
// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field
import 'api/applinks_api.dart';
import 'api/asar_api.dart';
import 'api/downloader_api.dart';
import 'api/http_api.dart';
@@ -72,7 +73,7 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
String get codegenVersion => '2.11.1';
@override
int get rustContentHash => -1903117367;
int get rustContentHash => -351025706;
static const kDefaultExternalLibraryLoaderConfig =
ExternalLibraryLoaderConfig(
@@ -85,6 +86,9 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
abstract class RustLibApi extends BaseApi {
Future<void> crateApiWin32ApiAddNvmePatch();
Future<ApplinksRegistrationResult>
crateApiApplinksApiCheckApplinksRegistration({required String scheme});
Future<bool> crateApiWin32ApiCheckNvmePatchStatus();
Future<void> crateApiOrtApiClearAllModels();
@@ -268,6 +272,11 @@ abstract class RustLibApi extends BaseApi {
Future<void> crateApiUnp4KApiP4KOpen({required String p4KPath});
Future<ApplinksRegistrationResult> crateApiApplinksApiRegisterApplinks({
required String scheme,
String? appName,
});
Future<void> crateApiWin32ApiRemoveNvmePatch();
Future<String> crateApiWin32ApiResolveShortcut({required String lnkPath});
@@ -320,6 +329,10 @@ abstract class RustLibApi extends BaseApi {
Future<void> crateApiOrtApiUnloadTranslationModel({required String modelKey});
Future<ApplinksRegistrationResult> crateApiApplinksApiUnregisterApplinks({
required String scheme,
});
Future<WebViewConfiguration> crateApiWebviewApiWebViewConfigurationDefault();
Future<WebViewNavigationState>
@@ -411,6 +424,36 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
TaskConstMeta get kCrateApiWin32ApiAddNvmePatchConstMeta =>
const TaskConstMeta(debugName: "add_nvme_patch", argNames: []);
@override
Future<ApplinksRegistrationResult>
crateApiApplinksApiCheckApplinksRegistration({required String scheme}) {
return handler.executeNormal(
NormalTask(
callFfi: (port_) {
var arg0 = cst_encode_String(scheme);
return wire
.wire__crate__api__applinks_api__check_applinks_registration(
port_,
arg0,
);
},
codec: DcoCodec(
decodeSuccessData: dco_decode_applinks_registration_result,
decodeErrorData: dco_decode_AnyhowException,
),
constMeta: kCrateApiApplinksApiCheckApplinksRegistrationConstMeta,
argValues: [scheme],
apiImpl: this,
),
);
}
TaskConstMeta get kCrateApiApplinksApiCheckApplinksRegistrationConstMeta =>
const TaskConstMeta(
debugName: "check_applinks_registration",
argNames: ["scheme"],
);
@override
Future<bool> crateApiWin32ApiCheckNvmePatchStatus() {
return handler.executeNormal(
@@ -1987,6 +2030,39 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
TaskConstMeta get kCrateApiUnp4KApiP4KOpenConstMeta =>
const TaskConstMeta(debugName: "p4k_open", argNames: ["p4KPath"]);
@override
Future<ApplinksRegistrationResult> crateApiApplinksApiRegisterApplinks({
required String scheme,
String? appName,
}) {
return handler.executeNormal(
NormalTask(
callFfi: (port_) {
var arg0 = cst_encode_String(scheme);
var arg1 = cst_encode_opt_String(appName);
return wire.wire__crate__api__applinks_api__register_applinks(
port_,
arg0,
arg1,
);
},
codec: DcoCodec(
decodeSuccessData: dco_decode_applinks_registration_result,
decodeErrorData: dco_decode_AnyhowException,
),
constMeta: kCrateApiApplinksApiRegisterApplinksConstMeta,
argValues: [scheme, appName],
apiImpl: this,
),
);
}
TaskConstMeta get kCrateApiApplinksApiRegisterApplinksConstMeta =>
const TaskConstMeta(
debugName: "register_applinks",
argNames: ["scheme", "appName"],
);
@override
Future<void> crateApiWin32ApiRemoveNvmePatch() {
return handler.executeNormal(
@@ -2369,6 +2445,36 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
argNames: ["modelKey"],
);
@override
Future<ApplinksRegistrationResult> crateApiApplinksApiUnregisterApplinks({
required String scheme,
}) {
return handler.executeNormal(
NormalTask(
callFfi: (port_) {
var arg0 = cst_encode_String(scheme);
return wire.wire__crate__api__applinks_api__unregister_applinks(
port_,
arg0,
);
},
codec: DcoCodec(
decodeSuccessData: dco_decode_applinks_registration_result,
decodeErrorData: dco_decode_AnyhowException,
),
constMeta: kCrateApiApplinksApiUnregisterApplinksConstMeta,
argValues: [scheme],
apiImpl: this,
),
);
}
TaskConstMeta get kCrateApiApplinksApiUnregisterApplinksConstMeta =>
const TaskConstMeta(
debugName: "unregister_applinks",
argNames: ["scheme"],
);
@override
Future<WebViewConfiguration> crateApiWebviewApiWebViewConfigurationDefault() {
return handler.executeNormal(
@@ -2869,6 +2975,21 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return raw as String;
}
@protected
ApplinksRegistrationResult dco_decode_applinks_registration_result(
dynamic raw,
) {
// Codec=Dco (DartCObject based), see doc to use other codecs
final arr = raw as List<dynamic>;
if (arr.length != 3)
throw Exception('unexpected arr length: expect 3 but see ${arr.length}');
return ApplinksRegistrationResult(
success: dco_decode_bool(arr[0]),
message: dco_decode_String(arr[1]),
wasModified: dco_decode_bool(arr[2]),
);
}
@protected
bool dco_decode_bool(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
@@ -3347,6 +3468,21 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return utf8.decoder.convert(inner);
}
@protected
ApplinksRegistrationResult sse_decode_applinks_registration_result(
SseDeserializer deserializer,
) {
// Codec=Sse (Serialization based), see doc to use other codecs
var var_success = sse_decode_bool(deserializer);
var var_message = sse_decode_String(deserializer);
var var_wasModified = sse_decode_bool(deserializer);
return ApplinksRegistrationResult(
success: var_success,
message: var_message,
wasModified: var_wasModified,
);
}
@protected
bool sse_decode_bool(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -4047,6 +4183,17 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_list_prim_u_8_strict(utf8.encoder.convert(self), serializer);
}
@protected
void sse_encode_applinks_registration_result(
ApplinksRegistrationResult self,
SseSerializer serializer,
) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_bool(self.success, serializer);
sse_encode_String(self.message, serializer);
sse_encode_bool(self.wasModified, serializer);
}
@protected
void sse_encode_bool(bool self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs

View File

@@ -3,6 +3,7 @@
// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field
import 'api/applinks_api.dart';
import 'api/asar_api.dart';
import 'api/downloader_api.dart';
import 'api/http_api.dart';
@@ -39,6 +40,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
String dco_decode_String(dynamic raw);
@protected
ApplinksRegistrationResult dco_decode_applinks_registration_result(
dynamic raw,
);
@protected
bool dco_decode_bool(dynamic raw);
@@ -216,6 +222,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
String sse_decode_String(SseDeserializer deserializer);
@protected
ApplinksRegistrationResult sse_decode_applinks_registration_result(
SseDeserializer deserializer,
);
@protected
bool sse_decode_bool(SseDeserializer deserializer);
@@ -672,6 +683,16 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
return raw.toSigned(64).toInt();
}
@protected
void cst_api_fill_to_wire_applinks_registration_result(
ApplinksRegistrationResult apiObj,
wire_cst_applinks_registration_result wireObj,
) {
wireObj.success = cst_encode_bool(apiObj.success);
wireObj.message = cst_encode_String(apiObj.message);
wireObj.was_modified = cst_encode_bool(apiObj.wasModified);
}
@protected
void cst_api_fill_to_wire_box_autoadd_rsi_launcher_asar_data(
RsiLauncherAsarData apiObj,
@@ -946,6 +967,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
void sse_encode_String(String self, SseSerializer serializer);
@protected
void sse_encode_applinks_registration_result(
ApplinksRegistrationResult self,
SseSerializer serializer,
);
@protected
void sse_encode_bool(bool self, SseSerializer serializer);
@@ -1227,6 +1254,33 @@ class RustLibWire implements BaseWire {
_wire__crate__api__win32_api__add_nvme_patchPtr
.asFunction<void Function(int)>();
void wire__crate__api__applinks_api__check_applinks_registration(
int port_,
ffi.Pointer<wire_cst_list_prim_u_8_strict> scheme,
) {
return _wire__crate__api__applinks_api__check_applinks_registration(
port_,
scheme,
);
}
late final _wire__crate__api__applinks_api__check_applinks_registrationPtr =
_lookup<
ffi.NativeFunction<
ffi.Void Function(
ffi.Int64,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
)
>
>(
'frbgen_starcitizen_doctor_wire__crate__api__applinks_api__check_applinks_registration',
);
late final _wire__crate__api__applinks_api__check_applinks_registration =
_wire__crate__api__applinks_api__check_applinks_registrationPtr
.asFunction<
void Function(int, ffi.Pointer<wire_cst_list_prim_u_8_strict>)
>();
void wire__crate__api__win32_api__check_nvme_patch_status(int port_) {
return _wire__crate__api__win32_api__check_nvme_patch_status(port_);
}
@@ -2453,6 +2507,40 @@ class RustLibWire implements BaseWire {
void Function(int, ffi.Pointer<wire_cst_list_prim_u_8_strict>)
>();
void wire__crate__api__applinks_api__register_applinks(
int port_,
ffi.Pointer<wire_cst_list_prim_u_8_strict> scheme,
ffi.Pointer<wire_cst_list_prim_u_8_strict> app_name,
) {
return _wire__crate__api__applinks_api__register_applinks(
port_,
scheme,
app_name,
);
}
late final _wire__crate__api__applinks_api__register_applinksPtr =
_lookup<
ffi.NativeFunction<
ffi.Void Function(
ffi.Int64,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
)
>
>(
'frbgen_starcitizen_doctor_wire__crate__api__applinks_api__register_applinks',
);
late final _wire__crate__api__applinks_api__register_applinks =
_wire__crate__api__applinks_api__register_applinksPtr
.asFunction<
void Function(
int,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
)
>();
void wire__crate__api__win32_api__remove_nvme_patch(int port_) {
return _wire__crate__api__win32_api__remove_nvme_patch(port_);
}
@@ -2467,9 +2555,9 @@ class RustLibWire implements BaseWire {
void wire__crate__api__win32_api__resolve_shortcut(
int port_,
ffi.Pointer<wire_cst_list_prim_u_8_strict> _lnk_path,
ffi.Pointer<wire_cst_list_prim_u_8_strict> lnk_path,
) {
return _wire__crate__api__win32_api__resolve_shortcut(port_, _lnk_path);
return _wire__crate__api__win32_api__resolve_shortcut(port_, lnk_path);
}
late final _wire__crate__api__win32_api__resolve_shortcutPtr =
@@ -2799,6 +2887,30 @@ class RustLibWire implements BaseWire {
void Function(int, ffi.Pointer<wire_cst_list_prim_u_8_strict>)
>();
void wire__crate__api__applinks_api__unregister_applinks(
int port_,
ffi.Pointer<wire_cst_list_prim_u_8_strict> scheme,
) {
return _wire__crate__api__applinks_api__unregister_applinks(port_, scheme);
}
late final _wire__crate__api__applinks_api__unregister_applinksPtr =
_lookup<
ffi.NativeFunction<
ffi.Void Function(
ffi.Int64,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
)
>
>(
'frbgen_starcitizen_doctor_wire__crate__api__applinks_api__unregister_applinks',
);
late final _wire__crate__api__applinks_api__unregister_applinks =
_wire__crate__api__applinks_api__unregister_applinksPtr
.asFunction<
void Function(int, ffi.Pointer<wire_cst_list_prim_u_8_strict>)
>();
void wire__crate__api__webview_api__web_view_configuration_default(
int port_,
) {
@@ -3721,6 +3833,16 @@ final class wire_cst_list_web_view_event extends ffi.Struct {
external int len;
}
final class wire_cst_applinks_registration_result extends ffi.Struct {
@ffi.Bool()
external bool success;
external ffi.Pointer<wire_cst_list_prim_u_8_strict> message;
@ffi.Bool()
external bool was_modified;
}
final class wire_cst_download_global_stat extends ffi.Struct {
@ffi.Uint64()
external int download_speed;

View File

@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:io';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/rendering.dart';
@@ -7,105 +8,118 @@ import 'dart:ui' as ui;
import 'package:flutter/services.dart';
import 'package:starcitizen_doctor/generated/l10n.dart';
Future showToast(BuildContext context, String msg, {BoxConstraints? constraints, String? title}) async {
return showBaseDialog(context,
title: title ?? S.current.app_common_tip,
content: Text(msg),
actions: [
FilledButton(
child: Padding(
padding: const EdgeInsets.only(top: 2, bottom: 2, left: 8, right: 8),
child: Text(S.current.app_common_tip_i_know),
),
onPressed: () => Navigator.pop(context),
),
],
constraints: constraints);
/// String extension for cross-platform path compatibility
extension PathStringExtension on String {
/// Converts path separators to the current platform's format.
/// On Windows: / -> \
/// On Linux/macOS: \ -> /
String get platformPath {
if (Platform.isWindows) {
return replaceAll('/', '\\');
}
return replaceAll('\\', '/');
}
}
Future<bool> showConfirmDialogs(BuildContext context, String title, Widget content,
{String confirm = "", String cancel = "", BoxConstraints? constraints}) async {
Future showToast(BuildContext context, String msg, {BoxConstraints? constraints, String? title}) async {
return showBaseDialog(
context,
title: title ?? S.current.app_common_tip,
content: Text(msg),
actions: [
FilledButton(
child: Padding(
padding: const EdgeInsets.only(top: 2, bottom: 2, left: 8, right: 8),
child: Text(S.current.app_common_tip_i_know),
),
onPressed: () => Navigator.pop(context),
),
],
constraints: constraints,
);
}
Future<bool> showConfirmDialogs(
BuildContext context,
String title,
Widget content, {
String confirm = "",
String cancel = "",
BoxConstraints? constraints,
}) async {
if (confirm.isEmpty) confirm = S.current.app_common_tip_confirm;
if (cancel.isEmpty) cancel = S.current.app_common_tip_cancel;
final r = await showBaseDialog(context,
title: title,
content: content,
actions: [
if (confirm.isNotEmpty)
FilledButton(
child: Padding(
padding: const EdgeInsets.only(top: 2, bottom: 2, left: 8, right: 8),
child: Text(confirm),
),
onPressed: () => Navigator.pop(context, true),
),
if (cancel.isNotEmpty)
Button(
child: Padding(
padding: const EdgeInsets.only(top: 2, bottom: 2, left: 8, right: 8),
child: Text(cancel),
),
onPressed: () => Navigator.pop(context, false),
),
],
constraints: constraints);
final r = await showBaseDialog(
context,
title: title,
content: content,
actions: [
if (confirm.isNotEmpty)
FilledButton(
child: Padding(padding: const EdgeInsets.only(top: 2, bottom: 2, left: 8, right: 8), child: Text(confirm)),
onPressed: () => Navigator.pop(context, true),
),
if (cancel.isNotEmpty)
Button(
child: Padding(padding: const EdgeInsets.only(top: 2, bottom: 2, left: 8, right: 8), child: Text(cancel)),
onPressed: () => Navigator.pop(context, false),
),
],
constraints: constraints,
);
return r == true;
}
Future<String?> showInputDialogs(BuildContext context,
{required String title,
required String content,
BoxConstraints? constraints,
String? initialValue,
List<TextInputFormatter>? inputFormatters}) async {
Future<String?> showInputDialogs(
BuildContext context, {
required String title,
required String content,
BoxConstraints? constraints,
String? initialValue,
List<TextInputFormatter>? inputFormatters,
}) async {
String? userInput;
constraints ??= BoxConstraints(maxWidth: MediaQuery
.of(context)
.size
.width * .38);
constraints ??= BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .38);
final ok = await showConfirmDialogs(
context,
title,
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (content.isNotEmpty)
Text(
content,
style: TextStyle(color: Colors.white.withValues(alpha: .6)),
),
const SizedBox(height: 8),
TextFormBox(
initialValue: initialValue,
onChanged: (str) {
userInput = str;
},
inputFormatters: inputFormatters,
),
],
),
constraints: constraints);
context,
title,
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (content.isNotEmpty) Text(content, style: TextStyle(color: Colors.white.withValues(alpha: .6))),
const SizedBox(height: 8),
TextFormBox(
initialValue: initialValue,
onChanged: (str) {
userInput = str;
},
inputFormatters: inputFormatters,
),
],
),
constraints: constraints,
);
if (ok == true) return userInput;
return null;
}
Future showBaseDialog(BuildContext context,
{required String title, required Widget content, List<Widget>? actions, BoxConstraints? constraints}) async {
Future showBaseDialog(
BuildContext context, {
required String title,
required Widget content,
List<Widget>? actions,
BoxConstraints? constraints,
}) async {
return await showDialog(
context: context,
builder: (context) =>
ContentDialog(
title: Text(title),
content: content,
constraints: constraints ??
const BoxConstraints(
maxWidth: 512,
maxHeight: 756.0,
),
actions: actions,
),
builder: (context) => ContentDialog(
title: Text(title),
content: content,
constraints: constraints ?? const BoxConstraints(maxWidth: 512, maxHeight: 756.0),
actions: actions,
),
);
}

View File

@@ -1,4 +1,5 @@
import 'dart:convert';
import 'dart:io';
import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:fluent_ui/fluent_ui.dart';
@@ -112,7 +113,7 @@ class MultiWindowManager {
await Window.initialize();
if (windowAppState.windowsVersion >= 10) {
if (Platform.isWindows && windowAppState.windowsVersion >= 10) {
await Window.setEffect(effect: WindowEffect.acrylic);
}

View File

@@ -0,0 +1,121 @@
import 'dart:async';
import 'package:app_links/app_links.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/ui/auth/auth_page.dart';
/// URL Scheme handler for deep linking
/// Handles: sctoolbox://auth?callbackUrl=https://example.com
class UrlSchemeHandler {
static final UrlSchemeHandler _instance = UrlSchemeHandler._internal();
factory UrlSchemeHandler() => _instance;
UrlSchemeHandler._internal();
final _appLinks = AppLinks();
StreamSubscription<Uri>? _linkSubscription;
BuildContext? _context;
// Debouncing variables
String? _lastHandledUri;
DateTime? _lastHandledTime;
static const _debounceDuration = Duration(seconds: 2);
/// Initialize URL scheme handler
Future<void> initialize(BuildContext context) async {
_context = context;
// Handle initial link when app is launched via URL scheme
try {
final initialUri = await _appLinks.getInitialLink();
if (initialUri != null) {
dPrint('Initial URI: $initialUri');
_handleUri(initialUri);
}
} catch (e) {
dPrint('Failed to get initial URI: $e');
}
// Handle links while app is running
_linkSubscription = _appLinks.uriLinkStream.listen(
(uri) {
dPrint('Received URI: $uri');
_handleUri(uri);
},
onError: (err) {
dPrint('URI link stream error: $err');
},
);
}
/// Handle incoming URI with debouncing
void _handleUri(Uri uri) {
final uriString = uri.toString();
final now = DateTime.now();
// Check if this is a duplicate URI within debounce duration
if (_lastHandledUri == uriString && _lastHandledTime != null) {
final timeSinceLastHandle = now.difference(_lastHandledTime!);
if (timeSinceLastHandle < _debounceDuration) {
dPrint('Debounced duplicate URI: $uriString (${timeSinceLastHandle.inMilliseconds}ms since last)');
return;
}
}
// Update last handled URI and time
_lastHandledUri = uriString;
_lastHandledTime = now;
dPrint('Handling URI: $uri');
// Check if it's an auth request
// Check if it's an auth request
// Expected format: sctoolbox://auth?callbackUrl=https://example.com&state=...&nonce=...
// Note: old format with domain in path (sctoolbox://auth/domain?...) is also supported but domain is ignored
if (uri.scheme == 'sctoolbox' && uri.host == 'auth') {
final callbackUrl = uri.queryParameters['callbackUrl'];
final state = uri.queryParameters['state'];
final nonce = uri.queryParameters['nonce'];
if (callbackUrl == null || callbackUrl.isEmpty) {
dPrint('Invalid auth URI: missing callbackUrl parameter');
return;
}
if (state == null || state.isEmpty) {
dPrint('Invalid auth URI: missing state parameter');
return;
}
dPrint('Auth request - callbackUrl: $callbackUrl, state: $state');
_showAuthDialog(callbackUrl, state, nonce);
}
}
/// Show auth dialog
void _showAuthDialog(String callbackUrl, String state, String? nonce) {
if (_context == null || !_context!.mounted) {
dPrint('Cannot show auth dialog: context not available');
return;
}
showDialog(
context: _context!,
builder: (context) => AuthPage(callbackUrl: callbackUrl, stateParameter: state, nonce: nonce),
);
}
/// Dispose the handler
void dispose() {
_linkSubscription?.cancel();
_linkSubscription = null;
_context = null;
_lastHandledUri = null;
_lastHandledTime = null;
}
/// Update context (useful when switching screens)
void updateContext(BuildContext context) {
_context = context;
}
}

View File

@@ -402,6 +402,9 @@ class MessageLookup extends MessageLookupByLibrary {
"Loading images...",
),
"app_common_network_error": m1,
"app_common_recommended": MessageLookupByLibrary.simpleMessage(
"Recommended",
),
"app_common_tip": MessageLookupByLibrary.simpleMessage("Tip"),
"app_common_tip_cancel": MessageLookupByLibrary.simpleMessage("Cancel"),
"app_common_tip_confirm": MessageLookupByLibrary.simpleMessage("Confirm"),
@@ -2166,6 +2169,9 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage(
"Hosts Acceleration (Experimental)",
),
"tools_action_info_cleaning": MessageLookupByLibrary.simpleMessage(
"Cleaning...",
),
"tools_action_info_cleanup_complete": MessageLookupByLibrary.simpleMessage(
"Cleanup complete, please complete one installation / game launch operation.",
),
@@ -2277,6 +2283,9 @@ class MessageLookup extends MessageLookupByLibrary {
"tools_action_rsi_launcher_log_fix": MessageLookupByLibrary.simpleMessage(
"RSI Launcher Log Fix",
),
"tools_action_start_cleaning": MessageLookupByLibrary.simpleMessage(
"Start Cleaning",
),
"tools_action_switch_graphics_renderer":
MessageLookupByLibrary.simpleMessage("Switch DirectX/Vulkan Renderer"),
"tools_action_switch_graphics_renderer_info": m104,
@@ -2406,6 +2415,19 @@ class MessageLookup extends MessageLookupByLibrary {
"tools_rsi_launcher_enhance_working_msg2": MessageLookupByLibrary.simpleMessage(
"Installing patch, this will take some time depending on your computer\'s performance...",
),
"tools_shader_clean_all": MessageLookupByLibrary.simpleMessage("Clean All"),
"tools_shader_clean_all_desc": MessageLookupByLibrary.simpleMessage(
"Clean all version caches (only keep GraphicsSettings). Suitable for graphics anomalies or post-update cleaning.",
),
"tools_shader_clean_dialog_title": MessageLookupByLibrary.simpleMessage(
"Clear Shader Cache",
),
"tools_shader_clean_keep_latest": MessageLookupByLibrary.simpleMessage(
"Keep Latest",
),
"tools_shader_clean_keep_latest_desc": MessageLookupByLibrary.simpleMessage(
"Suitable for use after game updates and completing the first launch. Keep all files in the latest version, clean other old versions (only keep GraphicsSettings).",
),
"tools_unp4k_action_cancel_multi_select":
MessageLookupByLibrary.simpleMessage("Cancel Multi-Select"),
"tools_unp4k_action_deselect_all": MessageLookupByLibrary.simpleMessage(

View File

@@ -349,6 +349,7 @@ class MessageLookup extends MessageLookupByLibrary {
"画像を読み込み中...",
),
"app_common_network_error": m1,
"app_common_recommended": MessageLookupByLibrary.simpleMessage("推奨"),
"app_common_tip": MessageLookupByLibrary.simpleMessage("ヒント"),
"app_common_tip_cancel": MessageLookupByLibrary.simpleMessage("キャンセル"),
"app_common_tip_confirm": MessageLookupByLibrary.simpleMessage("確認"),
@@ -1844,6 +1845,9 @@ class MessageLookup extends MessageLookupByLibrary {
),
"tools_action_hosts_acceleration_experimental":
MessageLookupByLibrary.simpleMessage("Hosts加速実験的"),
"tools_action_info_cleaning": MessageLookupByLibrary.simpleMessage(
"クリーニング中...",
),
"tools_action_info_cleanup_complete": MessageLookupByLibrary.simpleMessage(
"クリーンアップが完了しました。インストールまたはゲーム起動操作を完了してください。",
),
@@ -1945,6 +1949,9 @@ class MessageLookup extends MessageLookupByLibrary {
"tools_action_rsi_launcher_log_fix": MessageLookupByLibrary.simpleMessage(
"RSI Launcher ログ修復",
),
"tools_action_start_cleaning": MessageLookupByLibrary.simpleMessage(
"クリーニング開始",
),
"tools_action_unp4k": MessageLookupByLibrary.simpleMessage("P4Kビューア"),
"tools_action_unp4k_info": MessageLookupByLibrary.simpleMessage(
"Star Citizen p4kファイルを解凍",
@@ -2036,6 +2043,19 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage(
"パッチをインストール中、これはコンピュータのパフォーマンスによって時間がかかります...",
),
"tools_shader_clean_all": MessageLookupByLibrary.simpleMessage("すべてクリア"),
"tools_shader_clean_all_desc": MessageLookupByLibrary.simpleMessage(
"すべてのバージョンのキャッシュをクリアGraphicsSettingsのみ保持。グラフィック異常またはアップデート後のクリーニングに適しています。",
),
"tools_shader_clean_dialog_title": MessageLookupByLibrary.simpleMessage(
"シェーダーキャッシュをクリア",
),
"tools_shader_clean_keep_latest": MessageLookupByLibrary.simpleMessage(
"最新を保持",
),
"tools_shader_clean_keep_latest_desc": MessageLookupByLibrary.simpleMessage(
"ゲームアップデート後と初回起動完了後の使用に適しています。最新バージョンのすべてのファイルを保持し、他の古いバージョンをクリアGraphicsSettingsのみ保持",
),
"tools_unp4k_missing_runtime": MessageLookupByLibrary.simpleMessage(
"ランタイムがありません",
),

View File

@@ -372,6 +372,9 @@ class MessageLookup extends MessageLookupByLibrary {
"Загрузка изображений...",
),
"app_common_network_error": m1,
"app_common_recommended": MessageLookupByLibrary.simpleMessage(
"Рекомендуется",
),
"app_common_tip": MessageLookupByLibrary.simpleMessage("Подсказка"),
"app_common_tip_cancel": MessageLookupByLibrary.simpleMessage("Отмена"),
"app_common_tip_confirm": MessageLookupByLibrary.simpleMessage(
@@ -2078,6 +2081,9 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage(
"Ускорение через Hosts (экспериментально)",
),
"tools_action_info_cleaning": MessageLookupByLibrary.simpleMessage(
"Очистка...",
),
"tools_action_info_cleanup_complete": MessageLookupByLibrary.simpleMessage(
"Очистка завершена, пожалуйста, выполните установку или запуск игры.",
),
@@ -2190,6 +2196,9 @@ class MessageLookup extends MessageLookupByLibrary {
"tools_action_rsi_launcher_log_fix": MessageLookupByLibrary.simpleMessage(
"Исправление лог-файла RSI Launcher",
),
"tools_action_start_cleaning": MessageLookupByLibrary.simpleMessage(
"Начать очистку",
),
"tools_action_unp4k": MessageLookupByLibrary.simpleMessage(
"Просмотрщик P4K",
),
@@ -2293,6 +2302,21 @@ class MessageLookup extends MessageLookupByLibrary {
"tools_rsi_launcher_enhance_working_msg2": MessageLookupByLibrary.simpleMessage(
"Установка патча, это займет некоторое время в зависимости от производительности вашего компьютера...",
),
"tools_shader_clean_all": MessageLookupByLibrary.simpleMessage(
"Очистить всё",
),
"tools_shader_clean_all_desc": MessageLookupByLibrary.simpleMessage(
"Очистить кеш всех версий (сохранить только GraphicsSettings). Подходит для графических аномалий или очистки после обновления.",
),
"tools_shader_clean_dialog_title": MessageLookupByLibrary.simpleMessage(
"Очистить кеш шейдеров",
),
"tools_shader_clean_keep_latest": MessageLookupByLibrary.simpleMessage(
"Сохранить последнее",
),
"tools_shader_clean_keep_latest_desc": MessageLookupByLibrary.simpleMessage(
"Подходит для использования после обновления игры и завершения первого запуска. Сохранить все файлы последней версии, очистить другие старые версии (сохранить только GraphicsSettings).",
),
"tools_unp4k_missing_runtime": MessageLookupByLibrary.simpleMessage(
"Отсутствует среда выполнения",
),

View File

@@ -363,6 +363,7 @@ class MessageLookup extends MessageLookupByLibrary {
"加载图片...",
),
"app_common_network_error": m1,
"app_common_recommended": MessageLookupByLibrary.simpleMessage("推荐"),
"app_common_tip": MessageLookupByLibrary.simpleMessage("提示"),
"app_common_tip_cancel": MessageLookupByLibrary.simpleMessage("取消"),
"app_common_tip_confirm": MessageLookupByLibrary.simpleMessage("确认"),
@@ -1834,6 +1835,9 @@ class MessageLookup extends MessageLookupByLibrary {
),
"tools_action_hosts_acceleration_experimental":
MessageLookupByLibrary.simpleMessage("Hosts 加速(实验性)"),
"tools_action_info_cleaning": MessageLookupByLibrary.simpleMessage(
"正在清理...",
),
"tools_action_info_cleanup_complete": MessageLookupByLibrary.simpleMessage(
"清理完毕,请完成一次安装 / 游戏启动 操作。",
),
@@ -1929,6 +1933,7 @@ class MessageLookup extends MessageLookupByLibrary {
"tools_action_rsi_launcher_log_fix": MessageLookupByLibrary.simpleMessage(
"RSI Launcher Log 修复",
),
"tools_action_start_cleaning": MessageLookupByLibrary.simpleMessage("开始清理"),
"tools_action_switch_graphics_renderer":
MessageLookupByLibrary.simpleMessage("切换 DirectX/Vulkan 渲染器"),
"tools_action_switch_graphics_renderer_info": m104,
@@ -2044,6 +2049,19 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("生成补丁 ..."),
"tools_rsi_launcher_enhance_working_msg2":
MessageLookupByLibrary.simpleMessage("安装补丁,这需要一点时间,取决于您的计算机性能 ..."),
"tools_shader_clean_all": MessageLookupByLibrary.simpleMessage("全部清理"),
"tools_shader_clean_all_desc": MessageLookupByLibrary.simpleMessage(
"清理所有版本的缓存。适合画面异常或更新后清理。",
),
"tools_shader_clean_dialog_title": MessageLookupByLibrary.simpleMessage(
"清理着色器缓存",
),
"tools_shader_clean_keep_latest": MessageLookupByLibrary.simpleMessage(
"保留最新",
),
"tools_shader_clean_keep_latest_desc": MessageLookupByLibrary.simpleMessage(
"适合游戏更新并完成首次启动后使用。保留最新版本的所有文件,清理其他旧版本。",
),
"tools_unp4k_action_cancel_multi_select":
MessageLookupByLibrary.simpleMessage("取消多选"),
"tools_unp4k_action_deselect_all": MessageLookupByLibrary.simpleMessage(

View File

@@ -334,6 +334,7 @@ class MessageLookup extends MessageLookupByLibrary {
"載入圖片...",
),
"app_common_network_error": m1,
"app_common_recommended": MessageLookupByLibrary.simpleMessage("推薦"),
"app_common_tip": MessageLookupByLibrary.simpleMessage("提示"),
"app_common_tip_cancel": MessageLookupByLibrary.simpleMessage("取消"),
"app_common_tip_confirm": MessageLookupByLibrary.simpleMessage("確認"),
@@ -1729,6 +1730,9 @@ class MessageLookup extends MessageLookupByLibrary {
),
"tools_action_hosts_acceleration_experimental":
MessageLookupByLibrary.simpleMessage("Hosts 加速Beta"),
"tools_action_info_cleaning": MessageLookupByLibrary.simpleMessage(
"正在清理...",
),
"tools_action_info_cleanup_complete": MessageLookupByLibrary.simpleMessage(
"清理完畢,請完成一次安裝 / 遊戲啟動 操作。",
),
@@ -1822,6 +1826,7 @@ class MessageLookup extends MessageLookupByLibrary {
"tools_action_rsi_launcher_log_fix": MessageLookupByLibrary.simpleMessage(
"啟動器日誌修復",
),
"tools_action_start_cleaning": MessageLookupByLibrary.simpleMessage("開始清理"),
"tools_action_unp4k": MessageLookupByLibrary.simpleMessage("P4K 查看器"),
"tools_action_unp4k_info": MessageLookupByLibrary.simpleMessage(
"解包星際公民 p4k 文件",
@@ -1907,6 +1912,19 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("生成補丁 ..."),
"tools_rsi_launcher_enhance_working_msg2":
MessageLookupByLibrary.simpleMessage("安裝補丁,這需要一點時間,取決於您的電腦性能 ..."),
"tools_shader_clean_all": MessageLookupByLibrary.simpleMessage("全部清理"),
"tools_shader_clean_all_desc": MessageLookupByLibrary.simpleMessage(
"清理所有版本的快取(僅保留 GraphicsSettings。適合畫面異常或更新後清理。",
),
"tools_shader_clean_dialog_title": MessageLookupByLibrary.simpleMessage(
"清理著色器緩存",
),
"tools_shader_clean_keep_latest": MessageLookupByLibrary.simpleMessage(
"保留最新",
),
"tools_shader_clean_keep_latest_desc": MessageLookupByLibrary.simpleMessage(
"適合遊戲更新並完成首次啟動後使用。保留最新版本的所有檔案,清理其他舊版本(僅保留 GraphicsSettings",
),
"tools_unp4k_missing_runtime": MessageLookupByLibrary.simpleMessage(
"缺少運行庫",
),

View File

@@ -9546,6 +9546,86 @@ class S {
args: [year],
);
}
/// `Clear Shader Cache`
String get tools_shader_clean_dialog_title {
return Intl.message(
'Clear Shader Cache',
name: 'tools_shader_clean_dialog_title',
desc: '',
args: [],
);
}
/// `Keep Latest`
String get tools_shader_clean_keep_latest {
return Intl.message(
'Keep Latest',
name: 'tools_shader_clean_keep_latest',
desc: '',
args: [],
);
}
/// `Clean All`
String get tools_shader_clean_all {
return Intl.message(
'Clean All',
name: 'tools_shader_clean_all',
desc: '',
args: [],
);
}
/// `Suitable for use after game updates and completing the first launch. Keep all files in the latest version, clean other old versions (only keep GraphicsSettings).`
String get tools_shader_clean_keep_latest_desc {
return Intl.message(
'Suitable for use after game updates and completing the first launch. Keep all files in the latest version, clean other old versions (only keep GraphicsSettings).',
name: 'tools_shader_clean_keep_latest_desc',
desc: '',
args: [],
);
}
/// `Clean all version caches (only keep GraphicsSettings). Suitable for graphics anomalies or post-update cleaning.`
String get tools_shader_clean_all_desc {
return Intl.message(
'Clean all version caches (only keep GraphicsSettings). Suitable for graphics anomalies or post-update cleaning.',
name: 'tools_shader_clean_all_desc',
desc: '',
args: [],
);
}
/// `Cleaning...`
String get tools_action_info_cleaning {
return Intl.message(
'Cleaning...',
name: 'tools_action_info_cleaning',
desc: '',
args: [],
);
}
/// `Start Cleaning`
String get tools_action_start_cleaning {
return Intl.message(
'Start Cleaning',
name: 'tools_action_start_cleaning',
desc: '',
args: [],
);
}
/// `Recommended`
String get app_common_recommended {
return Intl.message(
'Recommended',
name: 'app_common_recommended',
desc: '',
args: [],
);
}
}
class AppLocalizationDelegate extends LocalizationsDelegate<S> {

View File

@@ -8,7 +8,7 @@
// ignore_for_file: constant_identifier_names
// ignore_for_file: curly_braces_in_flow_control_structures
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
import 'dart:core' as $core;
@@ -17,6 +17,258 @@ import 'package:protobuf/protobuf.dart' as $pb;
export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions;
/// 生成 OIDC 授权码请求
class GenerateOIDCAuthCodeRequest extends $pb.GeneratedMessage {
factory GenerateOIDCAuthCodeRequest({
$core.String? nonce,
$core.String? redirectUri,
}) {
final result = create();
if (nonce != null) result.nonce = nonce;
if (redirectUri != null) result.redirectUri = redirectUri;
return result;
}
GenerateOIDCAuthCodeRequest._();
factory GenerateOIDCAuthCodeRequest.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory GenerateOIDCAuthCodeRequest.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'GenerateOIDCAuthCodeRequest',
package: const $pb.PackageName(_omitMessageNames ? '' : 'auth'),
createEmptyInstance: create)
..aOS(1, _omitFieldNames ? '' : 'nonce')
..aOS(2, _omitFieldNames ? '' : 'redirectUri')
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
GenerateOIDCAuthCodeRequest clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
GenerateOIDCAuthCodeRequest copyWith(
void Function(GenerateOIDCAuthCodeRequest) updates) =>
super.copyWith(
(message) => updates(message as GenerateOIDCAuthCodeRequest))
as GenerateOIDCAuthCodeRequest;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static GenerateOIDCAuthCodeRequest create() =>
GenerateOIDCAuthCodeRequest._();
@$core.override
GenerateOIDCAuthCodeRequest createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static GenerateOIDCAuthCodeRequest getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<GenerateOIDCAuthCodeRequest>(create);
static GenerateOIDCAuthCodeRequest? _defaultInstance;
@$pb.TagNumber(1)
$core.String get nonce => $_getSZ(0);
@$pb.TagNumber(1)
set nonce($core.String value) => $_setString(0, value);
@$pb.TagNumber(1)
$core.bool hasNonce() => $_has(0);
@$pb.TagNumber(1)
void clearNonce() => $_clearField(1);
@$pb.TagNumber(2)
$core.String get redirectUri => $_getSZ(1);
@$pb.TagNumber(2)
set redirectUri($core.String value) => $_setString(1, value);
@$pb.TagNumber(2)
$core.bool hasRedirectUri() => $_has(1);
@$pb.TagNumber(2)
void clearRedirectUri() => $_clearField(2);
}
/// 生成 OIDC 授权码响应
class GenerateOIDCAuthCodeResponse extends $pb.GeneratedMessage {
factory GenerateOIDCAuthCodeResponse({
$core.String? code,
$fixnum.Int64? expiresAt,
}) {
final result = create();
if (code != null) result.code = code;
if (expiresAt != null) result.expiresAt = expiresAt;
return result;
}
GenerateOIDCAuthCodeResponse._();
factory GenerateOIDCAuthCodeResponse.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory GenerateOIDCAuthCodeResponse.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'GenerateOIDCAuthCodeResponse',
package: const $pb.PackageName(_omitMessageNames ? '' : 'auth'),
createEmptyInstance: create)
..aOS(1, _omitFieldNames ? '' : 'code')
..aInt64(2, _omitFieldNames ? '' : 'expiresAt')
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
GenerateOIDCAuthCodeResponse clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
GenerateOIDCAuthCodeResponse copyWith(
void Function(GenerateOIDCAuthCodeResponse) updates) =>
super.copyWith(
(message) => updates(message as GenerateOIDCAuthCodeResponse))
as GenerateOIDCAuthCodeResponse;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static GenerateOIDCAuthCodeResponse create() =>
GenerateOIDCAuthCodeResponse._();
@$core.override
GenerateOIDCAuthCodeResponse createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static GenerateOIDCAuthCodeResponse getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<GenerateOIDCAuthCodeResponse>(create);
static GenerateOIDCAuthCodeResponse? _defaultInstance;
@$pb.TagNumber(1)
$core.String get code => $_getSZ(0);
@$pb.TagNumber(1)
set code($core.String value) => $_setString(0, value);
@$pb.TagNumber(1)
$core.bool hasCode() => $_has(0);
@$pb.TagNumber(1)
void clearCode() => $_clearField(1);
@$pb.TagNumber(2)
$fixnum.Int64 get expiresAt => $_getI64(1);
@$pb.TagNumber(2)
set expiresAt($fixnum.Int64 value) => $_setInt64(1, value);
@$pb.TagNumber(2)
$core.bool hasExpiresAt() => $_has(1);
@$pb.TagNumber(2)
void clearExpiresAt() => $_clearField(2);
}
/// 刷新用户资料请求
class RefreshUserProfileRequest extends $pb.GeneratedMessage {
factory RefreshUserProfileRequest() => create();
RefreshUserProfileRequest._();
factory RefreshUserProfileRequest.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory RefreshUserProfileRequest.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'RefreshUserProfileRequest',
package: const $pb.PackageName(_omitMessageNames ? '' : 'auth'),
createEmptyInstance: create)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
RefreshUserProfileRequest clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
RefreshUserProfileRequest copyWith(
void Function(RefreshUserProfileRequest) updates) =>
super.copyWith((message) => updates(message as RefreshUserProfileRequest))
as RefreshUserProfileRequest;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static RefreshUserProfileRequest create() => RefreshUserProfileRequest._();
@$core.override
RefreshUserProfileRequest createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static RefreshUserProfileRequest getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<RefreshUserProfileRequest>(create);
static RefreshUserProfileRequest? _defaultInstance;
}
/// 刷新用户资料响应
class RefreshUserProfileResponse extends $pb.GeneratedMessage {
factory RefreshUserProfileResponse({
$core.bool? success,
GameUserInfo? userInfo,
}) {
final result = create();
if (success != null) result.success = success;
if (userInfo != null) result.userInfo = userInfo;
return result;
}
RefreshUserProfileResponse._();
factory RefreshUserProfileResponse.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory RefreshUserProfileResponse.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'RefreshUserProfileResponse',
package: const $pb.PackageName(_omitMessageNames ? '' : 'auth'),
createEmptyInstance: create)
..aOB(1, _omitFieldNames ? '' : 'success')
..aOM<GameUserInfo>(2, _omitFieldNames ? '' : 'userInfo',
subBuilder: GameUserInfo.create)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
RefreshUserProfileResponse clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
RefreshUserProfileResponse copyWith(
void Function(RefreshUserProfileResponse) updates) =>
super.copyWith(
(message) => updates(message as RefreshUserProfileResponse))
as RefreshUserProfileResponse;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static RefreshUserProfileResponse create() => RefreshUserProfileResponse._();
@$core.override
RefreshUserProfileResponse createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static RefreshUserProfileResponse getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<RefreshUserProfileResponse>(create);
static RefreshUserProfileResponse? _defaultInstance;
@$pb.TagNumber(1)
$core.bool get success => $_getBF(0);
@$pb.TagNumber(1)
set success($core.bool value) => $_setBool(0, value);
@$pb.TagNumber(1)
$core.bool hasSuccess() => $_has(0);
@$pb.TagNumber(1)
void clearSuccess() => $_clearField(1);
@$pb.TagNumber(2)
GameUserInfo get userInfo => $_getN(1);
@$pb.TagNumber(2)
set userInfo(GameUserInfo value) => $_setField(2, value);
@$pb.TagNumber(2)
$core.bool hasUserInfo() => $_has(1);
@$pb.TagNumber(2)
void clearUserInfo() => $_clearField(2);
@$pb.TagNumber(2)
GameUserInfo ensureUserInfo() => $_ensure(1);
}
/// 服务状态请求
class StatusRequest extends $pb.GeneratedMessage {
factory StatusRequest() => create();
@@ -725,6 +977,441 @@ class UnregisterResponse extends $pb.GeneratedMessage {
void clearSuccess() => $_clearField(1);
}
/// 验证 token 请求
class ValidateTokenRequest extends $pb.GeneratedMessage {
factory ValidateTokenRequest({
$core.String? token,
}) {
final result = create();
if (token != null) result.token = token;
return result;
}
ValidateTokenRequest._();
factory ValidateTokenRequest.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory ValidateTokenRequest.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'ValidateTokenRequest',
package: const $pb.PackageName(_omitMessageNames ? '' : 'auth'),
createEmptyInstance: create)
..aOS(1, _omitFieldNames ? '' : 'token')
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
ValidateTokenRequest clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
ValidateTokenRequest copyWith(void Function(ValidateTokenRequest) updates) =>
super.copyWith((message) => updates(message as ValidateTokenRequest))
as ValidateTokenRequest;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static ValidateTokenRequest create() => ValidateTokenRequest._();
@$core.override
ValidateTokenRequest createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static ValidateTokenRequest getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<ValidateTokenRequest>(create);
static ValidateTokenRequest? _defaultInstance;
@$pb.TagNumber(1)
$core.String get token => $_getSZ(0);
@$pb.TagNumber(1)
set token($core.String value) => $_setString(0, value);
@$pb.TagNumber(1)
$core.bool hasToken() => $_has(0);
@$pb.TagNumber(1)
void clearToken() => $_clearField(1);
}
/// 验证 token 响应
class ValidateTokenResponse extends $pb.GeneratedMessage {
factory ValidateTokenResponse({
$core.bool? valid,
$core.String? domain,
$fixnum.Int64? issuedAt,
$fixnum.Int64? expiresAt,
$core.String? errorMessage,
}) {
final result = create();
if (valid != null) result.valid = valid;
if (domain != null) result.domain = domain;
if (issuedAt != null) result.issuedAt = issuedAt;
if (expiresAt != null) result.expiresAt = expiresAt;
if (errorMessage != null) result.errorMessage = errorMessage;
return result;
}
ValidateTokenResponse._();
factory ValidateTokenResponse.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory ValidateTokenResponse.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'ValidateTokenResponse',
package: const $pb.PackageName(_omitMessageNames ? '' : 'auth'),
createEmptyInstance: create)
..aOB(1, _omitFieldNames ? '' : 'valid')
..aOS(2, _omitFieldNames ? '' : 'domain')
..aInt64(3, _omitFieldNames ? '' : 'issuedAt')
..aInt64(4, _omitFieldNames ? '' : 'expiresAt')
..aOS(5, _omitFieldNames ? '' : 'errorMessage')
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
ValidateTokenResponse clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
ValidateTokenResponse copyWith(
void Function(ValidateTokenResponse) updates) =>
super.copyWith((message) => updates(message as ValidateTokenResponse))
as ValidateTokenResponse;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static ValidateTokenResponse create() => ValidateTokenResponse._();
@$core.override
ValidateTokenResponse createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static ValidateTokenResponse getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<ValidateTokenResponse>(create);
static ValidateTokenResponse? _defaultInstance;
@$pb.TagNumber(1)
$core.bool get valid => $_getBF(0);
@$pb.TagNumber(1)
set valid($core.bool value) => $_setBool(0, value);
@$pb.TagNumber(1)
$core.bool hasValid() => $_has(0);
@$pb.TagNumber(1)
void clearValid() => $_clearField(1);
@$pb.TagNumber(2)
$core.String get domain => $_getSZ(1);
@$pb.TagNumber(2)
set domain($core.String value) => $_setString(1, value);
@$pb.TagNumber(2)
$core.bool hasDomain() => $_has(1);
@$pb.TagNumber(2)
void clearDomain() => $_clearField(2);
@$pb.TagNumber(3)
$fixnum.Int64 get issuedAt => $_getI64(2);
@$pb.TagNumber(3)
set issuedAt($fixnum.Int64 value) => $_setInt64(2, value);
@$pb.TagNumber(3)
$core.bool hasIssuedAt() => $_has(2);
@$pb.TagNumber(3)
void clearIssuedAt() => $_clearField(3);
@$pb.TagNumber(4)
$fixnum.Int64 get expiresAt => $_getI64(3);
@$pb.TagNumber(4)
set expiresAt($fixnum.Int64 value) => $_setInt64(3, value);
@$pb.TagNumber(4)
$core.bool hasExpiresAt() => $_has(3);
@$pb.TagNumber(4)
void clearExpiresAt() => $_clearField(4);
@$pb.TagNumber(5)
$core.String get errorMessage => $_getSZ(4);
@$pb.TagNumber(5)
set errorMessage($core.String value) => $_setString(4, value);
@$pb.TagNumber(5)
$core.bool hasErrorMessage() => $_has(4);
@$pb.TagNumber(5)
void clearErrorMessage() => $_clearField(5);
}
/// 获取公钥请求
class GetPublicKeyRequest extends $pb.GeneratedMessage {
factory GetPublicKeyRequest() => create();
GetPublicKeyRequest._();
factory GetPublicKeyRequest.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory GetPublicKeyRequest.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'GetPublicKeyRequest',
package: const $pb.PackageName(_omitMessageNames ? '' : 'auth'),
createEmptyInstance: create)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
GetPublicKeyRequest clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
GetPublicKeyRequest copyWith(void Function(GetPublicKeyRequest) updates) =>
super.copyWith((message) => updates(message as GetPublicKeyRequest))
as GetPublicKeyRequest;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static GetPublicKeyRequest create() => GetPublicKeyRequest._();
@$core.override
GetPublicKeyRequest createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static GetPublicKeyRequest getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<GetPublicKeyRequest>(create);
static GetPublicKeyRequest? _defaultInstance;
}
/// 获取公钥响应
class GetPublicKeyResponse extends $pb.GeneratedMessage {
factory GetPublicKeyResponse({
$core.String? publicKeyPem,
$core.String? keyId,
$core.String? algorithm,
}) {
final result = create();
if (publicKeyPem != null) result.publicKeyPem = publicKeyPem;
if (keyId != null) result.keyId = keyId;
if (algorithm != null) result.algorithm = algorithm;
return result;
}
GetPublicKeyResponse._();
factory GetPublicKeyResponse.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory GetPublicKeyResponse.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'GetPublicKeyResponse',
package: const $pb.PackageName(_omitMessageNames ? '' : 'auth'),
createEmptyInstance: create)
..aOS(1, _omitFieldNames ? '' : 'publicKeyPem')
..aOS(2, _omitFieldNames ? '' : 'keyId')
..aOS(3, _omitFieldNames ? '' : 'algorithm')
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
GetPublicKeyResponse clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
GetPublicKeyResponse copyWith(void Function(GetPublicKeyResponse) updates) =>
super.copyWith((message) => updates(message as GetPublicKeyResponse))
as GetPublicKeyResponse;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static GetPublicKeyResponse create() => GetPublicKeyResponse._();
@$core.override
GetPublicKeyResponse createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static GetPublicKeyResponse getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<GetPublicKeyResponse>(create);
static GetPublicKeyResponse? _defaultInstance;
@$pb.TagNumber(1)
$core.String get publicKeyPem => $_getSZ(0);
@$pb.TagNumber(1)
set publicKeyPem($core.String value) => $_setString(0, value);
@$pb.TagNumber(1)
$core.bool hasPublicKeyPem() => $_has(0);
@$pb.TagNumber(1)
void clearPublicKeyPem() => $_clearField(1);
@$pb.TagNumber(2)
$core.String get keyId => $_getSZ(1);
@$pb.TagNumber(2)
set keyId($core.String value) => $_setString(1, value);
@$pb.TagNumber(2)
$core.bool hasKeyId() => $_has(1);
@$pb.TagNumber(2)
void clearKeyId() => $_clearField(2);
@$pb.TagNumber(3)
$core.String get algorithm => $_getSZ(2);
@$pb.TagNumber(3)
set algorithm($core.String value) => $_setString(2, value);
@$pb.TagNumber(3)
$core.bool hasAlgorithm() => $_has(2);
@$pb.TagNumber(3)
void clearAlgorithm() => $_clearField(3);
}
/// JWT 域名信息
class JWTDomainInfo extends $pb.GeneratedMessage {
factory JWTDomainInfo({
$core.String? domain,
$core.String? name,
}) {
final result = create();
if (domain != null) result.domain = domain;
if (name != null) result.name = name;
return result;
}
JWTDomainInfo._();
factory JWTDomainInfo.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory JWTDomainInfo.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'JWTDomainInfo',
package: const $pb.PackageName(_omitMessageNames ? '' : 'auth'),
createEmptyInstance: create)
..aOS(1, _omitFieldNames ? '' : 'domain')
..aOS(2, _omitFieldNames ? '' : 'name')
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
JWTDomainInfo clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
JWTDomainInfo copyWith(void Function(JWTDomainInfo) updates) =>
super.copyWith((message) => updates(message as JWTDomainInfo))
as JWTDomainInfo;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static JWTDomainInfo create() => JWTDomainInfo._();
@$core.override
JWTDomainInfo createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static JWTDomainInfo getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<JWTDomainInfo>(create);
static JWTDomainInfo? _defaultInstance;
@$pb.TagNumber(1)
$core.String get domain => $_getSZ(0);
@$pb.TagNumber(1)
set domain($core.String value) => $_setString(0, value);
@$pb.TagNumber(1)
$core.bool hasDomain() => $_has(0);
@$pb.TagNumber(1)
void clearDomain() => $_clearField(1);
@$pb.TagNumber(2)
$core.String get name => $_getSZ(1);
@$pb.TagNumber(2)
set name($core.String value) => $_setString(1, value);
@$pb.TagNumber(2)
$core.bool hasName() => $_has(1);
@$pb.TagNumber(2)
void clearName() => $_clearField(2);
}
/// 获取 JWT 域名列表请求
class GetJWTDomainListRequest extends $pb.GeneratedMessage {
factory GetJWTDomainListRequest() => create();
GetJWTDomainListRequest._();
factory GetJWTDomainListRequest.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory GetJWTDomainListRequest.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'GetJWTDomainListRequest',
package: const $pb.PackageName(_omitMessageNames ? '' : 'auth'),
createEmptyInstance: create)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
GetJWTDomainListRequest clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
GetJWTDomainListRequest copyWith(
void Function(GetJWTDomainListRequest) updates) =>
super.copyWith((message) => updates(message as GetJWTDomainListRequest))
as GetJWTDomainListRequest;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static GetJWTDomainListRequest create() => GetJWTDomainListRequest._();
@$core.override
GetJWTDomainListRequest createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static GetJWTDomainListRequest getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<GetJWTDomainListRequest>(create);
static GetJWTDomainListRequest? _defaultInstance;
}
/// 获取 JWT 域名列表响应
class GetJWTDomainListResponse extends $pb.GeneratedMessage {
factory GetJWTDomainListResponse({
$core.Iterable<JWTDomainInfo>? domains,
}) {
final result = create();
if (domains != null) result.domains.addAll(domains);
return result;
}
GetJWTDomainListResponse._();
factory GetJWTDomainListResponse.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory GetJWTDomainListResponse.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'GetJWTDomainListResponse',
package: const $pb.PackageName(_omitMessageNames ? '' : 'auth'),
createEmptyInstance: create)
..pPM<JWTDomainInfo>(1, _omitFieldNames ? '' : 'domains',
subBuilder: JWTDomainInfo.create)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
GetJWTDomainListResponse clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
GetJWTDomainListResponse copyWith(
void Function(GetJWTDomainListResponse) updates) =>
super.copyWith((message) => updates(message as GetJWTDomainListResponse))
as GetJWTDomainListResponse;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static GetJWTDomainListResponse create() => GetJWTDomainListResponse._();
@$core.override
GetJWTDomainListResponse createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static GetJWTDomainListResponse getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<GetJWTDomainListResponse>(create);
static GetJWTDomainListResponse? _defaultInstance;
@$pb.TagNumber(1)
$pb.PbList<JWTDomainInfo> get domains => $_getList(0);
}
const $core.bool _omitFieldNames =
$core.bool.fromEnvironment('protobuf.omit_field_names');
const $core.bool _omitMessageNames =

View File

@@ -8,4 +8,4 @@
// ignore_for_file: constant_identifier_names
// ignore_for_file: curly_braces_in_flow_control_structures
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports

View File

@@ -8,7 +8,7 @@
// ignore_for_file: constant_identifier_names
// ignore_for_file: curly_braces_in_flow_control_structures
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
import 'dart:async' as $async;
import 'dart:core' as $core;
@@ -73,6 +73,45 @@ class AuthServiceClient extends $grpc.Client {
return $createUnaryCall(_$unregister, request, options: options);
}
/// 验证 JWT token
$grpc.ResponseFuture<$0.ValidateTokenResponse> validateToken(
$0.ValidateTokenRequest request, {
$grpc.CallOptions? options,
}) {
return $createUnaryCall(_$validateToken, request, options: options);
}
/// 获取公钥信息
$grpc.ResponseFuture<$0.GetPublicKeyResponse> getPublicKey(
$0.GetPublicKeyRequest request, {
$grpc.CallOptions? options,
}) {
return $createUnaryCall(_$getPublicKey, request, options: options);
}
$grpc.ResponseFuture<$0.GetJWTDomainListResponse> getJWTDomainList(
$0.GetJWTDomainListRequest request, {
$grpc.CallOptions? options,
}) {
return $createUnaryCall(_$getJWTDomainList, request, options: options);
}
/// 刷新用户资料(需要认证)
$grpc.ResponseFuture<$0.RefreshUserProfileResponse> refreshUserProfile(
$0.RefreshUserProfileRequest request, {
$grpc.CallOptions? options,
}) {
return $createUnaryCall(_$refreshUserProfile, request, options: options);
}
/// 生成 OIDC 授权码(供客户端 App 使用)
$grpc.ResponseFuture<$0.GenerateOIDCAuthCodeResponse> generateOIDCAuthCode(
$0.GenerateOIDCAuthCodeRequest request, {
$grpc.CallOptions? options,
}) {
return $createUnaryCall(_$generateOIDCAuthCode, request, options: options);
}
// method descriptors
static final _$status =
@@ -99,6 +138,31 @@ class AuthServiceClient extends $grpc.Client {
'/auth.AuthService/Unregister',
($0.UnregisterRequest value) => value.writeToBuffer(),
$0.UnregisterResponse.fromBuffer);
static final _$validateToken =
$grpc.ClientMethod<$0.ValidateTokenRequest, $0.ValidateTokenResponse>(
'/auth.AuthService/ValidateToken',
($0.ValidateTokenRequest value) => value.writeToBuffer(),
$0.ValidateTokenResponse.fromBuffer);
static final _$getPublicKey =
$grpc.ClientMethod<$0.GetPublicKeyRequest, $0.GetPublicKeyResponse>(
'/auth.AuthService/GetPublicKey',
($0.GetPublicKeyRequest value) => value.writeToBuffer(),
$0.GetPublicKeyResponse.fromBuffer);
static final _$getJWTDomainList = $grpc.ClientMethod<
$0.GetJWTDomainListRequest, $0.GetJWTDomainListResponse>(
'/auth.AuthService/GetJWTDomainList',
($0.GetJWTDomainListRequest value) => value.writeToBuffer(),
$0.GetJWTDomainListResponse.fromBuffer);
static final _$refreshUserProfile = $grpc.ClientMethod<
$0.RefreshUserProfileRequest, $0.RefreshUserProfileResponse>(
'/auth.AuthService/RefreshUserProfile',
($0.RefreshUserProfileRequest value) => value.writeToBuffer(),
$0.RefreshUserProfileResponse.fromBuffer);
static final _$generateOIDCAuthCode = $grpc.ClientMethod<
$0.GenerateOIDCAuthCodeRequest, $0.GenerateOIDCAuthCodeResponse>(
'/auth.AuthService/GenerateOIDCAuthCode',
($0.GenerateOIDCAuthCodeRequest value) => value.writeToBuffer(),
$0.GenerateOIDCAuthCodeResponse.fromBuffer);
}
@$pb.GrpcServiceName('auth.AuthService')
@@ -143,6 +207,51 @@ abstract class AuthServiceBase extends $grpc.Service {
false,
($core.List<$core.int> value) => $0.UnregisterRequest.fromBuffer(value),
($0.UnregisterResponse value) => value.writeToBuffer()));
$addMethod(
$grpc.ServiceMethod<$0.ValidateTokenRequest, $0.ValidateTokenResponse>(
'ValidateToken',
validateToken_Pre,
false,
false,
($core.List<$core.int> value) =>
$0.ValidateTokenRequest.fromBuffer(value),
($0.ValidateTokenResponse value) => value.writeToBuffer()));
$addMethod(
$grpc.ServiceMethod<$0.GetPublicKeyRequest, $0.GetPublicKeyResponse>(
'GetPublicKey',
getPublicKey_Pre,
false,
false,
($core.List<$core.int> value) =>
$0.GetPublicKeyRequest.fromBuffer(value),
($0.GetPublicKeyResponse value) => value.writeToBuffer()));
$addMethod($grpc.ServiceMethod<$0.GetJWTDomainListRequest,
$0.GetJWTDomainListResponse>(
'GetJWTDomainList',
getJWTDomainList_Pre,
false,
false,
($core.List<$core.int> value) =>
$0.GetJWTDomainListRequest.fromBuffer(value),
($0.GetJWTDomainListResponse value) => value.writeToBuffer()));
$addMethod($grpc.ServiceMethod<$0.RefreshUserProfileRequest,
$0.RefreshUserProfileResponse>(
'RefreshUserProfile',
refreshUserProfile_Pre,
false,
false,
($core.List<$core.int> value) =>
$0.RefreshUserProfileRequest.fromBuffer(value),
($0.RefreshUserProfileResponse value) => value.writeToBuffer()));
$addMethod($grpc.ServiceMethod<$0.GenerateOIDCAuthCodeRequest,
$0.GenerateOIDCAuthCodeResponse>(
'GenerateOIDCAuthCode',
generateOIDCAuthCode_Pre,
false,
false,
($core.List<$core.int> value) =>
$0.GenerateOIDCAuthCodeRequest.fromBuffer(value),
($0.GenerateOIDCAuthCodeResponse value) => value.writeToBuffer()));
}
$async.Future<$0.StatusResponse> status_Pre(
@@ -184,4 +293,49 @@ abstract class AuthServiceBase extends $grpc.Service {
$async.Future<$0.UnregisterResponse> unregister(
$grpc.ServiceCall call, $0.UnregisterRequest request);
$async.Future<$0.ValidateTokenResponse> validateToken_Pre(
$grpc.ServiceCall $call,
$async.Future<$0.ValidateTokenRequest> $request) async {
return validateToken($call, await $request);
}
$async.Future<$0.ValidateTokenResponse> validateToken(
$grpc.ServiceCall call, $0.ValidateTokenRequest request);
$async.Future<$0.GetPublicKeyResponse> getPublicKey_Pre(
$grpc.ServiceCall $call,
$async.Future<$0.GetPublicKeyRequest> $request) async {
return getPublicKey($call, await $request);
}
$async.Future<$0.GetPublicKeyResponse> getPublicKey(
$grpc.ServiceCall call, $0.GetPublicKeyRequest request);
$async.Future<$0.GetJWTDomainListResponse> getJWTDomainList_Pre(
$grpc.ServiceCall $call,
$async.Future<$0.GetJWTDomainListRequest> $request) async {
return getJWTDomainList($call, await $request);
}
$async.Future<$0.GetJWTDomainListResponse> getJWTDomainList(
$grpc.ServiceCall call, $0.GetJWTDomainListRequest request);
$async.Future<$0.RefreshUserProfileResponse> refreshUserProfile_Pre(
$grpc.ServiceCall $call,
$async.Future<$0.RefreshUserProfileRequest> $request) async {
return refreshUserProfile($call, await $request);
}
$async.Future<$0.RefreshUserProfileResponse> refreshUserProfile(
$grpc.ServiceCall call, $0.RefreshUserProfileRequest request);
$async.Future<$0.GenerateOIDCAuthCodeResponse> generateOIDCAuthCode_Pre(
$grpc.ServiceCall $call,
$async.Future<$0.GenerateOIDCAuthCodeRequest> $request) async {
return generateOIDCAuthCode($call, await $request);
}
$async.Future<$0.GenerateOIDCAuthCodeResponse> generateOIDCAuthCode(
$grpc.ServiceCall call, $0.GenerateOIDCAuthCodeRequest request);
}

View File

@@ -8,12 +8,74 @@
// ignore_for_file: constant_identifier_names
// ignore_for_file: curly_braces_in_flow_control_structures
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names, unused_import
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
// ignore_for_file: unused_import
import 'dart:convert' as $convert;
import 'dart:core' as $core;
import 'dart:typed_data' as $typed_data;
@$core.Deprecated('Use generateOIDCAuthCodeRequestDescriptor instead')
const GenerateOIDCAuthCodeRequest$json = {
'1': 'GenerateOIDCAuthCodeRequest',
'2': [
{'1': 'nonce', '3': 1, '4': 1, '5': 9, '10': 'nonce'},
{'1': 'redirect_uri', '3': 2, '4': 1, '5': 9, '10': 'redirectUri'},
],
};
/// Descriptor for `GenerateOIDCAuthCodeRequest`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List generateOIDCAuthCodeRequestDescriptor =
$convert.base64Decode(
'ChtHZW5lcmF0ZU9JRENBdXRoQ29kZVJlcXVlc3QSFAoFbm9uY2UYASABKAlSBW5vbmNlEiEKDH'
'JlZGlyZWN0X3VyaRgCIAEoCVILcmVkaXJlY3RVcmk=');
@$core.Deprecated('Use generateOIDCAuthCodeResponseDescriptor instead')
const GenerateOIDCAuthCodeResponse$json = {
'1': 'GenerateOIDCAuthCodeResponse',
'2': [
{'1': 'code', '3': 1, '4': 1, '5': 9, '10': 'code'},
{'1': 'expires_at', '3': 2, '4': 1, '5': 3, '10': 'expiresAt'},
],
};
/// Descriptor for `GenerateOIDCAuthCodeResponse`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List generateOIDCAuthCodeResponseDescriptor =
$convert.base64Decode(
'ChxHZW5lcmF0ZU9JRENBdXRoQ29kZVJlc3BvbnNlEhIKBGNvZGUYASABKAlSBGNvZGUSHQoKZX'
'hwaXJlc19hdBgCIAEoA1IJZXhwaXJlc0F0');
@$core.Deprecated('Use refreshUserProfileRequestDescriptor instead')
const RefreshUserProfileRequest$json = {
'1': 'RefreshUserProfileRequest',
};
/// Descriptor for `RefreshUserProfileRequest`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List refreshUserProfileRequestDescriptor =
$convert.base64Decode('ChlSZWZyZXNoVXNlclByb2ZpbGVSZXF1ZXN0');
@$core.Deprecated('Use refreshUserProfileResponseDescriptor instead')
const RefreshUserProfileResponse$json = {
'1': 'RefreshUserProfileResponse',
'2': [
{'1': 'success', '3': 1, '4': 1, '5': 8, '10': 'success'},
{
'1': 'user_info',
'3': 2,
'4': 1,
'5': 11,
'6': '.auth.GameUserInfo',
'10': 'userInfo'
},
],
};
/// Descriptor for `RefreshUserProfileResponse`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List refreshUserProfileResponseDescriptor =
$convert.base64Decode(
'ChpSZWZyZXNoVXNlclByb2ZpbGVSZXNwb25zZRIYCgdzdWNjZXNzGAEgASgIUgdzdWNjZXNzEi'
'8KCXVzZXJfaW5mbxgCIAEoCzISLmF1dGguR2FtZVVzZXJJbmZvUgh1c2VySW5mbw==');
@$core.Deprecated('Use statusRequestDescriptor instead')
const StatusRequest$json = {
'1': 'StatusRequest',
@@ -186,3 +248,104 @@ const UnregisterResponse$json = {
final $typed_data.Uint8List unregisterResponseDescriptor =
$convert.base64Decode(
'ChJVbnJlZ2lzdGVyUmVzcG9uc2USGAoHc3VjY2VzcxgBIAEoCFIHc3VjY2Vzcw==');
@$core.Deprecated('Use validateTokenRequestDescriptor instead')
const ValidateTokenRequest$json = {
'1': 'ValidateTokenRequest',
'2': [
{'1': 'token', '3': 1, '4': 1, '5': 9, '10': 'token'},
],
};
/// Descriptor for `ValidateTokenRequest`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List validateTokenRequestDescriptor =
$convert.base64Decode(
'ChRWYWxpZGF0ZVRva2VuUmVxdWVzdBIUCgV0b2tlbhgBIAEoCVIFdG9rZW4=');
@$core.Deprecated('Use validateTokenResponseDescriptor instead')
const ValidateTokenResponse$json = {
'1': 'ValidateTokenResponse',
'2': [
{'1': 'valid', '3': 1, '4': 1, '5': 8, '10': 'valid'},
{'1': 'domain', '3': 2, '4': 1, '5': 9, '10': 'domain'},
{'1': 'issued_at', '3': 3, '4': 1, '5': 3, '10': 'issuedAt'},
{'1': 'expires_at', '3': 4, '4': 1, '5': 3, '10': 'expiresAt'},
{'1': 'error_message', '3': 5, '4': 1, '5': 9, '10': 'errorMessage'},
],
};
/// Descriptor for `ValidateTokenResponse`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List validateTokenResponseDescriptor = $convert.base64Decode(
'ChVWYWxpZGF0ZVRva2VuUmVzcG9uc2USFAoFdmFsaWQYASABKAhSBXZhbGlkEhYKBmRvbWFpbh'
'gCIAEoCVIGZG9tYWluEhsKCWlzc3VlZF9hdBgDIAEoA1IIaXNzdWVkQXQSHQoKZXhwaXJlc19h'
'dBgEIAEoA1IJZXhwaXJlc0F0EiMKDWVycm9yX21lc3NhZ2UYBSABKAlSDGVycm9yTWVzc2FnZQ'
'==');
@$core.Deprecated('Use getPublicKeyRequestDescriptor instead')
const GetPublicKeyRequest$json = {
'1': 'GetPublicKeyRequest',
};
/// Descriptor for `GetPublicKeyRequest`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List getPublicKeyRequestDescriptor =
$convert.base64Decode('ChNHZXRQdWJsaWNLZXlSZXF1ZXN0');
@$core.Deprecated('Use getPublicKeyResponseDescriptor instead')
const GetPublicKeyResponse$json = {
'1': 'GetPublicKeyResponse',
'2': [
{'1': 'public_key_pem', '3': 1, '4': 1, '5': 9, '10': 'publicKeyPem'},
{'1': 'key_id', '3': 2, '4': 1, '5': 9, '10': 'keyId'},
{'1': 'algorithm', '3': 3, '4': 1, '5': 9, '10': 'algorithm'},
],
};
/// Descriptor for `GetPublicKeyResponse`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List getPublicKeyResponseDescriptor = $convert.base64Decode(
'ChRHZXRQdWJsaWNLZXlSZXNwb25zZRIkCg5wdWJsaWNfa2V5X3BlbRgBIAEoCVIMcHVibGljS2'
'V5UGVtEhUKBmtleV9pZBgCIAEoCVIFa2V5SWQSHAoJYWxnb3JpdGhtGAMgASgJUglhbGdvcml0'
'aG0=');
@$core.Deprecated('Use jWTDomainInfoDescriptor instead')
const JWTDomainInfo$json = {
'1': 'JWTDomainInfo',
'2': [
{'1': 'domain', '3': 1, '4': 1, '5': 9, '10': 'domain'},
{'1': 'name', '3': 2, '4': 1, '5': 9, '10': 'name'},
],
};
/// Descriptor for `JWTDomainInfo`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List jWTDomainInfoDescriptor = $convert.base64Decode(
'Cg1KV1REb21haW5JbmZvEhYKBmRvbWFpbhgBIAEoCVIGZG9tYWluEhIKBG5hbWUYAiABKAlSBG'
'5hbWU=');
@$core.Deprecated('Use getJWTDomainListRequestDescriptor instead')
const GetJWTDomainListRequest$json = {
'1': 'GetJWTDomainListRequest',
};
/// Descriptor for `GetJWTDomainListRequest`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List getJWTDomainListRequestDescriptor =
$convert.base64Decode('ChdHZXRKV1REb21haW5MaXN0UmVxdWVzdA==');
@$core.Deprecated('Use getJWTDomainListResponseDescriptor instead')
const GetJWTDomainListResponse$json = {
'1': 'GetJWTDomainListResponse',
'2': [
{
'1': 'domains',
'3': 1,
'4': 3,
'5': 11,
'6': '.auth.JWTDomainInfo',
'10': 'domains'
},
],
};
/// Descriptor for `GetJWTDomainListResponse`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List getJWTDomainListResponseDescriptor =
$convert.base64Decode(
'ChhHZXRKV1REb21haW5MaXN0UmVzcG9uc2USLQoHZG9tYWlucxgBIAMoCzITLmF1dGguSldURG'
'9tYWluSW5mb1IHZG9tYWlucw==');

View File

@@ -8,7 +8,7 @@
// ignore_for_file: constant_identifier_names
// ignore_for_file: curly_braces_in_flow_control_structures
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
import 'dart:core' as $core;

View File

@@ -8,4 +8,4 @@
// ignore_for_file: constant_identifier_names
// ignore_for_file: curly_braces_in_flow_control_structures
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports

View File

@@ -8,7 +8,7 @@
// ignore_for_file: constant_identifier_names
// ignore_for_file: curly_braces_in_flow_control_structures
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
import 'dart:async' as $async;
import 'dart:core' as $core;

View File

@@ -8,7 +8,8 @@
// ignore_for_file: constant_identifier_names
// ignore_for_file: curly_braces_in_flow_control_structures
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names, unused_import
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
// ignore_for_file: unused_import
import 'dart:convert' as $convert;
import 'dart:core' as $core;

View File

@@ -8,7 +8,7 @@
// ignore_for_file: constant_identifier_names
// ignore_for_file: curly_braces_in_flow_control_structures
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
import 'dart:core' as $core;

View File

@@ -8,7 +8,7 @@
// ignore_for_file: constant_identifier_names
// ignore_for_file: curly_braces_in_flow_control_structures
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
import 'dart:core' as $core;

View File

@@ -8,7 +8,7 @@
// ignore_for_file: constant_identifier_names
// ignore_for_file: curly_braces_in_flow_control_structures
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
import 'dart:async' as $async;
import 'dart:core' as $core;

View File

@@ -8,7 +8,8 @@
// ignore_for_file: constant_identifier_names
// ignore_for_file: curly_braces_in_flow_control_structures
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names, unused_import
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
// ignore_for_file: unused_import
import 'dart:convert' as $convert;
import 'dart:core' as $core;

View File

@@ -1912,5 +1912,21 @@
"yearly_report_card_title": "{year} Yearly Report (Limited Time)",
"@yearly_report_card_title": {},
"yearly_report_card_desc": "View your Star Citizen gameplay statistics for {year}. Data is from local logs, please check on your main computer.",
"@yearly_report_card_desc": {}
"@yearly_report_card_desc": {},
"tools_shader_clean_dialog_title": "Clear Shader Cache",
"@tools_shader_clean_dialog_title": {},
"tools_shader_clean_keep_latest": "Keep Latest",
"@tools_shader_clean_keep_latest": {},
"tools_shader_clean_all": "Clean All",
"@tools_shader_clean_all": {},
"tools_shader_clean_keep_latest_desc": "Suitable for use after game updates and completing the first launch. Keep all files in the latest version, clean other old versions (only keep GraphicsSettings).",
"@tools_shader_clean_keep_latest_desc": {},
"tools_shader_clean_all_desc": "Clean all version caches (only keep GraphicsSettings). Suitable for graphics anomalies or post-update cleaning.",
"@tools_shader_clean_all_desc": {},
"tools_action_info_cleaning": "Cleaning...",
"@tools_action_info_cleaning": {},
"tools_action_start_cleaning": "Start Cleaning",
"@tools_action_start_cleaning": {},
"app_common_recommended": "Recommended",
"@app_common_recommended": {}
}

View File

@@ -1734,5 +1734,21 @@
"yearly_report_card_title": "{year} 年間レポート(期間限定)",
"@yearly_report_card_title": {},
"yearly_report_card_desc": "{year}年のStar Citizenプレイ統計を表示します。データはローカルログからのものです。メインのコンピュータで確認してください。",
"@yearly_report_card_desc": {}
"@yearly_report_card_desc": {},
"tools_shader_clean_dialog_title": "シェーダーキャッシュをクリア",
"@tools_shader_clean_dialog_title": {},
"tools_shader_clean_keep_latest": "最新を保持",
"@tools_shader_clean_keep_latest": {},
"tools_shader_clean_all": "すべてクリア",
"@tools_shader_clean_all": {},
"tools_shader_clean_keep_latest_desc": "ゲームアップデート後と初回起動完了後の使用に適しています。最新バージョンのすべてのファイルを保持し、他の古いバージョンをクリアGraphicsSettingsのみ保持。",
"@tools_shader_clean_keep_latest_desc": {},
"tools_shader_clean_all_desc": "すべてのバージョンのキャッシュをクリアGraphicsSettingsのみ保持。グラフィック異常またはアップデート後のクリーニングに適しています。",
"@tools_shader_clean_all_desc": {},
"tools_action_info_cleaning": "クリーニング中...",
"@tools_action_info_cleaning": {},
"tools_action_start_cleaning": "クリーニング開始",
"@tools_action_start_cleaning": {},
"app_common_recommended": "推奨",
"@app_common_recommended": {}
}

View File

@@ -1734,5 +1734,21 @@
"yearly_report_card_title": "Ежегодный отчет {year} (Ограниченное время)",
"@yearly_report_card_title": {},
"yearly_report_card_desc": "Посмотрите статистику вашей игры в Star Citizen за {year} год. Данные из локальных логов, пожалуйста, проверяйте на основном компьютере.",
"@yearly_report_card_desc": {}
"@yearly_report_card_desc": {},
"tools_shader_clean_dialog_title": "Очистить кеш шейдеров",
"@tools_shader_clean_dialog_title": {},
"tools_shader_clean_keep_latest": "Сохранить последнее",
"@tools_shader_clean_keep_latest": {},
"tools_shader_clean_all": "Очистить всё",
"@tools_shader_clean_all": {},
"tools_shader_clean_keep_latest_desc": "Подходит для использования после обновления игры и завершения первого запуска. Сохранить все файлы последней версии, очистить другие старые версии (сохранить только GraphicsSettings).",
"@tools_shader_clean_keep_latest_desc": {},
"tools_shader_clean_all_desc": "Очистить кеш всех версий (сохранить только GraphicsSettings). Подходит для графических аномалий или очистки после обновления.",
"@tools_shader_clean_all_desc": {},
"tools_action_info_cleaning": "Очистка...",
"@tools_action_info_cleaning": {},
"tools_action_start_cleaning": "Начать очистку",
"@tools_action_start_cleaning": {},
"app_common_recommended": "Рекомендуется",
"@app_common_recommended": {}
}

View File

@@ -1328,5 +1328,13 @@
"yearly_report_powered_by": "由 SC 汉化盒子为您呈现",
"yearly_report_disclaimer": "数据使用您的本地日志生成,不会发送到任何第三方。因跨版本 Log 改动较大,数据可能不完整,仅供娱乐。",
"yearly_report_card_title": "{year} 年度报告(限时)",
"yearly_report_card_desc": "查看您在{year}年的星际公民游玩统计,数据来自本地 log ,请确保在常用电脑上查看。"
"yearly_report_card_desc": "查看您在{year}年的星际公民游玩统计,数据来自本地 log ,请确保在常用电脑上查看。",
"tools_shader_clean_dialog_title": "清理着色器缓存",
"tools_shader_clean_keep_latest": "保留最新",
"tools_shader_clean_all": "全部清理",
"tools_shader_clean_keep_latest_desc": "适合游戏更新并完成首次启动后使用。保留最新版本的所有文件,清理其他旧版本。",
"tools_shader_clean_all_desc": "清理所有版本的缓存。适合画面异常或更新后清理。",
"tools_action_info_cleaning": "正在清理...",
"tools_action_start_cleaning": "开始清理",
"app_common_recommended": "推荐"
}

View File

@@ -1670,5 +1670,13 @@
"yearly_report_powered_by": "由 SC工具箱為您呈現",
"yearly_report_disclaimer": "數據使用您的本地日誌生成,不會發送到任何第三方。因跨版本 Log 改動較大,數據可能不完整,僅供娛樂。",
"yearly_report_card_title": "{year} 年度報告(限時)",
"yearly_report_card_desc": "查看您在{year}年的星際公民遊玩統計,數據來自本地 log ,請確保在常用電腦上查看。"
"yearly_report_card_desc": "查看您在{year}年的星際公民遊玩統計,數據來自本地 log ,請確保在常用電腦上查看。",
"tools_shader_clean_dialog_title": "清理著色器緩存",
"tools_shader_clean_keep_latest": "保留最新",
"tools_shader_clean_all": "全部清理",
"tools_shader_clean_keep_latest_desc": "適合遊戲更新並完成首次啟動後使用。保留最新版本的所有檔案,清理其他舊版本(僅保留 GraphicsSettings。",
"tools_shader_clean_all_desc": "清理所有版本的快取(僅保留 GraphicsSettings。適合畫面異常或更新後清理。",
"tools_action_info_cleaning": "正在清理...",
"tools_action_start_cleaning": "開始清理",
"app_common_recommended": "推薦"
}

View File

@@ -1,6 +1,7 @@
import 'dart:io';
import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter_acrylic/flutter_acrylic.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:starcitizen_doctor/generated/l10n.dart';
@@ -36,6 +37,9 @@ Future<void> main(List<String> args) async {
}
Future<void> _initWindow() async {
// Initialize flutter_acrylic before runApp (same as official example)
await Window.initialize();
await Window.hideWindowControls();
await windowManager.setTitleBarStyle(TitleBarStyle.hidden, windowButtonVisibility: false);
await windowManager.setSize(const Size(1280, 810));
await windowManager.setMinimumSize(const Size(1280, 810));

View File

@@ -10,11 +10,11 @@ part of 'dcb_viewer.dart';
// ignore_for_file: type=lint, type=warning
@ProviderFor(DcbViewerModel)
const dcbViewerModelProvider = DcbViewerModelProvider._();
final dcbViewerModelProvider = DcbViewerModelProvider._();
final class DcbViewerModelProvider
extends $NotifierProvider<DcbViewerModel, DcbViewerState> {
const DcbViewerModelProvider._()
DcbViewerModelProvider._()
: super(
from: null,
argument: null,
@@ -48,7 +48,6 @@ abstract class _$DcbViewerModel extends $Notifier<DcbViewerState> {
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<DcbViewerState, DcbViewerState>;
final element =
ref.element
@@ -58,6 +57,6 @@ abstract class _$DcbViewerModel extends $Notifier<DcbViewerState> {
Object?,
Object?
>;
element.handleValue(ref, created);
element.handleCreate(ref, build);
}
}

View File

@@ -10,11 +10,11 @@ part of 'download_manager.dart';
// ignore_for_file: type=lint, type=warning
@ProviderFor(DownloadManager)
const downloadManagerProvider = DownloadManagerProvider._();
final downloadManagerProvider = DownloadManagerProvider._();
final class DownloadManagerProvider
extends $NotifierProvider<DownloadManager, DownloadManagerState> {
const DownloadManagerProvider._()
DownloadManagerProvider._()
: super(
from: null,
argument: null,
@@ -48,7 +48,6 @@ abstract class _$DownloadManager extends $Notifier<DownloadManagerState> {
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<DownloadManagerState, DownloadManagerState>;
final element =
ref.element
@@ -58,6 +57,6 @@ abstract class _$DownloadManager extends $Notifier<DownloadManagerState> {
Object?,
Object?
>;
element.handleValue(ref, created);
element.handleCreate(ref, build);
}
}

View File

@@ -127,11 +127,17 @@ class PartyRoom extends _$PartyRoom {
final serverAddress = URLConf.partyRoomServerAddress;
final serverPort = URLConf.partyRoomServerPort;
var credentials = ChannelCredentials.secure();
if (serverAddress == '127.0.0.1' || serverAddress == 'localhost') {
credentials = ChannelCredentials.insecure();
}
final channel = ClientChannel(
serverAddress,
port: serverPort,
options: ChannelOptions(
credentials: credentials,
keepAlive: ClientKeepAliveOptions(
pingInterval: Duration(seconds: 30),
timeout: Duration(seconds: 10),
@@ -179,7 +185,7 @@ class PartyRoom extends _$PartyRoom {
}
/// 获取认证 CallOptions
CallOptions _getAuthCallOptions() {
CallOptions getAuthCallOptions() {
return CallOptions(metadata: {'uuid': state.auth.uuid, 'secret-key': state.auth.secretKey});
}
@@ -191,7 +197,7 @@ class PartyRoom extends _$PartyRoom {
final client = state.client.authClient;
if (client == null) throw Exception('Not connected to server');
final response = await client.login(auth.LoginRequest(), options: _getAuthCallOptions());
final response = await client.login(auth.LoginRequest(), options: getAuthCallOptions());
state = state.copyWith(
auth: state.auth.copyWith(
@@ -255,7 +261,7 @@ class PartyRoom extends _$PartyRoom {
final client = state.client.authClient;
if (client == null) throw Exception('Not connected to server');
await client.unregister(auth.UnregisterRequest(), options: _getAuthCallOptions());
await client.unregister(auth.UnregisterRequest(), options: getAuthCallOptions());
// 清除本地认证信息
await _confBox?.delete(_secretKeyKey);
@@ -271,6 +277,24 @@ class PartyRoom extends _$PartyRoom {
}
}
/// 刷新用户资料
Future<void> refreshUserProfile() async {
try {
final client = state.client.authClient;
if (client == null) throw Exception('Not connected to server');
final response = await client.refreshUserProfile(auth.RefreshUserProfileRequest(), options: getAuthCallOptions());
if (response.success && response.hasUserInfo()) {
state = state.copyWith(auth: state.auth.copyWith(userInfo: response.userInfo));
dPrint('[PartyRoom] User profile refreshed: ${response.userInfo.gameUserId}');
}
} catch (e) {
dPrint('[PartyRoom] RefreshUserProfile error: $e');
rethrow;
}
}
// ========== 房间相关方法 ==========
/// 加载标签和信号类型
@@ -349,7 +373,7 @@ class PartyRoom extends _$PartyRoom {
password_5: password ?? '',
socialLinks: socialLinks ?? [],
),
options: _getAuthCallOptions(),
options: getAuthCallOptions(),
);
state = state.copyWith(room: state.room.copyWith(roomUuid: response.roomUuid, isInRoom: true, isOwner: true));
@@ -376,7 +400,7 @@ class PartyRoom extends _$PartyRoom {
await client.joinRoom(
partroom.JoinRoomRequest(roomUuid: roomUuid, password: password ?? ''),
options: _getAuthCallOptions(),
options: getAuthCallOptions(),
);
state = state.copyWith(room: state.room.copyWith(roomUuid: roomUuid, isInRoom: true, isOwner: false));
@@ -404,7 +428,7 @@ class PartyRoom extends _$PartyRoom {
final roomUuid = state.room.roomUuid;
if (roomUuid == null) return;
await client.leaveRoom(partroom.LeaveRoomRequest(roomUuid: roomUuid), options: _getAuthCallOptions());
await client.leaveRoom(partroom.LeaveRoomRequest(roomUuid: roomUuid), options: getAuthCallOptions());
await _stopHeartbeat();
await _stopEventStream();
@@ -427,7 +451,7 @@ class PartyRoom extends _$PartyRoom {
final roomUuid = state.room.roomUuid;
if (roomUuid == null) return;
await client.dismissRoom(partroom.DismissRoomRequest(roomUuid: roomUuid), options: _getAuthCallOptions());
await client.dismissRoom(partroom.DismissRoomRequest(roomUuid: roomUuid), options: getAuthCallOptions());
await _stopHeartbeat();
await _stopEventStream();
@@ -449,7 +473,7 @@ class PartyRoom extends _$PartyRoom {
final response = await client.getRoomInfo(
partroom.GetRoomInfoRequest(roomUuid: roomUuid),
options: _getAuthCallOptions(),
options: getAuthCallOptions(),
);
// 检查是否为房主
@@ -477,7 +501,7 @@ class PartyRoom extends _$PartyRoom {
final response = await client.getRoomMembers(
partroom.GetRoomMembersRequest(roomUuid: roomUuid, page: page, pageSize: pageSize),
options: _getAuthCallOptions(),
options: getAuthCallOptions(),
);
state = state.copyWith(room: state.room.copyWith(members: response.members));
@@ -495,7 +519,7 @@ class PartyRoom extends _$PartyRoom {
final client = state.client.roomClient;
if (client == null) return;
final response = await client.getMyRoom(partroom.GetMyRoomRequest(), options: _getAuthCallOptions());
final response = await client.getMyRoom(partroom.GetMyRoomRequest(), options: getAuthCallOptions());
if (response.hasRoom() && response.room.roomUuid.isNotEmpty) {
final isOwner = response.room.ownerGameId == state.auth.userInfo?.gameUserId;
@@ -553,7 +577,7 @@ class PartyRoom extends _$PartyRoom {
request.socialLinks.addAll(state.room.currentRoom!.socialLinks);
}
await client.updateRoom(request, options: _getAuthCallOptions());
await client.updateRoom(request, options: getAuthCallOptions());
// 刷新房间信息
await getRoomInfo(roomUuid);
@@ -576,7 +600,7 @@ class PartyRoom extends _$PartyRoom {
await client.kickMember(
partroom.KickMemberRequest(roomUuid: roomUuid, targetGameUserId: targetGameUserId),
options: _getAuthCallOptions(),
options: getAuthCallOptions(),
);
dPrint('[PartyRoom] Member kicked: $targetGameUserId');
@@ -608,7 +632,7 @@ class PartyRoom extends _$PartyRoom {
playTime: Int64(playTime ?? 0),
),
),
options: _getAuthCallOptions(),
options: getAuthCallOptions(),
);
dPrint('[PartyRoom] Status updated');
@@ -638,7 +662,7 @@ class PartyRoom extends _$PartyRoom {
request.params.addAll(params);
}
await client.sendSignal(request, options: _getAuthCallOptions());
await client.sendSignal(request, options: getAuthCallOptions());
dPrint('[PartyRoom] Signal sent: $signalId');
} catch (e) {
@@ -658,7 +682,7 @@ class PartyRoom extends _$PartyRoom {
await client.transferOwnership(
partroom.TransferOwnershipRequest(roomUuid: roomUuid, targetGameUserId: targetGameUserId),
options: _getAuthCallOptions(),
options: getAuthCallOptions(),
);
// 更新房主状态
@@ -685,7 +709,7 @@ class PartyRoom extends _$PartyRoom {
final response = await client.getKickedMembers(
partroom.GetKickedMembersRequest(roomUuid: roomUuid, page: page, pageSize: pageSize),
options: _getAuthCallOptions(),
options: getAuthCallOptions(),
);
return response;
@@ -706,7 +730,7 @@ class PartyRoom extends _$PartyRoom {
await client.removeKickedMember(
partroom.RemoveKickedMemberRequest(roomUuid: roomUuid, gameUserId: gameUserId),
options: _getAuthCallOptions(),
options: getAuthCallOptions(),
);
dPrint('[PartyRoom] Kicked member removed: $gameUserId');
@@ -732,7 +756,7 @@ class PartyRoom extends _$PartyRoom {
final client = state.client.roomClient;
if (client == null) return;
await client.heartbeat(partroom.HeartbeatRequest(roomUuid: roomUuid), options: _getAuthCallOptions());
await client.heartbeat(partroom.HeartbeatRequest(roomUuid: roomUuid), options: getAuthCallOptions());
dPrint('[PartyRoom] Heartbeat sent');
} catch (e) {
@@ -760,7 +784,7 @@ class PartyRoom extends _$PartyRoom {
final stream = client.listenRoomEvents(
partroom.ListenRoomEventsRequest(roomUuid: roomUuid),
options: _getAuthCallOptions(),
options: getAuthCallOptions(),
);
_eventStreamSubscription = stream.listen(

View File

@@ -11,13 +11,13 @@ part of 'party_room.dart';
/// PartyRoom Provider
@ProviderFor(PartyRoom)
const partyRoomProvider = PartyRoomProvider._();
final partyRoomProvider = PartyRoomProvider._();
/// PartyRoom Provider
final class PartyRoomProvider
extends $NotifierProvider<PartyRoom, PartyRoomFullState> {
/// PartyRoom Provider
const PartyRoomProvider._()
PartyRoomProvider._()
: super(
from: null,
argument: null,
@@ -44,7 +44,7 @@ final class PartyRoomProvider
}
}
String _$partyRoomHash() => r'2ce3ac365bec3af8f7e1d350b53262c8e4e2872d';
String _$partyRoomHash() => r'446e4cc88be96c890f8e676c6faf0e4d3b33a529';
/// PartyRoom Provider
@@ -53,7 +53,6 @@ abstract class _$PartyRoom extends $Notifier<PartyRoomFullState> {
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<PartyRoomFullState, PartyRoomFullState>;
final element =
ref.element
@@ -63,6 +62,6 @@ abstract class _$PartyRoom extends $Notifier<PartyRoomFullState> {
Object?,
Object?
>;
element.handleValue(ref, created);
element.handleCreate(ref, build);
}
}

View File

@@ -10,6 +10,7 @@ import 'package:path_provider/path_provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:starcitizen_doctor/api/analytics.dart';
import 'package:starcitizen_doctor/common/helper/log_helper.dart';
import 'package:starcitizen_doctor/common/utils/base_utils.dart';
import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/data/app_unp4k_p4k_item_data.dart';
import 'package:starcitizen_doctor/ui/tools/tools_ui_model.dart';
@@ -80,7 +81,7 @@ class Unp4kCModel extends _$Unp4kCModel {
void _init() async {
final gamePath = getGamePath();
final gameP4kPath = "$gamePath\\Data.p4k";
final gameP4kPath = "$gamePath\\Data.p4k".platformPath;
try {
state = state.copyWith(endMessage: S.current.tools_unp4k_msg_reading);

View File

@@ -10,11 +10,11 @@ part of 'unp4kc.dart';
// ignore_for_file: type=lint, type=warning
@ProviderFor(Unp4kCModel)
const unp4kCModelProvider = Unp4kCModelProvider._();
final unp4kCModelProvider = Unp4kCModelProvider._();
final class Unp4kCModelProvider
extends $NotifierProvider<Unp4kCModel, Unp4kcState> {
const Unp4kCModelProvider._()
Unp4kCModelProvider._()
: super(
from: null,
argument: null,
@@ -41,14 +41,13 @@ final class Unp4kCModelProvider
}
}
String _$unp4kCModelHash() => r'68c24d50113e9e734ae8d277f65999bbef05dc05';
String _$unp4kCModelHash() => r'6713e8e95e2bec1adcb36b63f6c6f9240a5959be';
abstract class _$Unp4kCModel extends $Notifier<Unp4kcState> {
Unp4kcState build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<Unp4kcState, Unp4kcState>;
final element =
ref.element
@@ -58,6 +57,6 @@ abstract class _$Unp4kCModel extends $Notifier<Unp4kcState> {
Object?,
Object?
>;
element.handleValue(ref, created);
element.handleCreate(ref, build);
}
}

306
lib/ui/auth/auth_page.dart Normal file
View File

@@ -0,0 +1,306 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:starcitizen_doctor/provider/party_room.dart';
import 'package:starcitizen_doctor/ui/auth/auth_ui_model.dart';
import 'package:starcitizen_doctor/ui/party_room/utils/party_room_utils.dart';
import 'package:starcitizen_doctor/widgets/widgets.dart';
import 'package:url_launcher/url_launcher_string.dart';
class AuthPage extends HookConsumerWidget {
final String? callbackUrl;
final String? stateParameter;
final String? nonce;
const AuthPage({super.key, this.callbackUrl, this.stateParameter, this.nonce});
@override
Widget build(BuildContext context, WidgetRef ref) {
final provider = authUIModelProvider(callbackUrl: callbackUrl, stateParameter: stateParameter, nonce: nonce);
final model = ref.watch(provider);
final modelNotifier = ref.read(provider.notifier);
final partyRoomState = ref.watch(partyRoomProvider);
final userName = partyRoomState.auth.userInfo?.handleName ?? '未知用户';
final userEmail = partyRoomState.auth.userInfo?.gameUserId ?? ''; // Using gameUserId as email-like identifier
final avatarUrl = partyRoomState.auth.userInfo?.avatarUrl;
final fullAvatarUrl = PartyRoomUtils.getAvatarUrl(avatarUrl);
useEffect(() {
Future.microtask(() => modelNotifier.initialize());
return null;
}, const []);
return ContentDialog(
constraints: const BoxConstraints(maxWidth: 450, maxHeight: 600),
// Remove standard title to customize layout
title: const SizedBox.shrink(),
content: _buildBody(context, model, modelNotifier, userName, userEmail, fullAvatarUrl),
actions: [
if (model.error == null && model.isLoggedIn) ...[
// Cancel button
Button(onPressed: () => Navigator.of(context).pop(), child: const Text('拒绝')),
// Allow button (Primary)
FilledButton(
onPressed: model.isLoading ? null : () => _handleAuthorize(context, ref, false),
child: const Text('允许'),
),
] else ...[
Button(onPressed: () => Navigator.of(context).pop(), child: const Text('关闭')),
],
],
);
}
Widget _buildBody(
BuildContext context,
AuthUIState state,
AuthUIModel model,
String userName,
String userEmail,
String? avatarUrl,
) {
if (state.isLoading) {
return const SizedBox(height: 300, child: Center(child: ProgressRing()));
}
if (state.isWaitingForConnection) {
return SizedBox(
height: 300,
child: Center(
child: Column(mainAxisSize: MainAxisSize.min, children: [const ProgressRing(), const SizedBox(height: 24)]),
),
);
}
if (state.error != null) {
return SizedBox(
height: 300,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(FluentIcons.error_badge, size: 48, color: Colors.red),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Text(
state.error!,
style: TextStyle(color: Colors.red, fontSize: 14),
textAlign: TextAlign.center,
),
),
const SizedBox(height: 24),
FilledButton(onPressed: () => model.initialize(), child: const Text('重试')),
],
),
),
);
}
if (!state.isLoggedIn) {
return SizedBox(
height: 300,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(FluentIcons.warning, size: 48, color: Colors.orange),
const SizedBox(height: 16),
const Text('您需要先登录才能授权', style: TextStyle(fontSize: 16)),
const SizedBox(height: 24),
FilledButton(onPressed: () => Navigator.of(context).pop(), child: const Text('前往登录')),
],
),
),
);
}
final name = state.domainName ?? state.domain ?? '未知应用';
final domain = state.domain;
final isTrusted = state.isDomainTrusted;
return SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(height: 8),
// Title
RichText(
textAlign: TextAlign.center,
text: TextSpan(
style: TextStyle(fontSize: 20, color: Colors.white.withValues(alpha: 0.95), fontFamily: 'Segoe UI'),
children: [
TextSpan(
text: name,
style: const TextStyle(fontWeight: FontWeight.bold),
),
const TextSpan(text: ' 申请访问您的账户'),
],
),
),
const SizedBox(height: 8),
if (domain != null)
Padding(
padding: const EdgeInsets.only(top: 4),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (state.domainName != null)
Text(domain, style: TextStyle(fontSize: 14, color: Colors.white.withValues(alpha: 0.5))),
if (state.domainName != null) const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: (isTrusted ? Colors.green : Colors.orange).withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(4),
border: Border.all(color: (isTrusted ? Colors.green : Colors.orange).withValues(alpha: 0.3)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
isTrusted ? FluentIcons.completed : FluentIcons.warning,
size: 10,
color: isTrusted ? Colors.green : Colors.orange,
),
const SizedBox(width: 4),
Text(
isTrusted ? '已认证' : '未验证',
style: TextStyle(
fontSize: 10,
color: isTrusted ? Colors.green : Colors.orange,
fontWeight: FontWeight.w500,
),
),
],
),
),
],
),
),
const SizedBox(height: 24),
// 2. User Account Info
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
border: Border.all(color: Colors.white.withValues(alpha: 0.1)),
borderRadius: BorderRadius.circular(24),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
ClipOval(
child: SizedBox(
width: 24,
height: 24,
child: avatarUrl != null
? CacheNetImage(url: avatarUrl, fit: BoxFit.cover)
: const Icon(FluentIcons.contact, size: 24),
),
),
const SizedBox(width: 12),
Text(
userEmail.isNotEmpty ? userEmail : userName,
style: TextStyle(
fontSize: 13,
color: Colors.white.withValues(alpha: 0.8),
fontWeight: FontWeight.w500,
),
),
],
),
),
const SizedBox(height: 32),
Align(
alignment: Alignment.centerLeft,
child: Text('此操作将允许 $domain', style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500)),
),
const SizedBox(height: 16),
_buildPermissionItem(FluentIcons.contact_info, '访问您的公开资料', '包括用户名、头像'),
const SizedBox(height: 48),
],
),
);
}
Widget _buildPermissionItem(IconData icon, String title, String subtitle) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(icon, size: 20, color: Colors.blue),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500)),
const SizedBox(height: 2),
Text(subtitle, style: TextStyle(fontSize: 12, color: Colors.white.withValues(alpha: 0.6))),
],
),
),
],
);
}
Future<void> _handleAuthorize(BuildContext context, WidgetRef ref, bool copyOnly) async {
final provider = authUIModelProvider(callbackUrl: callbackUrl, stateParameter: stateParameter, nonce: nonce);
final modelNotifier = ref.read(provider.notifier);
final model = ref.read(provider);
// First, generate the code if not already generated
if (model.code == null) {
final success = await modelNotifier.generateCodeOnConfirm();
if (!success) {
if (context.mounted) {
final currentState = ref.read(provider);
await showToast(context, currentState.error ?? '生成授权码失败');
}
return;
}
}
final authUrl = modelNotifier.getAuthorizationUrl();
if (authUrl == null) {
if (context.mounted) {
await showToast(context, '生成授权链接失败');
}
return;
}
if (copyOnly) {
await modelNotifier.copyAuthorizationUrl();
if (context.mounted) {
await showToast(context, '授权链接已复制到剪贴板');
if (context.mounted) {
Navigator.of(context).pop();
}
}
} else {
try {
final launched = await launchUrlString(authUrl);
if (!launched) {
if (context.mounted) {
await showToast(context, '打开浏览器失败,请复制链接手动访问');
}
return;
}
if (context.mounted) {
Navigator.of(context).pop();
}
} catch (e) {
if (context.mounted) {
await showToast(context, '打开浏览器失败: $e');
}
}
}
}
}

View File

@@ -0,0 +1,237 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/generated/proto/auth/auth.pb.dart';
import 'package:starcitizen_doctor/provider/party_room.dart';
import 'package:starcitizen_doctor/ui/party_room/party_room_ui_model.dart';
part 'auth_ui_model.freezed.dart';
part 'auth_ui_model.g.dart';
@freezed
sealed class AuthUIState with _$AuthUIState {
const factory AuthUIState({
@Default(false) bool isLoading,
@Default(false) bool isLoggedIn,
@Default(false) bool isWaitingForConnection,
String? domain,
String? callbackUrl,
String? stateParameter,
String? nonce,
String? code,
String? error,
@Default(false) bool isDomainTrusted,
String? domainName,
}) = _AuthUIState;
}
@riverpod
class AuthUIModel extends _$AuthUIModel {
@override
AuthUIState build({String? callbackUrl, String? stateParameter, String? nonce}) {
// Listen to party room connection and auth state changes
ref.listen(partyRoomProvider, (previous, next) {
// If we're waiting for connection and now connected, re-initialize
if (state.isWaitingForConnection && next.client.isConnected && next.client.authClient != null) {
dPrint('[AuthUI] Connection established, re-initializing...');
Future.microtask(() => initialize());
}
// If not logged in before and now logged in, re-initialize
if (!state.isLoggedIn && previous?.auth.isLoggedIn == false && next.auth.isLoggedIn) {
dPrint('[AuthUI] User logged in, re-initializing...');
Future.microtask(() => initialize());
}
});
// Listen to party room UI model for login status changes
ref.listen(partyRoomUIModelProvider, (previous, next) {
// If was logging in and now finished (success or fail), re-check logic
if (previous?.isLoggingIn == true && !next.isLoggingIn) {
dPrint('[AuthUI] Login process finished, re-initializing...');
Future.microtask(() => initialize());
}
});
return AuthUIState(callbackUrl: callbackUrl, stateParameter: stateParameter, nonce: nonce);
}
Future<void> initialize() async {
state = state.copyWith(isLoading: true, error: null, isWaitingForConnection: false);
try {
// Check if domain and callbackUrl are provided
if (state.callbackUrl == null || state.callbackUrl!.isEmpty) {
state = state.copyWith(isLoading: false, error: '缺少回调地址参数');
return;
}
if (state.stateParameter == null || state.stateParameter!.isEmpty) {
state = state.copyWith(isLoading: false, error: '缺少 state 参数');
return;
}
// Extract domain from callbackUrl
String? domain;
try {
final uri = Uri.parse(state.callbackUrl!);
if (uri.host.isNotEmpty) {
domain = uri.host;
}
} catch (e) {
dPrint('Failed to parse callbackUrl: $e');
}
if (domain == null || domain.isEmpty) {
state = state.copyWith(isLoading: false, error: '无法从回调地址解析域名');
return;
}
// Update state with extracted domain
state = state.copyWith(domain: domain);
// Get party room providers
final partyRoom = ref.read(partyRoomProvider);
final partyRoomUI = ref.read(partyRoomUIModelProvider);
// Check if connected to server
if (!partyRoom.client.isConnected || partyRoom.client.authClient == null) {
dPrint('[AuthUI] Server not connected, waiting for connection...');
state = state.copyWith(isLoading: false, isWaitingForConnection: true);
return;
}
// Check if user is logged in
if (!partyRoom.auth.isLoggedIn) {
// If still logging in process (auto-login after connect), keep waiting
if (partyRoomUI.isLoggingIn) {
dPrint('[AuthUI] Auto-login in progress, waiting...');
state = state.copyWith(isLoading: false, isWaitingForConnection: true);
return;
}
state = state.copyWith(isLoading: false, isLoggedIn: false);
return;
}
// Check domain trust status
final domainListResponse = await _getDomainList();
bool isDomainTrusted = false;
String? domainName;
if (domainListResponse != null) {
final domainInfo = domainListResponse.domains
.cast<
JWTDomainInfo?
>() // Cast to nullable to use firstWhere with orElse returning null if needed, though JWTDomainInfo is not nullable in proto usually
.firstWhere((d) => d?.domain.toLowerCase() == state.domain!.toLowerCase(), orElse: () => null);
if (domainInfo != null && domainInfo.domain.isNotEmpty) {
isDomainTrusted = true;
domainName = domainInfo.name;
}
}
// Don't generate token yet - wait for user confirmation
state = state.copyWith(
isLoading: false,
isLoggedIn: true,
isDomainTrusted: isDomainTrusted,
domainName: domainName,
);
} catch (e) {
dPrint('Auth initialization error: $e');
state = state.copyWith(isLoading: false, error: '初始化失败: $e');
}
}
Future<bool> generateCodeOnConfirm() async {
// Only generate code if user is logged in and no previous error
if (!state.isLoggedIn) {
return false;
}
state = state.copyWith(isLoading: true);
try {
// Generate OIDC Auth Code
final code = await _generateOIDCAuthCode();
if (code == null) {
state = state.copyWith(isLoading: false, error: '生成授权码失败');
return false;
}
state = state.copyWith(isLoading: false, code: code);
return true;
} catch (e) {
dPrint('Generate code on confirm error: $e');
state = state.copyWith(isLoading: false, error: '生成授权码失败: $e');
return false;
}
}
Future<GetJWTDomainListResponse?> _getDomainList() async {
try {
final partyRoom = ref.read(partyRoomProvider);
final partyRoomNotifier = ref.read(partyRoomProvider.notifier);
final client = partyRoom.client.authClient;
if (client == null) return null;
final response = await client.getJWTDomainList(
GetJWTDomainListRequest(),
options: partyRoomNotifier.getAuthCallOptions(),
);
return response;
} catch (e) {
dPrint('Get domain list error: $e');
return null;
}
}
Future<String?> _generateOIDCAuthCode() async {
try {
final partyRoom = ref.read(partyRoomProvider);
final partyRoomNotifier = ref.read(partyRoomProvider.notifier);
final client = partyRoom.client.authClient;
if (client == null || state.callbackUrl == null) return null;
final request = GenerateOIDCAuthCodeRequest(redirectUri: state.callbackUrl!, nonce: state.nonce ?? '');
final response = await client.generateOIDCAuthCode(request, options: partyRoomNotifier.getAuthCallOptions());
return response.code;
} catch (e) {
dPrint('Generate OIDC Auth Code error: $e');
return null;
}
}
String? getAuthorizationUrl() {
if (state.code == null || state.callbackUrl == null || state.stateParameter == null) {
return null;
}
// Build authorization URL
// Using query parameters (?) to allow both server-side and client-side processing
final uri = Uri.parse(state.callbackUrl!);
// Merge existing query parameters with new ones
final newQueryParameters = Map<String, dynamic>.from(uri.queryParameters);
newQueryParameters['code'] = state.code!;
newQueryParameters['state'] = state.stateParameter!;
final authUri = uri.replace(queryParameters: newQueryParameters);
return authUri.toString();
}
Future<void> copyAuthorizationUrl() async {
final url = getAuthorizationUrl();
if (url != null) {
await Clipboard.setData(ClipboardData(text: url));
}
}
}

View File

@@ -0,0 +1,295 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'auth_ui_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$AuthUIState {
bool get isLoading; bool get isLoggedIn; bool get isWaitingForConnection; String? get domain; String? get callbackUrl; String? get stateParameter; String? get nonce; String? get code; String? get error; bool get isDomainTrusted; String? get domainName;
/// Create a copy of AuthUIState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$AuthUIStateCopyWith<AuthUIState> get copyWith => _$AuthUIStateCopyWithImpl<AuthUIState>(this as AuthUIState, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is AuthUIState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isLoggedIn, isLoggedIn) || other.isLoggedIn == isLoggedIn)&&(identical(other.isWaitingForConnection, isWaitingForConnection) || other.isWaitingForConnection == isWaitingForConnection)&&(identical(other.domain, domain) || other.domain == domain)&&(identical(other.callbackUrl, callbackUrl) || other.callbackUrl == callbackUrl)&&(identical(other.stateParameter, stateParameter) || other.stateParameter == stateParameter)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&(identical(other.code, code) || other.code == code)&&(identical(other.error, error) || other.error == error)&&(identical(other.isDomainTrusted, isDomainTrusted) || other.isDomainTrusted == isDomainTrusted)&&(identical(other.domainName, domainName) || other.domainName == domainName));
}
@override
int get hashCode => Object.hash(runtimeType,isLoading,isLoggedIn,isWaitingForConnection,domain,callbackUrl,stateParameter,nonce,code,error,isDomainTrusted,domainName);
@override
String toString() {
return 'AuthUIState(isLoading: $isLoading, isLoggedIn: $isLoggedIn, isWaitingForConnection: $isWaitingForConnection, domain: $domain, callbackUrl: $callbackUrl, stateParameter: $stateParameter, nonce: $nonce, code: $code, error: $error, isDomainTrusted: $isDomainTrusted, domainName: $domainName)';
}
}
/// @nodoc
abstract mixin class $AuthUIStateCopyWith<$Res> {
factory $AuthUIStateCopyWith(AuthUIState value, $Res Function(AuthUIState) _then) = _$AuthUIStateCopyWithImpl;
@useResult
$Res call({
bool isLoading, bool isLoggedIn, bool isWaitingForConnection, String? domain, String? callbackUrl, String? stateParameter, String? nonce, String? code, String? error, bool isDomainTrusted, String? domainName
});
}
/// @nodoc
class _$AuthUIStateCopyWithImpl<$Res>
implements $AuthUIStateCopyWith<$Res> {
_$AuthUIStateCopyWithImpl(this._self, this._then);
final AuthUIState _self;
final $Res Function(AuthUIState) _then;
/// Create a copy of AuthUIState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? isLoading = null,Object? isLoggedIn = null,Object? isWaitingForConnection = null,Object? domain = freezed,Object? callbackUrl = freezed,Object? stateParameter = freezed,Object? nonce = freezed,Object? code = freezed,Object? error = freezed,Object? isDomainTrusted = null,Object? domainName = freezed,}) {
return _then(_self.copyWith(
isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,isLoggedIn: null == isLoggedIn ? _self.isLoggedIn : isLoggedIn // ignore: cast_nullable_to_non_nullable
as bool,isWaitingForConnection: null == isWaitingForConnection ? _self.isWaitingForConnection : isWaitingForConnection // ignore: cast_nullable_to_non_nullable
as bool,domain: freezed == domain ? _self.domain : domain // ignore: cast_nullable_to_non_nullable
as String?,callbackUrl: freezed == callbackUrl ? _self.callbackUrl : callbackUrl // ignore: cast_nullable_to_non_nullable
as String?,stateParameter: freezed == stateParameter ? _self.stateParameter : stateParameter // ignore: cast_nullable_to_non_nullable
as String?,nonce: freezed == nonce ? _self.nonce : nonce // ignore: cast_nullable_to_non_nullable
as String?,code: freezed == code ? _self.code : code // ignore: cast_nullable_to_non_nullable
as String?,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable
as String?,isDomainTrusted: null == isDomainTrusted ? _self.isDomainTrusted : isDomainTrusted // ignore: cast_nullable_to_non_nullable
as bool,domainName: freezed == domainName ? _self.domainName : domainName // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// Adds pattern-matching-related methods to [AuthUIState].
extension AuthUIStatePatterns on AuthUIState {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _AuthUIState value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _AuthUIState() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _AuthUIState value) $default,){
final _that = this;
switch (_that) {
case _AuthUIState():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _AuthUIState value)? $default,){
final _that = this;
switch (_that) {
case _AuthUIState() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool isLoading, bool isLoggedIn, bool isWaitingForConnection, String? domain, String? callbackUrl, String? stateParameter, String? nonce, String? code, String? error, bool isDomainTrusted, String? domainName)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _AuthUIState() when $default != null:
return $default(_that.isLoading,_that.isLoggedIn,_that.isWaitingForConnection,_that.domain,_that.callbackUrl,_that.stateParameter,_that.nonce,_that.code,_that.error,_that.isDomainTrusted,_that.domainName);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool isLoading, bool isLoggedIn, bool isWaitingForConnection, String? domain, String? callbackUrl, String? stateParameter, String? nonce, String? code, String? error, bool isDomainTrusted, String? domainName) $default,) {final _that = this;
switch (_that) {
case _AuthUIState():
return $default(_that.isLoading,_that.isLoggedIn,_that.isWaitingForConnection,_that.domain,_that.callbackUrl,_that.stateParameter,_that.nonce,_that.code,_that.error,_that.isDomainTrusted,_that.domainName);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool isLoading, bool isLoggedIn, bool isWaitingForConnection, String? domain, String? callbackUrl, String? stateParameter, String? nonce, String? code, String? error, bool isDomainTrusted, String? domainName)? $default,) {final _that = this;
switch (_that) {
case _AuthUIState() when $default != null:
return $default(_that.isLoading,_that.isLoggedIn,_that.isWaitingForConnection,_that.domain,_that.callbackUrl,_that.stateParameter,_that.nonce,_that.code,_that.error,_that.isDomainTrusted,_that.domainName);case _:
return null;
}
}
}
/// @nodoc
class _AuthUIState implements AuthUIState {
const _AuthUIState({this.isLoading = false, this.isLoggedIn = false, this.isWaitingForConnection = false, this.domain, this.callbackUrl, this.stateParameter, this.nonce, this.code, this.error, this.isDomainTrusted = false, this.domainName});
@override@JsonKey() final bool isLoading;
@override@JsonKey() final bool isLoggedIn;
@override@JsonKey() final bool isWaitingForConnection;
@override final String? domain;
@override final String? callbackUrl;
@override final String? stateParameter;
@override final String? nonce;
@override final String? code;
@override final String? error;
@override@JsonKey() final bool isDomainTrusted;
@override final String? domainName;
/// Create a copy of AuthUIState
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$AuthUIStateCopyWith<_AuthUIState> get copyWith => __$AuthUIStateCopyWithImpl<_AuthUIState>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AuthUIState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isLoggedIn, isLoggedIn) || other.isLoggedIn == isLoggedIn)&&(identical(other.isWaitingForConnection, isWaitingForConnection) || other.isWaitingForConnection == isWaitingForConnection)&&(identical(other.domain, domain) || other.domain == domain)&&(identical(other.callbackUrl, callbackUrl) || other.callbackUrl == callbackUrl)&&(identical(other.stateParameter, stateParameter) || other.stateParameter == stateParameter)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&(identical(other.code, code) || other.code == code)&&(identical(other.error, error) || other.error == error)&&(identical(other.isDomainTrusted, isDomainTrusted) || other.isDomainTrusted == isDomainTrusted)&&(identical(other.domainName, domainName) || other.domainName == domainName));
}
@override
int get hashCode => Object.hash(runtimeType,isLoading,isLoggedIn,isWaitingForConnection,domain,callbackUrl,stateParameter,nonce,code,error,isDomainTrusted,domainName);
@override
String toString() {
return 'AuthUIState(isLoading: $isLoading, isLoggedIn: $isLoggedIn, isWaitingForConnection: $isWaitingForConnection, domain: $domain, callbackUrl: $callbackUrl, stateParameter: $stateParameter, nonce: $nonce, code: $code, error: $error, isDomainTrusted: $isDomainTrusted, domainName: $domainName)';
}
}
/// @nodoc
abstract mixin class _$AuthUIStateCopyWith<$Res> implements $AuthUIStateCopyWith<$Res> {
factory _$AuthUIStateCopyWith(_AuthUIState value, $Res Function(_AuthUIState) _then) = __$AuthUIStateCopyWithImpl;
@override @useResult
$Res call({
bool isLoading, bool isLoggedIn, bool isWaitingForConnection, String? domain, String? callbackUrl, String? stateParameter, String? nonce, String? code, String? error, bool isDomainTrusted, String? domainName
});
}
/// @nodoc
class __$AuthUIStateCopyWithImpl<$Res>
implements _$AuthUIStateCopyWith<$Res> {
__$AuthUIStateCopyWithImpl(this._self, this._then);
final _AuthUIState _self;
final $Res Function(_AuthUIState) _then;
/// Create a copy of AuthUIState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? isLoading = null,Object? isLoggedIn = null,Object? isWaitingForConnection = null,Object? domain = freezed,Object? callbackUrl = freezed,Object? stateParameter = freezed,Object? nonce = freezed,Object? code = freezed,Object? error = freezed,Object? isDomainTrusted = null,Object? domainName = freezed,}) {
return _then(_AuthUIState(
isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,isLoggedIn: null == isLoggedIn ? _self.isLoggedIn : isLoggedIn // ignore: cast_nullable_to_non_nullable
as bool,isWaitingForConnection: null == isWaitingForConnection ? _self.isWaitingForConnection : isWaitingForConnection // ignore: cast_nullable_to_non_nullable
as bool,domain: freezed == domain ? _self.domain : domain // ignore: cast_nullable_to_non_nullable
as String?,callbackUrl: freezed == callbackUrl ? _self.callbackUrl : callbackUrl // ignore: cast_nullable_to_non_nullable
as String?,stateParameter: freezed == stateParameter ? _self.stateParameter : stateParameter // ignore: cast_nullable_to_non_nullable
as String?,nonce: freezed == nonce ? _self.nonce : nonce // ignore: cast_nullable_to_non_nullable
as String?,code: freezed == code ? _self.code : code // ignore: cast_nullable_to_non_nullable
as String?,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable
as String?,isDomainTrusted: null == isDomainTrusted ? _self.isDomainTrusted : isDomainTrusted // ignore: cast_nullable_to_non_nullable
as bool,domainName: freezed == domainName ? _self.domainName : domainName // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
// dart format on

View File

@@ -0,0 +1,133 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'auth_ui_model.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(AuthUIModel)
final authUIModelProvider = AuthUIModelFamily._();
final class AuthUIModelProvider
extends $NotifierProvider<AuthUIModel, AuthUIState> {
AuthUIModelProvider._({
required AuthUIModelFamily super.from,
required ({String? callbackUrl, String? stateParameter, String? nonce})
super.argument,
}) : super(
retry: null,
name: r'authUIModelProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$authUIModelHash();
@override
String toString() {
return r'authUIModelProvider'
''
'$argument';
}
@$internal
@override
AuthUIModel create() => AuthUIModel();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(AuthUIState value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<AuthUIState>(value),
);
}
@override
bool operator ==(Object other) {
return other is AuthUIModelProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$authUIModelHash() => r'485bf56e488ba01cd1371131e6d92077c76176df';
final class AuthUIModelFamily extends $Family
with
$ClassFamilyOverride<
AuthUIModel,
AuthUIState,
AuthUIState,
AuthUIState,
({String? callbackUrl, String? stateParameter, String? nonce})
> {
AuthUIModelFamily._()
: super(
retry: null,
name: r'authUIModelProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
AuthUIModelProvider call({
String? callbackUrl,
String? stateParameter,
String? nonce,
}) => AuthUIModelProvider._(
argument: (
callbackUrl: callbackUrl,
stateParameter: stateParameter,
nonce: nonce,
),
from: this,
);
@override
String toString() => r'authUIModelProvider';
}
abstract class _$AuthUIModel extends $Notifier<AuthUIState> {
late final _$args =
ref.$arg
as ({String? callbackUrl, String? stateParameter, String? nonce});
String? get callbackUrl => _$args.callbackUrl;
String? get stateParameter => _$args.stateParameter;
String? get nonce => _$args.nonce;
AuthUIState build({
String? callbackUrl,
String? stateParameter,
String? nonce,
});
@$mustCallSuper
@override
void runBuild() {
final ref = this.ref as $Ref<AuthUIState, AuthUIState>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<AuthUIState, AuthUIState>,
AuthUIState,
Object?,
Object?
>;
element.handleCreate(
ref,
() => build(
callbackUrl: _$args.callbackUrl,
stateParameter: _$args.stateParameter,
nonce: _$args.nonce,
),
);
}
}

View File

@@ -36,17 +36,9 @@ class GuideUI extends HookConsumerWidget {
alignment: AlignmentDirectional.centerStart,
child: Row(
children: [
Image.asset(
"assets/app_logo_mini.png",
width: 20,
height: 20,
fit: BoxFit.cover,
),
Image.asset("assets/app_logo_mini.png", width: 20, height: 20, fit: BoxFit.cover),
const SizedBox(width: 12),
Text(
S.current.app_index_version_info(
ConstConf.appVersion, ConstConf.isMSE ? "" : " Dev"),
)
Text(S.current.app_index_version_info(ConstConf.appVersion, ConstConf.isMSE ? "" : " Dev")),
],
),
),
@@ -56,42 +48,34 @@ class GuideUI extends HookConsumerWidget {
children: [
Image.asset("assets/app_logo.png", width: 192, height: 192),
SizedBox(height: 12),
Text(
S.current.guide_title_welcome,
style: TextStyle(
fontSize: 38,
),
),
Text(S.current.guide_title_welcome, style: TextStyle(fontSize: 38)),
SizedBox(height: 24),
Text(S.current.guide_info_check_settings),
SizedBox(height: 32),
Container(
padding: EdgeInsets.symmetric(horizontal: 32),
child: Row(
children: [
Expanded(
child: Column(
children: [
makeGameLauncherPathSelect(
context, toolsModel, toolsState, settingModel),
const SizedBox(height: 12),
makeGamePathSelect(
context, toolsModel, toolsState, settingModel),
],
),
padding: EdgeInsets.symmetric(horizontal: 32),
child: Row(
children: [
Expanded(
child: Column(
children: [
makeGameLauncherPathSelect(context, toolsModel, toolsState, settingModel),
const SizedBox(height: 12),
makeGamePathSelect(context, toolsModel, toolsState, settingModel),
],
),
SizedBox(width: 12),
Button(
onPressed: () => toolsModel.reScanPath(context,
checkActive: true, skipToast: true),
child: const Padding(
padding: EdgeInsets.only(
top: 30, bottom: 30, left: 12, right: 12),
child: Icon(FluentIcons.refresh),
),
),
SizedBox(width: 12),
Button(
onPressed: () => toolsModel.reScanPath(context, checkActive: true, skipToast: true),
child: const Padding(
padding: EdgeInsets.only(top: 30, bottom: 30, left: 12, right: 12),
child: Icon(FluentIcons.refresh),
),
],
)),
),
],
),
),
SizedBox(height: 12),
Padding(
padding: const EdgeInsets.only(right: 32, left: 32),
@@ -100,9 +84,7 @@ class GuideUI extends HookConsumerWidget {
Expanded(
child: Text(
S.current.guide_info_game_download_note,
style: TextStyle(
fontSize: 12,
color: Colors.white.withValues(alpha: .6)),
style: TextStyle(fontSize: 12, color: Colors.white.withValues(alpha: .6)),
textAlign: TextAlign.end,
),
),
@@ -115,8 +97,7 @@ class GuideUI extends HookConsumerWidget {
Spacer(),
Button(
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Text(S.current.guide_action_get_help),
),
onPressed: () {
@@ -126,26 +107,25 @@ class GuideUI extends HookConsumerWidget {
SizedBox(width: 24),
FilledButton(
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Text(S.current.guide_action_complete_setup),
),
onPressed: () async {
if (toolsState.rsiLauncherInstallPaths.isEmpty) {
final ok = await showConfirmDialogs(
context,
S.current.guide_dialog_confirm_complete_setup,
Text(S.current
.guide_action_info_no_launcher_path_warning));
context,
S.current.guide_dialog_confirm_complete_setup,
Text(S.current.guide_action_info_no_launcher_path_warning),
);
if (!ok) return;
}
if (toolsState.scInstallPaths.isEmpty) {
if (!context.mounted) return;
final ok = await showConfirmDialogs(
context,
S.current.guide_dialog_confirm_complete_setup,
Text(S
.current.guide_action_info_no_game_path_warning));
context,
S.current.guide_dialog_confirm_complete_setup,
Text(S.current.guide_action_info_no_game_path_warning),
);
if (!ok) return;
}
final appConf = await Hive.openBox("app_conf");
@@ -164,8 +144,12 @@ class GuideUI extends HookConsumerWidget {
);
}
Widget makeGameLauncherPathSelect(BuildContext context, ToolsUIModel model,
ToolsUIState state, SettingsUIModel settingModel) {
Widget makeGameLauncherPathSelect(
BuildContext context,
ToolsUIModel model,
ToolsUIState state,
SettingsUIModel settingModel,
) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
@@ -177,13 +161,7 @@ class GuideUI extends HookConsumerWidget {
child: ComboBox<String>(
isExpanded: true,
value: state.rsiLauncherInstalledPath,
items: [
for (final path in state.rsiLauncherInstallPaths)
ComboBoxItem(
value: path,
child: Text(path),
)
],
items: [for (final path in state.rsiLauncherInstallPaths) ComboBoxItem(value: path, child: Text(path))],
onChanged: (v) {
model.onChangeLauncherPath(v!);
},
@@ -192,10 +170,7 @@ class GuideUI extends HookConsumerWidget {
),
const SizedBox(width: 8),
Button(
child: const Padding(
padding: EdgeInsets.all(6),
child: Icon(FluentIcons.folder_search),
),
child: const Padding(padding: EdgeInsets.all(6), child: Icon(FluentIcons.folder_search)),
onPressed: () async {
await settingModel.setLauncherPath(context);
if (!context.mounted) return;
@@ -206,8 +181,12 @@ class GuideUI extends HookConsumerWidget {
);
}
Widget makeGamePathSelect(BuildContext context, ToolsUIModel model,
ToolsUIState state, SettingsUIModel settingModel) {
Widget makeGamePathSelect(
BuildContext context,
ToolsUIModel model,
ToolsUIState state,
SettingsUIModel settingModel,
) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
@@ -219,13 +198,7 @@ class GuideUI extends HookConsumerWidget {
child: ComboBox<String>(
isExpanded: true,
value: state.scInstalledPath,
items: [
for (final path in state.scInstallPaths)
ComboBoxItem(
value: path,
child: Text(path),
)
],
items: [for (final path in state.scInstallPaths) ComboBoxItem(value: path, child: Text(path))],
onChanged: (v) {
model.onChangeGamePath(v!);
},
@@ -234,15 +207,13 @@ class GuideUI extends HookConsumerWidget {
),
const SizedBox(width: 8),
Button(
child: const Padding(
padding: EdgeInsets.all(6),
child: Icon(FluentIcons.folder_search),
),
onPressed: () async {
await settingModel.setGamePath(context);
if (!context.mounted) return;
model.reScanPath(context, checkActive: true, skipToast: true);
})
child: const Padding(padding: EdgeInsets.all(6), child: Icon(FluentIcons.folder_search)),
onPressed: () async {
await settingModel.setGamePath(context);
if (!context.mounted) return;
model.reScanPath(context, checkActive: true, skipToast: true);
},
),
],
);
}

View File

@@ -216,7 +216,8 @@ class HomeGameLoginUIModel extends _$HomeGameLoginUIModel {
}
String getChannelID(String installPath) {
if (installPath.endsWith("\\LIVE")) {
final pathLower = installPath.platformPath.toLowerCase();
if (pathLower.endsWith('\\live'.platformPath)) {
return "LIVE";
}
return "PTU";

View File

@@ -10,11 +10,11 @@ part of 'home_game_login_dialog_ui_model.dart';
// ignore_for_file: type=lint, type=warning
@ProviderFor(HomeGameLoginUIModel)
const homeGameLoginUIModelProvider = HomeGameLoginUIModelProvider._();
final homeGameLoginUIModelProvider = HomeGameLoginUIModelProvider._();
final class HomeGameLoginUIModelProvider
extends $NotifierProvider<HomeGameLoginUIModel, HomeGameLoginState> {
const HomeGameLoginUIModelProvider._()
HomeGameLoginUIModelProvider._()
: super(
from: null,
argument: null,
@@ -42,14 +42,13 @@ final class HomeGameLoginUIModelProvider
}
String _$homeGameLoginUIModelHash() =>
r'd81831f54c6b1e98ea8a1e94b5e6049fe552996f';
r'ca905904d20a6b1956fee04dcb501f0d1c19f86b';
abstract class _$HomeGameLoginUIModel extends $Notifier<HomeGameLoginState> {
HomeGameLoginState build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<HomeGameLoginState, HomeGameLoginState>;
final element =
ref.element
@@ -59,6 +58,6 @@ abstract class _$HomeGameLoginUIModel extends $Notifier<HomeGameLoginState> {
Object?,
Object?
>;
element.handleValue(ref, created);
element.handleCreate(ref, build);
}
}

View File

@@ -10,11 +10,11 @@ part of 'home_downloader_ui_model.dart';
// ignore_for_file: type=lint, type=warning
@ProviderFor(HomeDownloaderUIModel)
const homeDownloaderUIModelProvider = HomeDownloaderUIModelProvider._();
final homeDownloaderUIModelProvider = HomeDownloaderUIModelProvider._();
final class HomeDownloaderUIModelProvider
extends $NotifierProvider<HomeDownloaderUIModel, HomeDownloaderUIState> {
const HomeDownloaderUIModelProvider._()
HomeDownloaderUIModelProvider._()
: super(
from: null,
argument: null,
@@ -50,7 +50,6 @@ abstract class _$HomeDownloaderUIModel
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<HomeDownloaderUIState, HomeDownloaderUIState>;
final element =
ref.element
@@ -60,6 +59,6 @@ abstract class _$HomeDownloaderUIModel
Object?,
Object?
>;
element.handleValue(ref, created);
element.handleCreate(ref, build);
}
}

View File

@@ -10,11 +10,11 @@ part of 'game_doctor_ui_model.dart';
// ignore_for_file: type=lint, type=warning
@ProviderFor(HomeGameDoctorUIModel)
const homeGameDoctorUIModelProvider = HomeGameDoctorUIModelProvider._();
final homeGameDoctorUIModelProvider = HomeGameDoctorUIModelProvider._();
final class HomeGameDoctorUIModelProvider
extends $NotifierProvider<HomeGameDoctorUIModel, HomeGameDoctorState> {
const HomeGameDoctorUIModelProvider._()
HomeGameDoctorUIModelProvider._()
: super(
from: null,
argument: null,
@@ -49,7 +49,6 @@ abstract class _$HomeGameDoctorUIModel extends $Notifier<HomeGameDoctorState> {
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<HomeGameDoctorState, HomeGameDoctorState>;
final element =
ref.element
@@ -59,6 +58,6 @@ abstract class _$HomeGameDoctorUIModel extends $Notifier<HomeGameDoctorState> {
Object?,
Object?
>;
element.handleValue(ref, created);
element.handleCreate(ref, build);
}
}

View File

@@ -247,9 +247,21 @@ class HomeUIModel extends _$HomeUIModel {
final fixedList = list.map((item) {
if (item.time == null) return item;
final itemDateTime = DateTime.fromMillisecondsSinceEpoch(item.time!);
final itemDatePlusSeven = itemDateTime.add(const Duration(days: 7));
if (itemDatePlusSeven.isBefore(now)) {
final nextDate = DateTime(
// 计算今年的节日日期
final thisYearDate = DateTime(
now.year,
itemDateTime.month,
itemDateTime.day,
itemDateTime.hour,
itemDateTime.minute,
itemDateTime.second,
);
// 如果今年的节日日期 + 7天已经过了使用明年的日期
final thisYearDatePlusSeven = thisYearDate.add(const Duration(days: 7));
if (thisYearDatePlusSeven.isBefore(now)) {
final nextYearDate = DateTime(
now.year + 1,
itemDateTime.month,
itemDateTime.day,
@@ -257,11 +269,13 @@ class HomeUIModel extends _$HomeUIModel {
itemDateTime.minute,
itemDateTime.second,
);
final newTimestamp = (nextDate.millisecondsSinceEpoch).round();
final newTimestamp = (nextYearDate.millisecondsSinceEpoch).round();
return CountdownFestivalItemData(name: item.name, time: newTimestamp, icon: item.icon);
}
return item;
// 否则使用今年的日期
final newTimestamp = (thisYearDate.millisecondsSinceEpoch).round();
return CountdownFestivalItemData(name: item.name, time: newTimestamp, icon: item.icon);
}).toList();
// Sort by time (ascending order - nearest festival first)

View File

@@ -10,11 +10,11 @@ part of 'home_ui_model.dart';
// ignore_for_file: type=lint, type=warning
@ProviderFor(HomeUIModel)
const homeUIModelProvider = HomeUIModelProvider._();
final homeUIModelProvider = HomeUIModelProvider._();
final class HomeUIModelProvider
extends $NotifierProvider<HomeUIModel, HomeUIModelState> {
const HomeUIModelProvider._()
HomeUIModelProvider._()
: super(
from: null,
argument: null,
@@ -41,14 +41,13 @@ final class HomeUIModelProvider
}
}
String _$homeUIModelHash() => r'c3affcfc99a0cd7e3587cc58f02dbdc24c4f8ae7';
String _$homeUIModelHash() => r'9e16e9021ef2b68403f5d5ce21829d1fee0f4c8a';
abstract class _$HomeUIModel extends $Notifier<HomeUIModelState> {
HomeUIModelState build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<HomeUIModelState, HomeUIModelState>;
final element =
ref.element
@@ -58,6 +57,6 @@ abstract class _$HomeUIModel extends $Notifier<HomeUIModelState> {
Object?,
Object?
>;
element.handleValue(ref, created);
element.handleCreate(ref, build);
}
}

View File

@@ -10,12 +10,12 @@ part of 'input_method_dialog_ui_model.dart';
// ignore_for_file: type=lint, type=warning
@ProviderFor(InputMethodDialogUIModel)
const inputMethodDialogUIModelProvider = InputMethodDialogUIModelProvider._();
final inputMethodDialogUIModelProvider = InputMethodDialogUIModelProvider._();
final class InputMethodDialogUIModelProvider
extends
$NotifierProvider<InputMethodDialogUIModel, InputMethodDialogUIState> {
const InputMethodDialogUIModelProvider._()
InputMethodDialogUIModelProvider._()
: super(
from: null,
argument: null,
@@ -51,7 +51,6 @@ abstract class _$InputMethodDialogUIModel
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref =
this.ref as $Ref<InputMethodDialogUIState, InputMethodDialogUIState>;
final element =
@@ -62,16 +61,16 @@ abstract class _$InputMethodDialogUIModel
Object?,
Object?
>;
element.handleValue(ref, created);
element.handleCreate(ref, build);
}
}
@ProviderFor(OnnxTranslation)
const onnxTranslationProvider = OnnxTranslationFamily._();
final onnxTranslationProvider = OnnxTranslationFamily._();
final class OnnxTranslationProvider
extends $NotifierProvider<OnnxTranslation, bool> {
const OnnxTranslationProvider._({
OnnxTranslationProvider._({
required OnnxTranslationFamily super.from,
required (String, String, bool) super.argument,
}) : super(
@@ -126,7 +125,7 @@ final class OnnxTranslationFamily extends $Family
bool,
(String, String, bool)
> {
const OnnxTranslationFamily._()
OnnxTranslationFamily._()
: super(
retry: null,
name: r'onnxTranslationProvider',
@@ -158,7 +157,6 @@ abstract class _$OnnxTranslation extends $Notifier<bool> {
@$mustCallSuper
@override
void runBuild() {
final created = build(_$args.$1, _$args.$2, _$args.$3);
final ref = this.ref as $Ref<bool, bool>;
final element =
ref.element
@@ -168,6 +166,6 @@ abstract class _$OnnxTranslation extends $Notifier<bool> {
Object?,
Object?
>;
element.handleValue(ref, created);
element.handleCreate(ref, () => build(_$args.$1, _$args.$2, _$args.$3));
}
}

View File

@@ -10,11 +10,11 @@ part of 'server.dart';
// ignore_for_file: type=lint, type=warning
@ProviderFor(InputMethodServer)
const inputMethodServerProvider = InputMethodServerProvider._();
final inputMethodServerProvider = InputMethodServerProvider._();
final class InputMethodServerProvider
extends $NotifierProvider<InputMethodServer, InputMethodServerState> {
const InputMethodServerProvider._()
InputMethodServerProvider._()
: super(
from: null,
argument: null,
@@ -48,7 +48,6 @@ abstract class _$InputMethodServer extends $Notifier<InputMethodServerState> {
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref =
this.ref as $Ref<InputMethodServerState, InputMethodServerState>;
final element =
@@ -59,6 +58,6 @@ abstract class _$InputMethodServer extends $Notifier<InputMethodServerState> {
Object?,
Object?
>;
element.handleValue(ref, created);
element.handleCreate(ref, build);
}
}

View File

@@ -10,11 +10,11 @@ part of 'server_qr_dialog_ui.dart';
// ignore_for_file: type=lint, type=warning
@ProviderFor(ServerQrState)
const serverQrStateProvider = ServerQrStateProvider._();
final serverQrStateProvider = ServerQrStateProvider._();
final class ServerQrStateProvider
extends $NotifierProvider<ServerQrState, bool> {
const ServerQrStateProvider._()
ServerQrStateProvider._()
: super(
from: null,
argument: null,
@@ -48,7 +48,6 @@ abstract class _$ServerQrState extends $Notifier<bool> {
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<bool, bool>;
final element =
ref.element
@@ -58,6 +57,6 @@ abstract class _$ServerQrState extends $Notifier<bool> {
Object?,
Object?
>;
element.handleValue(ref, created);
element.handleCreate(ref, build);
}
}

View File

@@ -10,7 +10,7 @@ part of 'advanced_localization_ui_model.dart';
// ignore_for_file: type=lint, type=warning
@ProviderFor(AdvancedLocalizationUIModel)
const advancedLocalizationUIModelProvider =
final advancedLocalizationUIModelProvider =
AdvancedLocalizationUIModelProvider._();
final class AdvancedLocalizationUIModelProvider
@@ -19,7 +19,7 @@ final class AdvancedLocalizationUIModelProvider
AdvancedLocalizationUIModel,
AdvancedLocalizationUIState
> {
const AdvancedLocalizationUIModelProvider._()
AdvancedLocalizationUIModelProvider._()
: super(
from: null,
argument: null,
@@ -55,7 +55,6 @@ abstract class _$AdvancedLocalizationUIModel
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref =
this.ref
as $Ref<AdvancedLocalizationUIState, AdvancedLocalizationUIState>;
@@ -70,6 +69,6 @@ abstract class _$AdvancedLocalizationUIModel
Object?,
Object?
>;
element.handleValue(ref, created);
element.handleCreate(ref, build);
}
}

View File

@@ -30,13 +30,9 @@ class LocalizationDialogUI extends HookConsumerWidget {
return ContentDialog(
title: makeTitle(context, model, state),
constraints: BoxConstraints(
maxWidth: MediaQuery
.of(context)
.size
.width * .7, minHeight: MediaQuery
.of(context)
.size
.height * .9),
maxWidth: MediaQuery.of(context).size.width * .7,
minHeight: MediaQuery.of(context).size.height * .9,
),
content: Padding(
padding: const EdgeInsets.only(left: 12, right: 12, top: 12),
child: SingleChildScrollView(
@@ -44,132 +40,128 @@ class LocalizationDialogUI extends HookConsumerWidget {
children: [
AnimatedSize(
duration: const Duration(milliseconds: 130),
child: state.patchStatus?.key == true &&
state.patchStatus?.value == S.current.home_action_info_game_built_in
child:
state.patchStatus?.key == true &&
state.patchStatus?.value == S.current.home_action_info_game_built_in
? Padding(
padding: const EdgeInsets.only(bottom: 12),
child: InfoBar(
title: Text(S.current.home_action_info_warning),
content: Text(S.current.localization_info_machine_translation_warning),
severity: InfoBarSeverity.info,
style: InfoBarThemeData(decoration: (severity) {
return const BoxDecoration(color: Color.fromRGBO(155, 7, 7, 1.0));
}, iconColor: (severity) {
return Colors.white;
}),
),
)
: SizedBox(
width: MediaQuery
.of(context)
.size
.width,
),
padding: const EdgeInsets.only(bottom: 12),
child: InfoBar(
title: Text(S.current.home_action_info_warning),
content: Text(S.current.localization_info_machine_translation_warning),
severity: InfoBarSeverity.info,
style: InfoBarThemeData(
decoration: (severity) {
return const BoxDecoration(color: Color.fromRGBO(155, 7, 7, 1.0));
},
iconColor: (severity) {
return Colors.white;
},
),
),
)
: SizedBox(width: MediaQuery.of(context).size.width),
),
makeToolsListContainer(context, model, state),
makeListContainer(
S.current.localization_info_translation,
[
if (state.patchStatus == null)
makeLoading(context)
else
...[
const SizedBox(height: 6),
Row(
children: [
Center(
child: Text(S.current.localization_info_enabled(
LocalizationUIModel.languageSupport[state.selectedLanguage] ?? "")),
),
const Spacer(),
ToggleSwitch(
checked: state.patchStatus?.key == true,
onChanged: model.updateLangCfg,
)
],
makeListContainer(S.current.localization_info_translation, [
if (state.patchStatus == null)
makeLoading(context)
else ...[
const SizedBox(height: 6),
Row(
children: [
Center(
child: Text(
S.current.localization_info_enabled(
LocalizationUIModel.languageSupport[state.selectedLanguage] ?? "",
),
),
const SizedBox(height: 12),
Row(
),
const Spacer(),
ToggleSwitch(checked: state.patchStatus?.key == true, onChanged: model.updateLangCfg),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: Row(
children: [
Expanded(
child: Row(
children: [
Text(S.current.localization_info_installed_version(
"${state.patchStatus?.value ?? ""} ${(state.isInstalledAdvanced ?? false) ? S
.current.home_localization_msg_version_advanced : ""}")),
SizedBox(width: 24),
if (state.installedCommunityInputMethodSupportVersion != null)
Text(
S.current.input_method_community_input_method_support_version(
state.installedCommunityInputMethodSupportVersion ?? "?"),
)
],
Text(
S.current.localization_info_installed_version(
"${state.patchStatus?.value ?? ""} ${(state.isInstalledAdvanced ?? false) ? S.current.home_localization_msg_version_advanced : ""}",
),
),
if (state.patchStatus?.value != S.current.home_action_info_game_built_in)
Row(
children: [
Button(
onPressed: model.goFeedback,
child: Padding(
padding: const EdgeInsets.all(4),
child: Row(
children: [
const Icon(FluentIcons.feedback),
const SizedBox(width: 6),
Text(S.current.localization_action_translation_feedback),
],
),
)),
const SizedBox(width: 16),
Button(
onPressed: model.doDelIniFile(),
child: Padding(
padding: const EdgeInsets.all(4),
child: Row(
children: [
const Icon(FluentIcons.delete),
const SizedBox(width: 6),
Text(S.current.localization_action_uninstall_translation),
],
),
)),
],
SizedBox(width: 24),
if (state.installedCommunityInputMethodSupportVersion != null)
Text(
S.current.input_method_community_input_method_support_version(
state.installedCommunityInputMethodSupportVersion ?? "?",
),
),
],
),
const SizedBox(height: 12),
Container(
color: Colors.white.withValues(alpha: .1),
height: 1,
),
const SizedBox(height: 12),
if (state.apiLocalizationData == null)
makeLoading(context)
else
if (state.apiLocalizationData!.isEmpty)
Center(
child: Text(
S.current.localization_info_no_translation_available,
style: TextStyle(fontSize: 13, color: Colors.white.withValues(alpha: .8)),
),
if (state.patchStatus?.value != S.current.home_action_info_game_built_in)
Row(
children: [
Button(
onPressed: model.goFeedback,
child: Padding(
padding: const EdgeInsets.all(4),
child: Row(
children: [
const Icon(FluentIcons.feedback),
const SizedBox(width: 6),
Text(S.current.localization_action_translation_feedback),
],
),
),
)
else
AlignedGridView.count(
crossAxisCount: 2,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
itemBuilder: (BuildContext context, int index) {
final item = state.apiLocalizationData!.entries.elementAt(index);
return makeRemoteList(context, model, item, state, index);
},
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: state.apiLocalizationData?.length ?? 0,
)
],
],
context),
),
const SizedBox(width: 16),
Button(
onPressed: model.doDelIniFile(),
child: Padding(
padding: const EdgeInsets.all(4),
child: Row(
children: [
const Icon(FluentIcons.delete),
const SizedBox(width: 6),
Text(S.current.localization_action_uninstall_translation),
],
),
),
),
],
),
],
),
const SizedBox(height: 12),
Container(color: Colors.white.withValues(alpha: .1), height: 1),
const SizedBox(height: 12),
if (state.apiLocalizationData == null)
makeLoading(context)
else if (state.apiLocalizationData!.isEmpty)
Center(
child: Text(
S.current.localization_info_no_translation_available,
style: TextStyle(fontSize: 13, color: Colors.white.withValues(alpha: .8)),
),
)
else
AlignedGridView.count(
crossAxisCount: 2,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
itemBuilder: (BuildContext context, int index) {
final item = state.apiLocalizationData!.entries.elementAt(index);
return makeRemoteList(context, model, item, state, index);
},
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: state.apiLocalizationData?.length ?? 0,
),
],
], context),
],
),
),
@@ -177,11 +169,16 @@ class LocalizationDialogUI extends HookConsumerWidget {
);
}
Widget makeRemoteList(BuildContext context, LocalizationUIModel model, MapEntry<String, ScLocalizationData> item,
LocalizationUIState state, int index) {
Widget makeRemoteList(
BuildContext context,
LocalizationUIModel model,
MapEntry<String, ScLocalizationData> item,
LocalizationUIState state,
int index,
) {
final isWorking = state.workingVersion.isNotEmpty;
final isMineWorking = state.workingVersion == item.key;
final isInstalled = state.patchStatus?.value == item.key;
final isInstalled = state.patchStatus?.value == item.key || state.patchStatus?.value == item.value.versionName;
final isItemEnabled = ((item.value.enable ?? false));
final tapDisabled = isInstalled || isWorking || !isItemEnabled || isMineWorking;
return GridItemAnimator(
@@ -207,10 +204,7 @@ class LocalizationDialogUI extends HookConsumerWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"${item.value.info}",
style: const TextStyle(fontSize: 19),
),
Text("${item.value.info}", style: const TextStyle(fontSize: 19)),
const SizedBox(height: 4),
Text(
S.current.localization_info_version_number(item.value.versionName ?? ""),
@@ -230,40 +224,30 @@ class LocalizationDialogUI extends HookConsumerWidget {
),
),
if (isMineWorking)
const Padding(
padding: EdgeInsets.only(right: 12),
child: ProgressRing(),
)
else
...[
Icon(
isInstalled
? FluentIcons.check_mark
: isItemEnabled
? FluentIcons.download
: FluentIcons.disable_updates,
color: Colors.white.withValues(alpha: .8),
size: 18,
),
const SizedBox(width: 6),
Text(
isInstalled
? S.current.localization_info_installed
: (isItemEnabled
? S.current.localization_action_install
: S.current.localization_info_unavailable),
style: TextStyle(
color: Colors.white.withValues(alpha: .8),
),
),
const SizedBox(width: 6),
if ((!isInstalled) && isItemEnabled)
Icon(
FluentIcons.chevron_right,
size: 14,
color: Colors.white.withValues(alpha: .6),
)
]
const Padding(padding: EdgeInsets.only(right: 12), child: ProgressRing())
else ...[
Icon(
isInstalled
? FluentIcons.check_mark
: isItemEnabled
? FluentIcons.download
: FluentIcons.disable_updates,
color: Colors.white.withValues(alpha: .8),
size: 18,
),
const SizedBox(width: 6),
Text(
isInstalled
? S.current.localization_info_installed
: (isItemEnabled
? S.current.localization_action_install
: S.current.localization_info_unavailable),
style: TextStyle(color: Colors.white.withValues(alpha: .8)),
),
const SizedBox(width: 6),
if ((!isInstalled) && isItemEnabled)
Icon(FluentIcons.chevron_right, size: 14, color: Colors.white.withValues(alpha: .6)),
],
],
),
if (item.value.note != null) ...[
@@ -272,10 +256,7 @@ class LocalizationDialogUI extends HookConsumerWidget {
"${item.value.note}",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white.withValues(alpha: .4),
fontSize: 13,
),
style: TextStyle(color: Colors.white.withValues(alpha: .4), fontSize: 13),
),
],
],
@@ -286,16 +267,20 @@ class LocalizationDialogUI extends HookConsumerWidget {
);
}
Widget makeListContainer(String title, List<Widget> children, BuildContext context,
{List<Widget> actions = const [], bool gridViewMode = false, int gridViewCrossAxisCount = 2}) {
Widget makeListContainer(
String title,
List<Widget> children,
BuildContext context, {
List<Widget> actions = const [],
bool gridViewMode = false,
int gridViewCrossAxisCount = 2,
}) {
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: AnimatedSize(
duration: const Duration(milliseconds: 130),
child: Container(
decoration: BoxDecoration(color: FluentTheme
.of(context)
.cardColor, borderRadius: BorderRadius.circular(7)),
decoration: BoxDecoration(color: FluentTheme.of(context).cardColor, borderRadius: BorderRadius.circular(7)),
child: Padding(
padding: const EdgeInsets.only(top: 12, bottom: 12, left: 24, right: 24),
child: Column(
@@ -303,21 +288,13 @@ class LocalizationDialogUI extends HookConsumerWidget {
children: [
Row(
children: [
Text(
title,
style: const TextStyle(fontSize: 22),
),
Text(title, style: const TextStyle(fontSize: 22)),
const Spacer(),
if (actions.isNotEmpty) ...actions,
],
),
const SizedBox(
height: 6,
),
Container(
color: Colors.white.withValues(alpha: .1),
height: 1,
),
const SizedBox(height: 6),
Container(color: Colors.white.withValues(alpha: .1), height: 1),
const SizedBox(height: 12),
if (gridViewMode)
AlignedGridView.count(
@@ -332,7 +309,7 @@ class LocalizationDialogUI extends HookConsumerWidget {
itemCount: children.length,
)
else
...children
...children,
],
),
),
@@ -344,55 +321,46 @@ class LocalizationDialogUI extends HookConsumerWidget {
Widget makeTitle(BuildContext context, LocalizationUIModel model, LocalizationUIState state) {
return Row(
children: [
IconButton(
icon: const Icon(
FluentIcons.back,
size: 22,
),
onPressed: model.onBack(context)),
IconButton(icon: const Icon(FluentIcons.back, size: 22), onPressed: model.onBack(context)),
const SizedBox(width: 12),
Text(S.current.home_action_localization_management),
const SizedBox(width: 24),
Text(
"${model.getScInstallPath()}",
style: const TextStyle(fontSize: 13),
Expanded(
child: Text(
"${model.getScInstallPath()}",
style: const TextStyle(fontSize: 13),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
const Spacer(),
SizedBox(width: 24),
SizedBox(
height: 36,
child: Row(
children: [
Text(
S.current.localization_info_language,
style: const TextStyle(fontSize: 16),
),
Text(S.current.localization_info_language, style: const TextStyle(fontSize: 16)),
const SizedBox(width: 12),
ComboBox<String>(
value: state.selectedLanguage,
items: [
for (final lang in LocalizationUIModel.languageSupport.entries)
ComboBoxItem(
value: lang.key,
child: Text(lang.value),
)
ComboBoxItem(value: lang.key, child: Text(lang.value)),
],
onChanged: state.workingVersion.isNotEmpty
? null
: (v) {
if (v == null) return;
model.selectLang(v);
},
)
if (v == null) return;
model.selectLang(v);
},
),
],
),
),
const SizedBox(width: 12),
Button(
onPressed: model.doRefresh(),
child: const Padding(
padding: EdgeInsets.all(6),
child: Icon(FluentIcons.refresh),
)),
onPressed: model.doRefresh(),
child: const Padding(padding: EdgeInsets.all(6), child: Icon(FluentIcons.refresh)),
),
],
);
}
@@ -400,85 +368,77 @@ class LocalizationDialogUI extends HookConsumerWidget {
Widget makeToolsListContainer(BuildContext context, LocalizationUIModel model, LocalizationUIState state) {
final toolsMenu = {
"launcher_mod": (
const Icon(FluentIcons.c_plus_plus, size: 24),
(S.current.home_localization_action_rsi_launcher_localization),
),
"advanced": (
const Icon(FluentIcons.queue_advanced, size: 24),
(S.current.home_localization_action_advanced),
const Icon(FluentIcons.c_plus_plus, size: 24),
(S.current.home_localization_action_rsi_launcher_localization),
),
"advanced": (const Icon(FluentIcons.queue_advanced, size: 24), (S.current.home_localization_action_advanced)),
"custom_files": (
const Icon(FluentIcons.custom_activity, size: 24),
(S.current.home_localization_action_install_customize),
const Icon(FluentIcons.custom_activity, size: 24),
(S.current.home_localization_action_install_customize),
),
};
final enableTap = state.workingVersion.isEmpty;
return makeListContainer(
S.current.home_localization_title_localization_tools,
[
for (final item in toolsMenu.entries)
Tilt(
disable: !enableTap,
shadowConfig: const ShadowConfig(maxIntensity: .3),
borderRadius: BorderRadius.circular(7),
child: GestureDetector(
onTap: enableTap
? () async {
switch (item.key) {
case "launcher_mod":
ToolsUIModel.rsiEnhance(context);
break;
case "advanced":
context.push("/index/advanced_localization");
break;
case "custom_files":
final sb = await showDialog(
context: context,
builder: (BuildContext context) => const LocalizationFromFileDialogUI(),
);
if (sb is (StringBuffer, bool)) {
if (!context.mounted) return;
await model.installFormString(
sb.$1,
S.current.localization_info_custom_files,
isEnableCommunityInputMethod: sb.$2,
context: context,
);
S.current.home_localization_title_localization_tools,
[
for (final item in toolsMenu.entries)
Tilt(
disable: !enableTap,
shadowConfig: const ShadowConfig(maxIntensity: .3),
borderRadius: BorderRadius.circular(7),
child: GestureDetector(
onTap: enableTap
? () async {
switch (item.key) {
case "launcher_mod":
ToolsUIModel.rsiEnhance(context);
break;
case "advanced":
context.push("/index/advanced_localization");
break;
case "custom_files":
final sb = await showDialog(
context: context,
builder: (BuildContext context) => const LocalizationFromFileDialogUI(),
);
if (sb is (StringBuffer, bool)) {
if (!context.mounted) return;
await model.installFormString(
sb.$1,
S.current.localization_info_custom_files,
isEnableCommunityInputMethod: sb.$2,
context: context,
);
}
break;
}
break;
}
}
: null,
child: Container(
decoration: BoxDecoration(
color: FluentTheme
.of(context)
.cardColor,
borderRadius: BorderRadius.circular(7),
),
padding: const EdgeInsets.all(12),
child: Row(
children: [
item.value.$1,
const SizedBox(width: 12),
Text(item.value.$2),
const SizedBox(width: 12),
const Spacer(),
Icon(
FluentIcons.chevron_right,
size: 14,
color: Colors.white.withValues(alpha: .6),
)
],
),
}
: null,
child: Container(
decoration: BoxDecoration(
color: FluentTheme.of(context).cardColor,
borderRadius: BorderRadius.circular(7),
),
padding: const EdgeInsets.all(12),
child: Row(
children: [
item.value.$1,
const SizedBox(width: 12),
Text(item.value.$2),
const SizedBox(width: 12),
const Spacer(),
Icon(FluentIcons.chevron_right, size: 14, color: Colors.white.withValues(alpha: .6)),
],
),
),
),
],
context,
gridViewMode: true,
gridViewCrossAxisCount: 3);
),
],
context,
gridViewMode: true,
gridViewCrossAxisCount: 3,
);
}
}

View File

@@ -48,13 +48,12 @@ abstract class LocalizationUIState with _$LocalizationUIState {
class LocalizationUIModel extends _$LocalizationUIModel {
static const languageSupport = {"chinese_(simplified)": NoL10n.langZHS, "chinese_(traditional)": NoL10n.langZHT};
Directory get _downloadDir => Directory("${appGlobalState.applicationSupportDir}\\Localizations");
Directory get _downloadDir => Directory("${appGlobalState.applicationSupportDir}\\Localizations".platformPath);
Directory getDownloadDir() => _downloadDir;
Directory get _scDataDir => Directory("${ref.read(homeUIModelProvider).scInstalledPath}\\data");
File get _cfgFile => File("${_scDataDir.absolute.path}\\system.cfg");
Directory get _scDataDir => Directory("${ref.read(homeUIModelProvider).scInstalledPath}\\data".platformPath);
File get _cfgFile => File("${_scDataDir.absolute.path}\\system.cfg".platformPath);
StreamSubscription? _customizeDirListenSub;
@@ -155,7 +154,7 @@ class LocalizationUIModel extends _$LocalizationUIModel {
}
void checkUserCfg(BuildContext context) async {
final userCfgFile = File("$_scInstallPath\\USER.cfg");
final userCfgFile = File("$_scInstallPath\\USER.cfg".platformPath);
if (await userCfgFile.exists()) {
final cfgString = await userCfgFile.readAsString();
if (cfgString.contains("g_language") && !cfgString.contains("g_language=${state.selectedLanguage}")) {
@@ -241,7 +240,9 @@ class LocalizationUIModel extends _$LocalizationUIModel {
VoidCallback? doDelIniFile() {
return () async {
final iniFile = File("${_scDataDir.absolute.path}\\Localization\\${state.selectedLanguage}\\global.ini");
final iniFile = File(
"${_scDataDir.absolute.path}\\Localization\\${state.selectedLanguage}\\global.ini".platformPath,
);
if (await iniFile.exists()) await iniFile.delete();
await updateLangCfg(false);
await _updateStatus();
@@ -249,7 +250,7 @@ class LocalizationUIModel extends _$LocalizationUIModel {
}
String getCustomizeFileName(String path) {
return path.split("\\").last;
return path.split("\\".platformPath).last;
}
Future<void> installFormString(
@@ -261,7 +262,9 @@ class LocalizationUIModel extends _$LocalizationUIModel {
BuildContext? context,
}) async {
dPrint("LocalizationUIModel -> installFormString $versionName");
final iniFile = File("${_scDataDir.absolute.path}\\Localization\\${state.selectedLanguage}\\global.ini");
final iniFile = File(
"${_scDataDir.absolute.path}\\Localization\\${state.selectedLanguage}\\global.ini".platformPath,
);
if (versionName.isNotEmpty) {
if (!globalIni.toString().endsWith("\n")) {
globalIni.write("\n");
@@ -323,7 +326,7 @@ class LocalizationUIModel extends _$LocalizationUIModel {
}
Future<Map<String, String>?> getCommunityInputMethodSupportData() async {
final iniPath = "${_scDataDir.absolute.path}\\Localization\\${state.selectedLanguage}\\global.ini";
final iniPath = "${_scDataDir.absolute.path}\\Localization\\${state.selectedLanguage}\\global.ini".platformPath;
final iniFile = File(iniPath);
if (!await iniFile.exists()) {
return {};
@@ -351,7 +354,7 @@ class LocalizationUIModel extends _$LocalizationUIModel {
}
Future<(String, String)> getIniContentWithoutCommunityInputMethodSupportData() async {
final iniPath = "${_scDataDir.absolute.path}\\Localization\\${state.selectedLanguage}\\global.ini";
final iniPath = "${_scDataDir.absolute.path}\\Localization\\${state.selectedLanguage}\\global.ini".platformPath;
final iniFile = File(iniPath);
if (!await iniFile.exists()) {
return ("", "");
@@ -383,7 +386,7 @@ class LocalizationUIModel extends _$LocalizationUIModel {
}) async {
AnalyticsApi.touch("install_localization");
final savePath = File("${_downloadDir.absolute.path}\\${value.versionName}.sclang");
final savePath = File("${_downloadDir.absolute.path}\\${value.versionName}.sclang".platformPath);
try {
state = state.copyWith(workingVersion: value.versionName!);
if (!await savePath.exists()) {
@@ -488,7 +491,7 @@ class LocalizationUIModel extends _$LocalizationUIModel {
}
Future<void> _updateStatus() async {
final iniPath = "${_scDataDir.absolute.path}\\Localization\\${state.selectedLanguage}\\global.ini";
final iniPath = "${_scDataDir.absolute.path}\\Localization\\${state.selectedLanguage}\\global.ini".platformPath;
final patchStatus = MapEntry(
await _getLangCfgEnableLang(lang: state.selectedLanguage!),
await _getInstalledIniVersion(iniPath),
@@ -535,7 +538,7 @@ class LocalizationUIModel extends _$LocalizationUIModel {
if (gamePath.isEmpty) {
gamePath = _scInstallPath;
}
final cfgFile = File("${_scDataDir.absolute.path}\\system.cfg");
final cfgFile = File("${_scDataDir.absolute.path}\\system.cfg".platformPath);
if (!await cfgFile.exists()) return false;
final str = (await cfgFile.readAsString()).replaceAll(" ", "");
return str.contains("sys_languages=$lang") &&
@@ -573,13 +576,13 @@ class LocalizationUIModel extends _$LocalizationUIModel {
for (var scInstallPath in homeState.scInstallPaths) {
// 读取游戏安装文件夹
final scDataDir = Directory("$scInstallPath\\data\\Localization");
final scDataDir = Directory("$scInstallPath\\data\\Localization".platformPath);
// 扫描目录确认已安装的语言
final dirList = await scDataDir.list().toList();
for (var element in dirList) {
for (var lang in languageSupport.keys) {
if (element.path.contains(lang) && await _getLangCfgEnableLang(lang: lang, gamePath: scInstallPath)) {
final installedVersion = await _getInstalledIniVersion("${element.path}\\global.ini");
final installedVersion = await _getInstalledIniVersion("${element.path}\\global.ini".platformPath);
if (installedVersion == S.current.home_action_info_game_built_in ||
installedVersion == S.current.localization_info_custom_files) {
continue;
@@ -674,9 +677,11 @@ class LocalizationUIModel extends _$LocalizationUIModel {
child: Row(
children: [
Expanded(
child: Text(
item.value.note ?? S.current.home_localization_msg_no_note,
style: const TextStyle(fontSize: 15),
child: SelectionArea(
child: Text(
item.value.note ?? S.current.home_localization_msg_no_note,
style: const TextStyle(fontSize: 15),
),
),
),
],

View File

@@ -10,11 +10,11 @@ part of 'localization_ui_model.dart';
// ignore_for_file: type=lint, type=warning
@ProviderFor(LocalizationUIModel)
const localizationUIModelProvider = LocalizationUIModelProvider._();
final localizationUIModelProvider = LocalizationUIModelProvider._();
final class LocalizationUIModelProvider
extends $NotifierProvider<LocalizationUIModel, LocalizationUIState> {
const LocalizationUIModelProvider._()
LocalizationUIModelProvider._()
: super(
from: null,
argument: null,
@@ -42,14 +42,13 @@ final class LocalizationUIModelProvider
}
String _$localizationUIModelHash() =>
r'7b398d3b2ddd306ff8f328be39f28200fe8bf49e';
r'34bfb83eb271b16d24a10219bbf86fc4b873f9aa';
abstract class _$LocalizationUIModel extends $Notifier<LocalizationUIState> {
LocalizationUIState build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<LocalizationUIState, LocalizationUIState>;
final element =
ref.element
@@ -59,6 +58,6 @@ abstract class _$LocalizationUIModel extends $Notifier<LocalizationUIState> {
Object?,
Object?
>;
element.handleValue(ref, created);
element.handleCreate(ref, build);
}
}

View File

@@ -10,11 +10,11 @@ part of 'performance_ui_model.dart';
// ignore_for_file: type=lint, type=warning
@ProviderFor(HomePerformanceUIModel)
const homePerformanceUIModelProvider = HomePerformanceUIModelProvider._();
final homePerformanceUIModelProvider = HomePerformanceUIModelProvider._();
final class HomePerformanceUIModelProvider
extends $NotifierProvider<HomePerformanceUIModel, HomePerformanceUIState> {
const HomePerformanceUIModelProvider._()
HomePerformanceUIModelProvider._()
: super(
from: null,
argument: null,
@@ -50,7 +50,6 @@ abstract class _$HomePerformanceUIModel
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref =
this.ref as $Ref<HomePerformanceUIState, HomePerformanceUIState>;
final element =
@@ -61,6 +60,6 @@ abstract class _$HomePerformanceUIModel
Object?,
Object?
>;
element.handleValue(ref, created);
element.handleCreate(ref, build);
}
}

View File

@@ -18,6 +18,7 @@ import 'party_room/party_room_ui_model.dart';
import 'settings/settings_ui.dart';
import 'tools/tools_ui.dart';
import 'index_ui_widgets/user_avatar_widget.dart';
import 'package:starcitizen_doctor/common/utils/url_scheme_handler.dart';
class IndexUI extends HookConsumerWidget {
const IndexUI({super.key});
@@ -32,6 +33,13 @@ class IndexUI extends HookConsumerWidget {
ref.watch(partyRoomUIModelProvider.select((value) => null));
final curIndex = useState(0);
// Initialize URL scheme handler
useEffect(() {
UrlSchemeHandler().initialize(context);
return () => UrlSchemeHandler().dispose();
}, const []);
return NavigationView(
appBar: NavigationAppBar(
automaticallyImplyLeading: false,

View File

@@ -5,6 +5,7 @@ import 'package:starcitizen_doctor/provider/party_room.dart';
import 'package:starcitizen_doctor/ui/party_room/party_room_ui_model.dart';
import 'package:starcitizen_doctor/ui/party_room/utils/party_room_utils.dart';
import 'package:starcitizen_doctor/widgets/widgets.dart';
import 'package:grpc/grpc.dart';
class UserAvatarWidget extends HookConsumerWidget {
final VoidCallback onTapNavigateToPartyRoom;
@@ -52,14 +53,14 @@ class UserAvatarWidget extends HookConsumerWidget {
child: uiState.isLoggingIn
? const Padding(padding: EdgeInsets.all(4), child: ProgressRing(strokeWidth: 2))
: (fullAvatarUrl != null
? CacheNetImage(url: fullAvatarUrl, fit: BoxFit.cover)
: Center(
child: Icon(
isLoggedIn ? FluentIcons.contact : FluentIcons.unknown,
size: 16,
color: Colors.white,
),
)),
? CacheNetImage(url: fullAvatarUrl, fit: BoxFit.cover)
: Center(
child: Icon(
isLoggedIn ? FluentIcons.contact : FluentIcons.unknown,
size: 16,
color: Colors.white,
),
)),
),
),
const SizedBox(width: 8),
@@ -85,91 +86,154 @@ class UserAvatarWidget extends HookConsumerWidget {
barrierDismissible: true,
barrierColor: Colors.transparent,
builder: (BuildContext dialogContext) {
return Stack(
children: [
// 透明遮罩,点击关闭
GestureDetector(
onTap: () => Navigator.of(dialogContext).pop(),
child: Container(color: Colors.transparent),
),
// 账户卡片
Positioned(
left: offset.dx - 100,
top: offset.dy + (box?.size.height ?? 0) + 8,
child: Container(
width: 360,
decoration: BoxDecoration(
color: FluentTheme
.of(context)
.micaBackgroundColor,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.white.withValues(alpha: .1), width: 1),
boxShadow: [
BoxShadow(color: Colors.black.withValues(alpha: .3), blurRadius: 20, offset: const Offset(0, 8)),
],
return Consumer(
builder: (context, ref, child) {
final partyRoomState = ref.watch(partyRoomProvider);
final uiState = ref.watch(partyRoomUIModelProvider);
final userInfo = partyRoomState.auth.userInfo;
final displayUserName = userInfo?.gameUserId ?? userName;
final displayAvatarUrl = PartyRoomUtils.getAvatarUrl(userInfo?.avatarUrl);
final displayEnlistedDate = userInfo?.enlistedDate ?? enlistedDate;
return Stack(
children: [
// 透明遮罩,点击关闭
GestureDetector(
onTap: () => Navigator.of(dialogContext).pop(),
child: Container(color: Colors.transparent),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 用户信息
Row(
// 账户卡片
Positioned(
left: offset.dx - 100,
top: offset.dy + (box?.size.height ?? 0) + 8,
child: Container(
width: 360,
decoration: BoxDecoration(
color: FluentTheme.of(context).micaBackgroundColor,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.white.withValues(alpha: .1), width: 1),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: .3),
blurRadius: 20,
offset: const Offset(0, 8),
),
],
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 48,
height: 48,
decoration: BoxDecoration(color: Colors.blue, borderRadius: BorderRadius.circular(24)),
child: ClipRRect(
borderRadius: BorderRadius.circular(24),
child: avatarUrl != null
? CacheNetImage(url: avatarUrl, fit: BoxFit.cover)
: const Center(child: Icon(FluentIcons.contact, size: 24, color: Colors.white)),
),
// 用户信息
Row(
children: [
Container(
width: 48,
height: 48,
decoration: BoxDecoration(color: Colors.blue, borderRadius: BorderRadius.circular(24)),
child: ClipRRect(
borderRadius: BorderRadius.circular(24),
child: displayAvatarUrl != null
? CacheNetImage(url: displayAvatarUrl, fit: BoxFit.cover)
: const Center(child: Icon(FluentIcons.contact, size: 24, color: Colors.white)),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
displayUserName,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
'注册时间:${PartyRoomUtils.formatDateTime(displayEnlistedDate)}',
style: TextStyle(fontSize: 12, color: Colors.white.withValues(alpha: .6)),
),
],
),
),
],
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
userName,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
maxLines: 1,
overflow: TextOverflow.ellipsis,
const SizedBox(height: 16),
const Divider(),
const SizedBox(height: 8),
// 按钮组
Row(
children: [
// 刷新按钮
Expanded(
child: Tooltip(
message: '每小时仅可刷新一次',
child: FilledButton(
onPressed: uiState.isRefreshingProfile
? null
: () async {
try {
await ref.read(partyRoomUIModelProvider.notifier).refreshUserProfile();
if (context.mounted) {
showToast(context, '刷新成功');
}
} catch (e) {
if (context.mounted) {
if (e is GrpcError && e.code == StatusCode.resourceExhausted) {
showToast(context, '资料刷新过于频繁,请一小时后再试');
} else {
showToast(context, '刷新失败: $e');
}
}
}
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (uiState.isRefreshingProfile)
const Padding(
padding: EdgeInsets.only(right: 8),
child: SizedBox(width: 12, height: 12, child: ProgressRing(strokeWidth: 2)),
),
const Text('刷新资料'),
],
),
),
),
const SizedBox(height: 4),
Text(
'注册时间:${PartyRoomUtils.formatDateTime(enlistedDate)}',
style: TextStyle(fontSize: 12, color: Colors.white.withValues(alpha: .6)),
),
const SizedBox(width: 8),
// 注销按钮
Expanded(
child: FilledButton(
onPressed: () async {
Navigator.of(dialogContext).pop();
await _handleUnregister(context, ref);
},
style: ButtonStyle(
backgroundColor: WidgetStateProperty.resolveWith((states) {
if (states.isHovered) return Colors.red.withValues(alpha: 0.8);
return Colors.red;
}),
),
child: Text(
S.current.user_action_unregister,
style: const TextStyle(color: Colors.white),
),
),
],
),
),
],
),
],
),
const SizedBox(height: 16),
const Divider(),
const SizedBox(height: 8),
// 注销按钮
SizedBox(
width: double.infinity,
child: FilledButton(
onPressed: () async {
Navigator.of(dialogContext).pop();
await _handleUnregister(context, ref);
},
style: ButtonStyle(backgroundColor: WidgetStateProperty.all(Colors.red)),
child: Text(S.current.user_action_unregister, style: const TextStyle(color: Colors.white)),
),
),
],
),
),
),
),
),
],
],
);
},
);
},
);

View File

@@ -10,10 +10,10 @@ part of 'nav_state.dart';
// ignore_for_file: type=lint, type=warning
@ProviderFor(Nav)
const navProvider = NavProvider._();
final navProvider = NavProvider._();
final class NavProvider extends $NotifierProvider<Nav, NavState> {
const NavProvider._()
NavProvider._()
: super(
from: null,
argument: null,
@@ -47,7 +47,6 @@ abstract class _$Nav extends $Notifier<NavState> {
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<NavState, NavState>;
final element =
ref.element
@@ -57,6 +56,6 @@ abstract class _$Nav extends $Notifier<NavState> {
Object?,
Object?
>;
element.handleValue(ref, created);
element.handleCreate(ref, build);
}
}

View File

@@ -34,8 +34,9 @@ sealed class PartyRoomUIState with _$PartyRoomUIState {
@Default(false) bool isReconnecting,
@Default(0) int reconnectAttempts,
@Default(false) bool isMinimized,
@Default(true) bool isLoggingIn,
@Default(false) bool isLoggingIn,
@Default(true) bool isGuestMode,
@Default(false) bool isRefreshingProfile,
}) = _PartyRoomUIState;
}
@@ -108,7 +109,9 @@ class PartyRoomUIModel extends _$PartyRoomUIModel {
if (next.deathEvents?.isNotEmpty ?? false) {
for (final event in next.deathEvents!) {
ref.read(partyRoomProvider.notifier).sendSignal("special_death", params: {"location": event.$1, "area": event.$2});
ref
.read(partyRoomProvider.notifier)
.sendSignal("special_death", params: {"location": event.$1, "area": event.$2});
}
}
}
@@ -214,6 +217,17 @@ class PartyRoomUIModel extends _$PartyRoomUIModel {
}
}
/// 刷新用户资料
Future<void> refreshUserProfile() async {
if (state.isRefreshingProfile) return;
state = state.copyWith(isRefreshingProfile: true);
try {
await ref.read(partyRoomProvider.notifier).refreshUserProfile();
} finally {
state = state.copyWith(isRefreshingProfile: false);
}
}
/// 请求注册验证码
Future<void> requestPreRegister(String gameUserId) async {
state = state.copyWith(isLoading: true, errorMessage: null, registerGameUserId: gameUserId);
@@ -338,4 +352,4 @@ class PartyRoomUIModel extends _$PartyRoomUIModel {
void setMinimized(bool minimized) {
state = state.copyWith(isMinimized: minimized);
}
}
}

View File

@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$PartyRoomUIState {
bool get isConnecting; bool get showRoomList; List<RoomListItem> get roomListItems; int get currentPage; int get pageSize; int get totalRooms; String? get selectedMainTagId; String? get selectedSubTagId; String get searchOwnerName; bool get isLoading; String? get errorMessage; String get preRegisterCode; String get registerGameUserId; bool get isReconnecting; int get reconnectAttempts; bool get isMinimized; bool get isLoggingIn; bool get isGuestMode;
bool get isConnecting; bool get showRoomList; List<RoomListItem> get roomListItems; int get currentPage; int get pageSize; int get totalRooms; String? get selectedMainTagId; String? get selectedSubTagId; String get searchOwnerName; bool get isLoading; String? get errorMessage; String get preRegisterCode; String get registerGameUserId; bool get isReconnecting; int get reconnectAttempts; bool get isMinimized; bool get isLoggingIn; bool get isGuestMode; bool get isRefreshingProfile;
/// Create a copy of PartyRoomUIState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -25,16 +25,16 @@ $PartyRoomUIStateCopyWith<PartyRoomUIState> get copyWith => _$PartyRoomUIStateCo
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is PartyRoomUIState&&(identical(other.isConnecting, isConnecting) || other.isConnecting == isConnecting)&&(identical(other.showRoomList, showRoomList) || other.showRoomList == showRoomList)&&const DeepCollectionEquality().equals(other.roomListItems, roomListItems)&&(identical(other.currentPage, currentPage) || other.currentPage == currentPage)&&(identical(other.pageSize, pageSize) || other.pageSize == pageSize)&&(identical(other.totalRooms, totalRooms) || other.totalRooms == totalRooms)&&(identical(other.selectedMainTagId, selectedMainTagId) || other.selectedMainTagId == selectedMainTagId)&&(identical(other.selectedSubTagId, selectedSubTagId) || other.selectedSubTagId == selectedSubTagId)&&(identical(other.searchOwnerName, searchOwnerName) || other.searchOwnerName == searchOwnerName)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.preRegisterCode, preRegisterCode) || other.preRegisterCode == preRegisterCode)&&(identical(other.registerGameUserId, registerGameUserId) || other.registerGameUserId == registerGameUserId)&&(identical(other.isReconnecting, isReconnecting) || other.isReconnecting == isReconnecting)&&(identical(other.reconnectAttempts, reconnectAttempts) || other.reconnectAttempts == reconnectAttempts)&&(identical(other.isMinimized, isMinimized) || other.isMinimized == isMinimized)&&(identical(other.isLoggingIn, isLoggingIn) || other.isLoggingIn == isLoggingIn)&&(identical(other.isGuestMode, isGuestMode) || other.isGuestMode == isGuestMode));
return identical(this, other) || (other.runtimeType == runtimeType&&other is PartyRoomUIState&&(identical(other.isConnecting, isConnecting) || other.isConnecting == isConnecting)&&(identical(other.showRoomList, showRoomList) || other.showRoomList == showRoomList)&&const DeepCollectionEquality().equals(other.roomListItems, roomListItems)&&(identical(other.currentPage, currentPage) || other.currentPage == currentPage)&&(identical(other.pageSize, pageSize) || other.pageSize == pageSize)&&(identical(other.totalRooms, totalRooms) || other.totalRooms == totalRooms)&&(identical(other.selectedMainTagId, selectedMainTagId) || other.selectedMainTagId == selectedMainTagId)&&(identical(other.selectedSubTagId, selectedSubTagId) || other.selectedSubTagId == selectedSubTagId)&&(identical(other.searchOwnerName, searchOwnerName) || other.searchOwnerName == searchOwnerName)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.preRegisterCode, preRegisterCode) || other.preRegisterCode == preRegisterCode)&&(identical(other.registerGameUserId, registerGameUserId) || other.registerGameUserId == registerGameUserId)&&(identical(other.isReconnecting, isReconnecting) || other.isReconnecting == isReconnecting)&&(identical(other.reconnectAttempts, reconnectAttempts) || other.reconnectAttempts == reconnectAttempts)&&(identical(other.isMinimized, isMinimized) || other.isMinimized == isMinimized)&&(identical(other.isLoggingIn, isLoggingIn) || other.isLoggingIn == isLoggingIn)&&(identical(other.isGuestMode, isGuestMode) || other.isGuestMode == isGuestMode)&&(identical(other.isRefreshingProfile, isRefreshingProfile) || other.isRefreshingProfile == isRefreshingProfile));
}
@override
int get hashCode => Object.hash(runtimeType,isConnecting,showRoomList,const DeepCollectionEquality().hash(roomListItems),currentPage,pageSize,totalRooms,selectedMainTagId,selectedSubTagId,searchOwnerName,isLoading,errorMessage,preRegisterCode,registerGameUserId,isReconnecting,reconnectAttempts,isMinimized,isLoggingIn,isGuestMode);
int get hashCode => Object.hashAll([runtimeType,isConnecting,showRoomList,const DeepCollectionEquality().hash(roomListItems),currentPage,pageSize,totalRooms,selectedMainTagId,selectedSubTagId,searchOwnerName,isLoading,errorMessage,preRegisterCode,registerGameUserId,isReconnecting,reconnectAttempts,isMinimized,isLoggingIn,isGuestMode,isRefreshingProfile]);
@override
String toString() {
return 'PartyRoomUIState(isConnecting: $isConnecting, showRoomList: $showRoomList, roomListItems: $roomListItems, currentPage: $currentPage, pageSize: $pageSize, totalRooms: $totalRooms, selectedMainTagId: $selectedMainTagId, selectedSubTagId: $selectedSubTagId, searchOwnerName: $searchOwnerName, isLoading: $isLoading, errorMessage: $errorMessage, preRegisterCode: $preRegisterCode, registerGameUserId: $registerGameUserId, isReconnecting: $isReconnecting, reconnectAttempts: $reconnectAttempts, isMinimized: $isMinimized, isLoggingIn: $isLoggingIn, isGuestMode: $isGuestMode)';
return 'PartyRoomUIState(isConnecting: $isConnecting, showRoomList: $showRoomList, roomListItems: $roomListItems, currentPage: $currentPage, pageSize: $pageSize, totalRooms: $totalRooms, selectedMainTagId: $selectedMainTagId, selectedSubTagId: $selectedSubTagId, searchOwnerName: $searchOwnerName, isLoading: $isLoading, errorMessage: $errorMessage, preRegisterCode: $preRegisterCode, registerGameUserId: $registerGameUserId, isReconnecting: $isReconnecting, reconnectAttempts: $reconnectAttempts, isMinimized: $isMinimized, isLoggingIn: $isLoggingIn, isGuestMode: $isGuestMode, isRefreshingProfile: $isRefreshingProfile)';
}
@@ -45,7 +45,7 @@ abstract mixin class $PartyRoomUIStateCopyWith<$Res> {
factory $PartyRoomUIStateCopyWith(PartyRoomUIState value, $Res Function(PartyRoomUIState) _then) = _$PartyRoomUIStateCopyWithImpl;
@useResult
$Res call({
bool isConnecting, bool showRoomList, List<RoomListItem> roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized, bool isLoggingIn, bool isGuestMode
bool isConnecting, bool showRoomList, List<RoomListItem> roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized, bool isLoggingIn, bool isGuestMode, bool isRefreshingProfile
});
@@ -62,7 +62,7 @@ class _$PartyRoomUIStateCopyWithImpl<$Res>
/// Create a copy of PartyRoomUIState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? isConnecting = null,Object? showRoomList = null,Object? roomListItems = null,Object? currentPage = null,Object? pageSize = null,Object? totalRooms = null,Object? selectedMainTagId = freezed,Object? selectedSubTagId = freezed,Object? searchOwnerName = null,Object? isLoading = null,Object? errorMessage = freezed,Object? preRegisterCode = null,Object? registerGameUserId = null,Object? isReconnecting = null,Object? reconnectAttempts = null,Object? isMinimized = null,Object? isLoggingIn = null,Object? isGuestMode = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? isConnecting = null,Object? showRoomList = null,Object? roomListItems = null,Object? currentPage = null,Object? pageSize = null,Object? totalRooms = null,Object? selectedMainTagId = freezed,Object? selectedSubTagId = freezed,Object? searchOwnerName = null,Object? isLoading = null,Object? errorMessage = freezed,Object? preRegisterCode = null,Object? registerGameUserId = null,Object? isReconnecting = null,Object? reconnectAttempts = null,Object? isMinimized = null,Object? isLoggingIn = null,Object? isGuestMode = null,Object? isRefreshingProfile = null,}) {
return _then(_self.copyWith(
isConnecting: null == isConnecting ? _self.isConnecting : isConnecting // ignore: cast_nullable_to_non_nullable
as bool,showRoomList: null == showRoomList ? _self.showRoomList : showRoomList // ignore: cast_nullable_to_non_nullable
@@ -82,6 +82,7 @@ as bool,reconnectAttempts: null == reconnectAttempts ? _self.reconnectAttempts :
as int,isMinimized: null == isMinimized ? _self.isMinimized : isMinimized // ignore: cast_nullable_to_non_nullable
as bool,isLoggingIn: null == isLoggingIn ? _self.isLoggingIn : isLoggingIn // ignore: cast_nullable_to_non_nullable
as bool,isGuestMode: null == isGuestMode ? _self.isGuestMode : isGuestMode // ignore: cast_nullable_to_non_nullable
as bool,isRefreshingProfile: null == isRefreshingProfile ? _self.isRefreshingProfile : isRefreshingProfile // ignore: cast_nullable_to_non_nullable
as bool,
));
}
@@ -164,10 +165,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool isConnecting, bool showRoomList, List<RoomListItem> roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized, bool isLoggingIn, bool isGuestMode)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool isConnecting, bool showRoomList, List<RoomListItem> roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized, bool isLoggingIn, bool isGuestMode, bool isRefreshingProfile)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _PartyRoomUIState() when $default != null:
return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that.currentPage,_that.pageSize,_that.totalRooms,_that.selectedMainTagId,_that.selectedSubTagId,_that.searchOwnerName,_that.isLoading,_that.errorMessage,_that.preRegisterCode,_that.registerGameUserId,_that.isReconnecting,_that.reconnectAttempts,_that.isMinimized,_that.isLoggingIn,_that.isGuestMode);case _:
return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that.currentPage,_that.pageSize,_that.totalRooms,_that.selectedMainTagId,_that.selectedSubTagId,_that.searchOwnerName,_that.isLoading,_that.errorMessage,_that.preRegisterCode,_that.registerGameUserId,_that.isReconnecting,_that.reconnectAttempts,_that.isMinimized,_that.isLoggingIn,_that.isGuestMode,_that.isRefreshingProfile);case _:
return orElse();
}
@@ -185,10 +186,10 @@ return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that.
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool isConnecting, bool showRoomList, List<RoomListItem> roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized, bool isLoggingIn, bool isGuestMode) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool isConnecting, bool showRoomList, List<RoomListItem> roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized, bool isLoggingIn, bool isGuestMode, bool isRefreshingProfile) $default,) {final _that = this;
switch (_that) {
case _PartyRoomUIState():
return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that.currentPage,_that.pageSize,_that.totalRooms,_that.selectedMainTagId,_that.selectedSubTagId,_that.searchOwnerName,_that.isLoading,_that.errorMessage,_that.preRegisterCode,_that.registerGameUserId,_that.isReconnecting,_that.reconnectAttempts,_that.isMinimized,_that.isLoggingIn,_that.isGuestMode);}
return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that.currentPage,_that.pageSize,_that.totalRooms,_that.selectedMainTagId,_that.selectedSubTagId,_that.searchOwnerName,_that.isLoading,_that.errorMessage,_that.preRegisterCode,_that.registerGameUserId,_that.isReconnecting,_that.reconnectAttempts,_that.isMinimized,_that.isLoggingIn,_that.isGuestMode,_that.isRefreshingProfile);}
}
/// A variant of `when` that fallback to returning `null`
///
@@ -202,10 +203,10 @@ return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that.
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool isConnecting, bool showRoomList, List<RoomListItem> roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized, bool isLoggingIn, bool isGuestMode)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool isConnecting, bool showRoomList, List<RoomListItem> roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized, bool isLoggingIn, bool isGuestMode, bool isRefreshingProfile)? $default,) {final _that = this;
switch (_that) {
case _PartyRoomUIState() when $default != null:
return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that.currentPage,_that.pageSize,_that.totalRooms,_that.selectedMainTagId,_that.selectedSubTagId,_that.searchOwnerName,_that.isLoading,_that.errorMessage,_that.preRegisterCode,_that.registerGameUserId,_that.isReconnecting,_that.reconnectAttempts,_that.isMinimized,_that.isLoggingIn,_that.isGuestMode);case _:
return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that.currentPage,_that.pageSize,_that.totalRooms,_that.selectedMainTagId,_that.selectedSubTagId,_that.searchOwnerName,_that.isLoading,_that.errorMessage,_that.preRegisterCode,_that.registerGameUserId,_that.isReconnecting,_that.reconnectAttempts,_that.isMinimized,_that.isLoggingIn,_that.isGuestMode,_that.isRefreshingProfile);case _:
return null;
}
@@ -217,7 +218,7 @@ return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that.
class _PartyRoomUIState implements PartyRoomUIState {
const _PartyRoomUIState({this.isConnecting = false, this.showRoomList = false, final List<RoomListItem> roomListItems = const [], this.currentPage = 1, this.pageSize = 20, this.totalRooms = 0, this.selectedMainTagId, this.selectedSubTagId, this.searchOwnerName = '', this.isLoading = false, this.errorMessage, this.preRegisterCode = '', this.registerGameUserId = '', this.isReconnecting = false, this.reconnectAttempts = 0, this.isMinimized = false, this.isLoggingIn = true, this.isGuestMode = true}): _roomListItems = roomListItems;
const _PartyRoomUIState({this.isConnecting = false, this.showRoomList = false, final List<RoomListItem> roomListItems = const [], this.currentPage = 1, this.pageSize = 20, this.totalRooms = 0, this.selectedMainTagId, this.selectedSubTagId, this.searchOwnerName = '', this.isLoading = false, this.errorMessage, this.preRegisterCode = '', this.registerGameUserId = '', this.isReconnecting = false, this.reconnectAttempts = 0, this.isMinimized = false, this.isLoggingIn = false, this.isGuestMode = true, this.isRefreshingProfile = false}): _roomListItems = roomListItems;
@override@JsonKey() final bool isConnecting;
@@ -244,6 +245,7 @@ class _PartyRoomUIState implements PartyRoomUIState {
@override@JsonKey() final bool isMinimized;
@override@JsonKey() final bool isLoggingIn;
@override@JsonKey() final bool isGuestMode;
@override@JsonKey() final bool isRefreshingProfile;
/// Create a copy of PartyRoomUIState
/// with the given fields replaced by the non-null parameter values.
@@ -255,16 +257,16 @@ _$PartyRoomUIStateCopyWith<_PartyRoomUIState> get copyWith => __$PartyRoomUIStat
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PartyRoomUIState&&(identical(other.isConnecting, isConnecting) || other.isConnecting == isConnecting)&&(identical(other.showRoomList, showRoomList) || other.showRoomList == showRoomList)&&const DeepCollectionEquality().equals(other._roomListItems, _roomListItems)&&(identical(other.currentPage, currentPage) || other.currentPage == currentPage)&&(identical(other.pageSize, pageSize) || other.pageSize == pageSize)&&(identical(other.totalRooms, totalRooms) || other.totalRooms == totalRooms)&&(identical(other.selectedMainTagId, selectedMainTagId) || other.selectedMainTagId == selectedMainTagId)&&(identical(other.selectedSubTagId, selectedSubTagId) || other.selectedSubTagId == selectedSubTagId)&&(identical(other.searchOwnerName, searchOwnerName) || other.searchOwnerName == searchOwnerName)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.preRegisterCode, preRegisterCode) || other.preRegisterCode == preRegisterCode)&&(identical(other.registerGameUserId, registerGameUserId) || other.registerGameUserId == registerGameUserId)&&(identical(other.isReconnecting, isReconnecting) || other.isReconnecting == isReconnecting)&&(identical(other.reconnectAttempts, reconnectAttempts) || other.reconnectAttempts == reconnectAttempts)&&(identical(other.isMinimized, isMinimized) || other.isMinimized == isMinimized)&&(identical(other.isLoggingIn, isLoggingIn) || other.isLoggingIn == isLoggingIn)&&(identical(other.isGuestMode, isGuestMode) || other.isGuestMode == isGuestMode));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PartyRoomUIState&&(identical(other.isConnecting, isConnecting) || other.isConnecting == isConnecting)&&(identical(other.showRoomList, showRoomList) || other.showRoomList == showRoomList)&&const DeepCollectionEquality().equals(other._roomListItems, _roomListItems)&&(identical(other.currentPage, currentPage) || other.currentPage == currentPage)&&(identical(other.pageSize, pageSize) || other.pageSize == pageSize)&&(identical(other.totalRooms, totalRooms) || other.totalRooms == totalRooms)&&(identical(other.selectedMainTagId, selectedMainTagId) || other.selectedMainTagId == selectedMainTagId)&&(identical(other.selectedSubTagId, selectedSubTagId) || other.selectedSubTagId == selectedSubTagId)&&(identical(other.searchOwnerName, searchOwnerName) || other.searchOwnerName == searchOwnerName)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.preRegisterCode, preRegisterCode) || other.preRegisterCode == preRegisterCode)&&(identical(other.registerGameUserId, registerGameUserId) || other.registerGameUserId == registerGameUserId)&&(identical(other.isReconnecting, isReconnecting) || other.isReconnecting == isReconnecting)&&(identical(other.reconnectAttempts, reconnectAttempts) || other.reconnectAttempts == reconnectAttempts)&&(identical(other.isMinimized, isMinimized) || other.isMinimized == isMinimized)&&(identical(other.isLoggingIn, isLoggingIn) || other.isLoggingIn == isLoggingIn)&&(identical(other.isGuestMode, isGuestMode) || other.isGuestMode == isGuestMode)&&(identical(other.isRefreshingProfile, isRefreshingProfile) || other.isRefreshingProfile == isRefreshingProfile));
}
@override
int get hashCode => Object.hash(runtimeType,isConnecting,showRoomList,const DeepCollectionEquality().hash(_roomListItems),currentPage,pageSize,totalRooms,selectedMainTagId,selectedSubTagId,searchOwnerName,isLoading,errorMessage,preRegisterCode,registerGameUserId,isReconnecting,reconnectAttempts,isMinimized,isLoggingIn,isGuestMode);
int get hashCode => Object.hashAll([runtimeType,isConnecting,showRoomList,const DeepCollectionEquality().hash(_roomListItems),currentPage,pageSize,totalRooms,selectedMainTagId,selectedSubTagId,searchOwnerName,isLoading,errorMessage,preRegisterCode,registerGameUserId,isReconnecting,reconnectAttempts,isMinimized,isLoggingIn,isGuestMode,isRefreshingProfile]);
@override
String toString() {
return 'PartyRoomUIState(isConnecting: $isConnecting, showRoomList: $showRoomList, roomListItems: $roomListItems, currentPage: $currentPage, pageSize: $pageSize, totalRooms: $totalRooms, selectedMainTagId: $selectedMainTagId, selectedSubTagId: $selectedSubTagId, searchOwnerName: $searchOwnerName, isLoading: $isLoading, errorMessage: $errorMessage, preRegisterCode: $preRegisterCode, registerGameUserId: $registerGameUserId, isReconnecting: $isReconnecting, reconnectAttempts: $reconnectAttempts, isMinimized: $isMinimized, isLoggingIn: $isLoggingIn, isGuestMode: $isGuestMode)';
return 'PartyRoomUIState(isConnecting: $isConnecting, showRoomList: $showRoomList, roomListItems: $roomListItems, currentPage: $currentPage, pageSize: $pageSize, totalRooms: $totalRooms, selectedMainTagId: $selectedMainTagId, selectedSubTagId: $selectedSubTagId, searchOwnerName: $searchOwnerName, isLoading: $isLoading, errorMessage: $errorMessage, preRegisterCode: $preRegisterCode, registerGameUserId: $registerGameUserId, isReconnecting: $isReconnecting, reconnectAttempts: $reconnectAttempts, isMinimized: $isMinimized, isLoggingIn: $isLoggingIn, isGuestMode: $isGuestMode, isRefreshingProfile: $isRefreshingProfile)';
}
@@ -275,7 +277,7 @@ abstract mixin class _$PartyRoomUIStateCopyWith<$Res> implements $PartyRoomUISta
factory _$PartyRoomUIStateCopyWith(_PartyRoomUIState value, $Res Function(_PartyRoomUIState) _then) = __$PartyRoomUIStateCopyWithImpl;
@override @useResult
$Res call({
bool isConnecting, bool showRoomList, List<RoomListItem> roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized, bool isLoggingIn, bool isGuestMode
bool isConnecting, bool showRoomList, List<RoomListItem> roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized, bool isLoggingIn, bool isGuestMode, bool isRefreshingProfile
});
@@ -292,7 +294,7 @@ class __$PartyRoomUIStateCopyWithImpl<$Res>
/// Create a copy of PartyRoomUIState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? isConnecting = null,Object? showRoomList = null,Object? roomListItems = null,Object? currentPage = null,Object? pageSize = null,Object? totalRooms = null,Object? selectedMainTagId = freezed,Object? selectedSubTagId = freezed,Object? searchOwnerName = null,Object? isLoading = null,Object? errorMessage = freezed,Object? preRegisterCode = null,Object? registerGameUserId = null,Object? isReconnecting = null,Object? reconnectAttempts = null,Object? isMinimized = null,Object? isLoggingIn = null,Object? isGuestMode = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? isConnecting = null,Object? showRoomList = null,Object? roomListItems = null,Object? currentPage = null,Object? pageSize = null,Object? totalRooms = null,Object? selectedMainTagId = freezed,Object? selectedSubTagId = freezed,Object? searchOwnerName = null,Object? isLoading = null,Object? errorMessage = freezed,Object? preRegisterCode = null,Object? registerGameUserId = null,Object? isReconnecting = null,Object? reconnectAttempts = null,Object? isMinimized = null,Object? isLoggingIn = null,Object? isGuestMode = null,Object? isRefreshingProfile = null,}) {
return _then(_PartyRoomUIState(
isConnecting: null == isConnecting ? _self.isConnecting : isConnecting // ignore: cast_nullable_to_non_nullable
as bool,showRoomList: null == showRoomList ? _self.showRoomList : showRoomList // ignore: cast_nullable_to_non_nullable
@@ -312,6 +314,7 @@ as bool,reconnectAttempts: null == reconnectAttempts ? _self.reconnectAttempts :
as int,isMinimized: null == isMinimized ? _self.isMinimized : isMinimized // ignore: cast_nullable_to_non_nullable
as bool,isLoggingIn: null == isLoggingIn ? _self.isLoggingIn : isLoggingIn // ignore: cast_nullable_to_non_nullable
as bool,isGuestMode: null == isGuestMode ? _self.isGuestMode : isGuestMode // ignore: cast_nullable_to_non_nullable
as bool,isRefreshingProfile: null == isRefreshingProfile ? _self.isRefreshingProfile : isRefreshingProfile // ignore: cast_nullable_to_non_nullable
as bool,
));
}

View File

@@ -10,11 +10,11 @@ part of 'party_room_ui_model.dart';
// ignore_for_file: type=lint, type=warning
@ProviderFor(PartyRoomUIModel)
const partyRoomUIModelProvider = PartyRoomUIModelProvider._();
final partyRoomUIModelProvider = PartyRoomUIModelProvider._();
final class PartyRoomUIModelProvider
extends $NotifierProvider<PartyRoomUIModel, PartyRoomUIState> {
const PartyRoomUIModelProvider._()
PartyRoomUIModelProvider._()
: super(
from: null,
argument: null,
@@ -41,14 +41,13 @@ final class PartyRoomUIModelProvider
}
}
String _$partyRoomUIModelHash() => r'b22ad79b6d4a877876b2534f35fb0448b34d4ad5';
String _$partyRoomUIModelHash() => r'cb37e8a29d22b122a5e0e552cca1af537dddc703';
abstract class _$PartyRoomUIModel extends $Notifier<PartyRoomUIState> {
PartyRoomUIState build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<PartyRoomUIState, PartyRoomUIState>;
final element =
ref.element
@@ -58,6 +57,6 @@ abstract class _$PartyRoomUIModel extends $Notifier<PartyRoomUIState> {
Object?,
Object?
>;
element.handleValue(ref, created);
element.handleCreate(ref, build);
}
}

View File

@@ -10,7 +10,7 @@ part of 'game_log_tracker_provider.dart';
// ignore_for_file: type=lint, type=warning
@ProviderFor(PartyRoomGameLogTrackerProvider)
const partyRoomGameLogTrackerProviderProvider =
final partyRoomGameLogTrackerProviderProvider =
PartyRoomGameLogTrackerProviderFamily._();
final class PartyRoomGameLogTrackerProviderProvider
@@ -19,7 +19,7 @@ final class PartyRoomGameLogTrackerProviderProvider
PartyRoomGameLogTrackerProvider,
PartyRoomGameLogTrackerProviderState
> {
const PartyRoomGameLogTrackerProviderProvider._({
PartyRoomGameLogTrackerProviderProvider._({
required PartyRoomGameLogTrackerProviderFamily super.from,
required DateTime super.argument,
}) : super(
@@ -77,7 +77,7 @@ final class PartyRoomGameLogTrackerProviderFamily extends $Family
PartyRoomGameLogTrackerProviderState,
DateTime
> {
const PartyRoomGameLogTrackerProviderFamily._()
PartyRoomGameLogTrackerProviderFamily._()
: super(
retry: null,
name: r'partyRoomGameLogTrackerProviderProvider',
@@ -105,7 +105,6 @@ abstract class _$PartyRoomGameLogTrackerProvider
@$mustCallSuper
@override
void runBuild() {
final created = build(startTime: _$args);
final ref =
this.ref
as $Ref<
@@ -123,6 +122,6 @@ abstract class _$PartyRoomGameLogTrackerProvider
Object?,
Object?
>;
element.handleValue(ref, created);
element.handleCreate(ref, () => build(startTime: _$args));
}
}

View File

@@ -80,8 +80,8 @@ class SettingsUIModel extends _$SettingsUIModel {
lockParentWindow: true,
);
if (r == null || r.files.firstOrNull?.path == null) return;
final fileName = r.files.first.path!;
if (fileName.endsWith("\\RSI Launcher.exe")) {
final fileName = r.files.first.path!.platformPath;
if (fileName.toLowerCase().endsWith('\\rsi launcher.exe'.platformPath)) {
await _saveCustomPath("custom_launcher_path", fileName);
if (!context.mounted) return;
showToast(context, S.current.setting_action_info_setting_success);
@@ -101,11 +101,14 @@ class SettingsUIModel extends _$SettingsUIModel {
lockParentWindow: true,
);
if (r == null || r.files.firstOrNull?.path == null) return;
final fileName = r.files.first.path!;
final fileName = r.files.first.path!.platformPath;
dPrint(fileName);
final fileNameRegExp = RegExp(r"^(.*\\StarCitizen\\.*\\)Bin64\\StarCitizen\.exe$", caseSensitive: false);
final fileNameRegExp = RegExp(
r'^(.*[/\\]starcitizen[/\\].*[/\\])bin64[/\\]starcitizen\.exe$',
caseSensitive: false,
);
if (fileNameRegExp.hasMatch(fileName)) {
RegExp pathRegex = RegExp(r"\\[^\\]+\\Bin64\\StarCitizen\.exe$");
RegExp pathRegex = RegExp(r'[/\\][^/\\]+[/\\]bin64[/\\]starcitizen\.exe$', caseSensitive: false);
String extractedPath = fileName.replaceFirst(pathRegex, '');
await _saveCustomPath("custom_game_path", extractedPath);
if (!context.mounted) return;

View File

@@ -10,11 +10,11 @@ part of 'settings_ui_model.dart';
// ignore_for_file: type=lint, type=warning
@ProviderFor(SettingsUIModel)
const settingsUIModelProvider = SettingsUIModelProvider._();
final settingsUIModelProvider = SettingsUIModelProvider._();
final class SettingsUIModelProvider
extends $NotifierProvider<SettingsUIModel, SettingsUIState> {
const SettingsUIModelProvider._()
SettingsUIModelProvider._()
: super(
from: null,
argument: null,
@@ -41,14 +41,13 @@ final class SettingsUIModelProvider
}
}
String _$settingsUIModelHash() => r'd34b1a2fac69d10f560d9a2e1a7431dd5a7954ca';
String _$settingsUIModelHash() => r'302b8ce09601218c28c0cf2bd133f1749ec471dc';
abstract class _$SettingsUIModel extends $Notifier<SettingsUIState> {
SettingsUIState build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<SettingsUIState, SettingsUIState>;
final element =
ref.element
@@ -58,6 +57,6 @@ abstract class _$SettingsUIModel extends $Notifier<SettingsUIState> {
Object?,
Object?
>;
element.handleValue(ref, created);
element.handleCreate(ref, build);
}
}

View File

@@ -70,8 +70,11 @@ class RsiLauncherEnhanceDialogUI extends HookConsumerWidget {
workingText.value = S.current.tools_rsi_launcher_enhance_working_msg1;
if ((await SystemHelper.getPID("RSI Launcher")).isNotEmpty) {
if (!context.mounted) return;
showToast(context, S.current.tools_action_info_rsi_launcher_running_warning,
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .35));
showToast(
context,
S.current.tools_action_info_rsi_launcher_running_warning,
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .35),
);
workingText.value = "";
return;
}
@@ -92,16 +95,16 @@ class RsiLauncherEnhanceDialogUI extends HookConsumerWidget {
return ContentDialog(
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .48),
title: Row(children: [
IconButton(
icon: const Icon(
FluentIcons.back,
size: 22,
),
onPressed: workingText.value.isEmpty ? Navigator.of(context).pop : null),
const SizedBox(width: 12),
Text(S.current.tools_rsi_launcher_enhance_title),
]),
title: Row(
children: [
IconButton(
icon: const Icon(FluentIcons.back, size: 22),
onPressed: workingText.value.isEmpty ? Navigator.of(context).pop : null,
),
const SizedBox(width: 12),
Text(S.current.tools_rsi_launcher_enhance_title),
],
),
content: AnimatedSize(
duration: const Duration(milliseconds: 130),
child: Column(
@@ -112,17 +115,16 @@ class RsiLauncherEnhanceDialogUI extends HookConsumerWidget {
InfoBar(
title: const SizedBox(),
content: Text(S.current.home_localization_action_rsi_launcher_no_game_path_msg),
style: InfoBarThemeData(decoration: (severity) {
return BoxDecoration(
color: Colors.orange,
);
}, iconColor: (severity) {
return Colors.white;
}),
),
const SizedBox(
height: 12,
style: InfoBarThemeData(
decoration: (severity) {
return BoxDecoration(color: Colors.orange);
},
iconColor: (severity) {
return Colors.white;
},
),
),
const SizedBox(height: 12),
],
if (workingText.value.isNotEmpty) ...[
Center(
@@ -144,19 +146,17 @@ class RsiLauncherEnhanceDialogUI extends HookConsumerWidget {
Expanded(
child: Text(
S.current.tools_rsi_launcher_enhance_msg_version(assarState.value?.version ?? ""),
style: TextStyle(
color: Colors.white.withValues(alpha: .6),
),
style: TextStyle(color: Colors.white.withValues(alpha: .6)),
),
),
Text(
S.current.tools_rsi_launcher_enhance_msg_patch_status((assarState.value?.isPatchInstalled ?? false)
? S.current.localization_info_installed
: S.current.tools_action_info_not_installed),
style: TextStyle(
color: Colors.white.withValues(alpha: .6),
S.current.tools_rsi_launcher_enhance_msg_patch_status(
(assarState.value?.isPatchInstalled ?? false)
? S.current.localization_info_installed
: S.current.tools_action_info_not_installed,
),
)
style: TextStyle(color: Colors.white.withValues(alpha: .6)),
),
],
),
if (assarState.value?.serverData.isEmpty ?? true) ...[
@@ -165,6 +165,84 @@ class RsiLauncherEnhanceDialogUI extends HookConsumerWidget {
const SizedBox(height: 24),
if (assarState.value?.enabledLocalization != null)
Container(
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: FluentTheme.of(context).cardColor,
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(S.current.tools_rsi_launcher_enhance_title_localization),
const SizedBox(height: 3),
Text(
S.current.tools_rsi_launcher_enhance_subtitle_localization,
style: TextStyle(fontSize: 13, color: Colors.white.withValues(alpha: .6)),
),
],
),
),
ComboBox(
items: [
for (final key in supportLocalizationMap.keys)
ComboBoxItem(value: key, child: Text(supportLocalizationMap[key]!)),
],
value: assarState.value?.enabledLocalization,
onChanged: (v) {
assarState.value = assarState.value!.copyWith(enabledLocalization: v);
},
),
],
),
),
const SizedBox(height: 3),
if (assarState.value?.enableDownloaderBoost != null) ...[
IconButton(
icon: Padding(
padding: const EdgeInsets.only(top: 3, bottom: 3),
child: Row(
children: [
Expanded(
child: Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(expandEnhance.value ? FluentIcons.chevron_up : FluentIcons.chevron_down),
const SizedBox(width: 12),
Text(
expandEnhance.value
? S.current.tools_rsi_launcher_enhance_action_fold
: S.current.tools_rsi_launcher_enhance_action_expand,
),
],
),
),
),
],
),
),
onPressed: () async {
if (!expandEnhance.value) {
final userOK = await showConfirmDialogs(
context,
S.current.tools_rsi_launcher_enhance_note_title,
Column(
mainAxisSize: MainAxisSize.min,
children: [Text(S.current.tools_rsi_launcher_enhance_note_msg)],
),
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .55),
);
if (!userOK) return;
}
expandEnhance.value = !expandEnhance.value;
},
),
if (expandEnhance.value)
Container(
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
@@ -174,111 +252,38 @@ class RsiLauncherEnhanceDialogUI extends HookConsumerWidget {
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(S.current.tools_rsi_launcher_enhance_title_localization),
const SizedBox(height: 3),
Text(
S.current.tools_rsi_launcher_enhance_subtitle_localization,
style: TextStyle(
fontSize: 13,
color: Colors.white.withValues(alpha: .6),
),
),
],
)),
ComboBox(
items: [
for (final key in supportLocalizationMap.keys)
ComboBoxItem(value: key, child: Text(supportLocalizationMap[key]!))
],
value: assarState.value?.enabledLocalization,
onChanged: (v) {
assarState.value = assarState.value!.copyWith(enabledLocalization: v);
},
),
],
)),
const SizedBox(height: 3),
if (assarState.value?.enableDownloaderBoost != null) ...[
IconButton(
icon: Padding(
padding: const EdgeInsets.only(top: 3, bottom: 3),
child: Row(
children: [
Expanded(
child: Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(expandEnhance.value ? FluentIcons.chevron_up : FluentIcons.chevron_down),
const SizedBox(width: 12),
Text(expandEnhance.value
? S.current.tools_rsi_launcher_enhance_action_fold
: S.current.tools_rsi_launcher_enhance_action_expand),
],
))),
],
),
),
onPressed: () async {
if (!expandEnhance.value) {
final userOK = await showConfirmDialogs(
context,
S.current.tools_rsi_launcher_enhance_note_title,
Column(
mainAxisSize: MainAxisSize.min,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(S.current.tools_rsi_launcher_enhance_note_msg),
Text(S.current.tools_rsi_launcher_enhance_title_download_booster),
const SizedBox(height: 3),
Text(
S.current.tools_rsi_launcher_enhance_subtitle_download_booster,
style: TextStyle(fontSize: 13, color: Colors.white.withValues(alpha: .6)),
),
],
),
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .55));
if (!userOK) return;
}
expandEnhance.value = !expandEnhance.value;
},
),
if (expandEnhance.value)
Container(
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: FluentTheme.of(context).cardColor,
borderRadius: BorderRadius.circular(12),
),
child: Row(children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(S.current.tools_rsi_launcher_enhance_title_download_booster),
const SizedBox(height: 3),
Text(
S.current.tools_rsi_launcher_enhance_subtitle_download_booster,
style: TextStyle(
fontSize: 13,
color: Colors.white.withValues(alpha: .6),
),
),
],
)),
),
ToggleSwitch(
onChanged: (value) {
assarState.value = assarState.value?.copyWith(enableDownloaderBoost: value);
},
checked: assarState.value?.enableDownloaderBoost ?? false,
)
])),
),
],
),
),
],
const SizedBox(height: 12),
Center(
child: FilledButton(
onPressed: doInstall,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6),
child: Text(S.current.tools_rsi_launcher_enhance_action_install),
))),
child: FilledButton(
onPressed: doInstall,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6),
child: Text(S.current.tools_rsi_launcher_enhance_action_install),
),
),
),
],
const SizedBox(height: 16),
Text(
@@ -300,7 +305,7 @@ class RsiLauncherEnhanceDialogUI extends HookConsumerWidget {
return null;
}
dPrint("[RsiLauncherEnhanceDialogUI] rsiLauncherPath ==== $lPath");
final dataPath = "${lPath}resources\\app.asar";
final dataPath = "${lPath}resources\\app.asar".platformPath;
dPrint("[RsiLauncherEnhanceDialogUI] rsiLauncherDataPath ==== $dataPath");
try {
final data = await asar_api.getRsiLauncherAsarData(asarPath: dataPath);
@@ -333,7 +338,10 @@ class RsiLauncherEnhanceDialogUI extends HookConsumerWidget {
}
Future<String> _loadEnhanceData(
BuildContext context, WidgetRef ref, ValueNotifier<RSILauncherStateData?> assarState) async {
BuildContext context,
WidgetRef ref,
ValueNotifier<RSILauncherStateData?> assarState,
) async {
final globalModel = ref.read(appGlobalModelProvider);
final enhancePath = "${globalModel.applicationSupportDir}/launcher_enhance_data";
@@ -418,11 +426,13 @@ class RsiLauncherEnhanceDialogUI extends HookConsumerWidget {
for (final line in serverScriptLines) {
final lineTrim = line.trim();
if (lineTrim.startsWith(SC_TOOLBOX_ENABLED_LOCALIZATION_SCRIPT_START)) {
scriptBuffer
.writeln("$SC_TOOLBOX_ENABLED_LOCALIZATION_SCRIPT_START\"${assarState.value!.enabledLocalization}\";");
scriptBuffer.writeln(
"$SC_TOOLBOX_ENABLED_LOCALIZATION_SCRIPT_START\"${assarState.value!.enabledLocalization}\";",
);
} else if (lineTrim.startsWith(SC_TOOLBOX_ENABLE_DOWNLOADER_BOOST_SCRIPT_START)) {
scriptBuffer
.writeln("$SC_TOOLBOX_ENABLE_DOWNLOADER_BOOST_SCRIPT_START${assarState.value!.enableDownloaderBoost};");
scriptBuffer.writeln(
"$SC_TOOLBOX_ENABLE_DOWNLOADER_BOOST_SCRIPT_START${assarState.value!.enableDownloaderBoost};",
);
} else {
scriptBuffer.writeln(line);
}

View File

@@ -0,0 +1,196 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:starcitizen_doctor/generated/l10n.dart';
/// 着色器缓存清理对话框
class ShaderCacheCleanDialog extends StatefulWidget {
final Future<void> Function(String mode) onClean;
const ShaderCacheCleanDialog({super.key, required this.onClean});
@override
State<ShaderCacheCleanDialog> createState() => _ShaderCacheCleanDialogState();
}
class _ShaderCacheCleanDialogState extends State<ShaderCacheCleanDialog> {
bool _isCleaning = false;
String? _selectedMode;
@override
Widget build(BuildContext context) {
return ContentDialog(
title: Row(
children: [
const Icon(FontAwesomeIcons.shapes, size: 20),
const SizedBox(width: 12),
Text(S.current.tools_shader_clean_dialog_title),
],
),
constraints: const BoxConstraints(maxWidth: 500),
content: _isCleaning
? SizedBox(
height: 120,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const ProgressRing(),
const SizedBox(height: 16),
Text(S.current.tools_action_info_cleaning),
],
),
),
)
: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 12),
// 保留最新模式
_CleanModeOption(
icon: FluentIcons.save,
title: S.current.tools_shader_clean_keep_latest,
description: S.current.tools_shader_clean_keep_latest_desc,
isSelected: _selectedMode == "keep_latest",
isRecommended: true,
onTap: () {
setState(() {
_selectedMode = "keep_latest";
});
},
),
const SizedBox(height: 12),
// 全部清理模式
_CleanModeOption(
icon: FluentIcons.delete,
title: S.current.tools_shader_clean_all,
description: S.current.tools_shader_clean_all_desc,
isSelected: _selectedMode == "clean_all",
onTap: () {
setState(() {
_selectedMode = "clean_all";
});
},
),
],
),
actions: [
if (!_isCleaning) ...[
Button(onPressed: () => Navigator.of(context).pop(), child: Text(S.current.app_common_tip_cancel)),
FilledButton(
onPressed: _selectedMode == null
? null
: () async {
setState(() {
_isCleaning = true;
});
try {
await widget.onClean(_selectedMode!);
} finally {
if (mounted) {
setState(() {
_isCleaning = false;
});
}
}
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
child: Text(S.current.tools_action_start_cleaning),
),
),
],
],
);
}
}
/// 清理模式选项组件
class _CleanModeOption extends StatelessWidget {
final IconData icon;
final String title;
final String description;
final bool isSelected;
final bool isRecommended;
final VoidCallback onTap;
const _CleanModeOption({
required this.icon,
required this.title,
required this.description,
required this.isSelected,
this.isRecommended = false,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: isSelected
? FluentTheme.of(context).accentColor.withValues(alpha: .15)
: FluentTheme.of(context).cardColor,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: isSelected ? FluentTheme.of(context).accentColor : Colors.transparent, width: 2),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
icon,
size: 20,
color: isSelected ? FluentTheme.of(context).accentColor : Colors.white.withValues(alpha: .7),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
title,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
color: isSelected ? FluentTheme.of(context).accentColor : Colors.white,
),
),
),
if (isRecommended)
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: const Color(0xFF4CAF50),
borderRadius: BorderRadius.circular(4),
),
child: Text(
S.current.app_common_recommended,
style: const TextStyle(fontSize: 11, color: Colors.white, fontWeight: FontWeight.w600),
),
),
],
),
const SizedBox(height: 8),
Text(
description,
style: TextStyle(fontSize: 13, color: Colors.white.withValues(alpha: .6), height: 1.4),
),
],
),
),
],
),
],
),
),
);
}
}

View File

@@ -10,11 +10,11 @@ part of 'log_analyze_provider.dart';
// ignore_for_file: type=lint, type=warning
@ProviderFor(ToolsLogAnalyze)
const toolsLogAnalyzeProvider = ToolsLogAnalyzeFamily._();
final toolsLogAnalyzeProvider = ToolsLogAnalyzeFamily._();
final class ToolsLogAnalyzeProvider
extends $AsyncNotifierProvider<ToolsLogAnalyze, List<LogAnalyzeLineData>> {
const ToolsLogAnalyzeProvider._({
ToolsLogAnalyzeProvider._({
required ToolsLogAnalyzeFamily super.from,
required (String, bool, {String? selectedLogFile}) super.argument,
}) : super(
@@ -61,7 +61,7 @@ final class ToolsLogAnalyzeFamily extends $Family
FutureOr<List<LogAnalyzeLineData>>,
(String, bool, {String? selectedLogFile})
> {
const ToolsLogAnalyzeFamily._()
ToolsLogAnalyzeFamily._()
: super(
retry: null,
name: r'toolsLogAnalyzeProvider',
@@ -102,11 +102,6 @@ abstract class _$ToolsLogAnalyze
@$mustCallSuper
@override
void runBuild() {
final created = build(
_$args.$1,
_$args.$2,
selectedLogFile: _$args.selectedLogFile,
);
final ref =
this.ref
as $Ref<
@@ -124,6 +119,10 @@ abstract class _$ToolsLogAnalyze
Object?,
Object?
>;
element.handleValue(ref, created);
element.handleCreate(
ref,
() =>
build(_$args.$1, _$args.$2, selectedLogFile: _$args.selectedLogFile),
);
}
}

View File

@@ -25,6 +25,7 @@ import 'package:xml/xml.dart';
import 'dialogs/hosts_booster_dialog_ui.dart';
import 'dialogs/rsi_launcher_enhance_dialog_ui.dart';
import 'dialogs/shader_cache_clean_dialog.dart';
import 'yearly_report_ui/yearly_report_ui.dart';
part 'tools_ui_model.g.dart';
@@ -72,13 +73,14 @@ class ToolsUIModel extends _$ToolsUIModel {
}
try {
items = [
ToolsItemData(
"systemnfo",
S.current.tools_action_view_system_info,
S.current.tools_action_info_view_critical_system_info,
const Icon(FluentIcons.system, size: 24),
onTap: () => _showSystemInfo(context),
),
if (Platform.isWindows)
ToolsItemData(
"systemnfo",
S.current.tools_action_view_system_info,
S.current.tools_action_info_view_critical_system_info,
const Icon(FluentIcons.system, size: 24),
onTap: () => _showSystemInfo(context),
),
];
// 年度报告入口 logic
@@ -108,13 +110,14 @@ class ToolsUIModel extends _$ToolsUIModel {
if (!context.mounted) return;
items.add(await _addP4kCard(context));
items.addAll([
ToolsItemData(
"hosts_booster",
S.current.tools_action_hosts_acceleration_experimental,
S.current.tools_action_info_hosts_acceleration_experimental_tip,
const Icon(FluentIcons.virtual_network, size: 24),
onTap: () => _doHostsBooster(context),
),
if (Platform.isWindows)
ToolsItemData(
"hosts_booster",
S.current.tools_action_hosts_acceleration_experimental,
S.current.tools_action_info_hosts_acceleration_experimental_tip,
const Icon(FluentIcons.virtual_network, size: 24),
onTap: () => _doHostsBooster(context),
),
ToolsItemData(
"log_analyze",
S.current.log_analyzer_title,
@@ -129,20 +132,22 @@ class ToolsUIModel extends _$ToolsUIModel {
const Icon(FluentIcons.c_plus_plus, size: 24),
onTap: () => rsiEnhance(context),
),
ToolsItemData(
"reinstall_eac",
S.current.tools_action_reinstall_easyanticheat,
S.current.tools_action_info_reinstall_eac,
const Icon(FluentIcons.game, size: 24),
onTap: () => _reinstallEAC(context),
),
ToolsItemData(
"rsilauncher_admin_mode",
S.current.tools_action_rsi_launcher_admin_mode,
S.current.tools_action_info_run_rsi_as_admin,
const Icon(FluentIcons.admin, size: 24),
onTap: () => _adminRSILauncher(context),
),
if (Platform.isWindows)
ToolsItemData(
"reinstall_eac",
S.current.tools_action_reinstall_easyanticheat,
S.current.tools_action_info_reinstall_eac,
const Icon(FluentIcons.game, size: 24),
onTap: () => _reinstallEAC(context),
),
if (Platform.isWindows)
ToolsItemData(
"rsilauncher_admin_mode",
S.current.tools_action_rsi_launcher_admin_mode,
S.current.tools_action_info_run_rsi_as_admin,
const Icon(FluentIcons.admin, size: 24),
onTap: () => _adminRSILauncher(context),
),
ToolsItemData(
"unp4kc",
S.current.tools_action_unp4k,
@@ -169,8 +174,10 @@ class ToolsUIModel extends _$ToolsUIModel {
if (!context.mounted) return;
items.add(await _addPhotographyCard(context));
state = state.copyWith(items: items);
if (!context.mounted) return;
items.addAll(await _addNvmePatchCard(context));
if (Platform.isWindows) {
if (!context.mounted) return;
items.addAll(await _addNvmePatchCard(context));
}
state = state.copyWith(items: items, isItemLoading: false);
} catch (e) {
if (!context.mounted) return;
@@ -250,7 +257,10 @@ class ToolsUIModel extends _$ToolsUIModel {
Future<ToolsItemData> _addShaderCard(BuildContext context) async {
final gameShaderCachePath = await SCLoggerHelper.getShaderCachePath();
final shaderSize =
((await SystemHelper.getDirLen(gameShaderCachePath ?? "", skipPath: ["$gameShaderCachePath\\Crashes"])) /
((await SystemHelper.getDirLen(
gameShaderCachePath ?? "",
skipPath: ["$gameShaderCachePath\\Crashes".platformPath],
)) /
1024 /
1024)
.toStringAsFixed(4);
@@ -300,7 +310,8 @@ class ToolsUIModel extends _$ToolsUIModel {
// 使用最新版本
final latestVersion = versions.first;
final settingsPath = "$gameShaderCachePath\\starcitizen_$latestVersion\\GraphicsSettings\\GraphicsSettings.json";
final settingsPath =
"$gameShaderCachePath\\starcitizen_$latestVersion\\GraphicsSettings\\GraphicsSettings.json".platformPath;
final file = File(settingsPath);
if (!await file.exists()) return (-1, latestVersion);
@@ -544,30 +555,101 @@ class ToolsUIModel extends _$ToolsUIModel {
state = state.copyWith(working: false);
}
/// 清理着色器缓存(全部清理模式),保留最新两个文件夹的 GraphicsSettings
static Future<void> cleanShaderCache() async {
await _cleanShaderCacheWithMode(keepLatest: false);
}
/// 清理着色器缓存(保留最新模式)
/// 保留最新文件夹的所有内容,保留第二新文件夹的 GraphicsSettings清理其他
static Future<void> cleanShaderCacheKeepLatest() async {
await _cleanShaderCacheWithMode(keepLatest: true);
}
/// 内部清理方法
/// [keepLatest] true: 保留最新模式false: 全部清理模式
static Future<void> _cleanShaderCacheWithMode({required bool keepLatest}) async {
final gameShaderCachePath = await SCLoggerHelper.getShaderCachePath();
final l = await Directory(gameShaderCachePath!).list(recursive: false).toList();
for (var value in l) {
if (value is Directory) {
final dirName = value.path.split(Platform.pathSeparator).last;
if (gameShaderCachePath == null) return;
final dir = Directory(gameShaderCachePath);
if (!await dir.exists()) return;
// 获取所有 starcitizen_* 目录并按创建时间排序
final scDirs = <Directory>[];
final otherDirs = <Directory>[];
await for (var entity in dir.list(recursive: false)) {
if (entity is Directory) {
final dirName = entity.path.split(Platform.pathSeparator).last;
if (dirName == "Crashes") continue;
// 对于 starcitizen_* 目录,手动遍历删除,保留 GraphicsSettings 文件夹
if (dirName.startsWith("starcitizen_")) {
await _cleanShaderCacheDirectory(value);
scDirs.add(entity);
} else {
await value.delete(recursive: true);
otherDirs.add(entity);
}
}
}
// 按目录名(版本号)降序排序,最新的在前面
scDirs.sort((a, b) {
final aName = a.path.split(Platform.pathSeparator).last;
final bName = b.path.split(Platform.pathSeparator).last;
return bName.compareTo(aName);
});
// 清理非 starcitizen_* 目录
for (var d in otherDirs) {
try {
await d.delete(recursive: true);
} catch (e) {
dPrint("_cleanShaderCacheWithMode delete other dir error: $e");
}
}
// 根据模式清理 starcitizen_* 目录
for (var i = 0; i < scDirs.length; i++) {
final scDir = scDirs[i];
if (keepLatest) {
// 保留最新模式:
// 第一个(最新):完全保留
// 第二个:仅保留 GraphicsSettings
// 其他:仅保留 GraphicsSettings
if (i == 0) {
// 最新的文件夹完全保留,不做任何操作
continue;
} else {
// 其他文件夹:清理内容,保留 GraphicsSettings
await _cleanShaderCacheDirectory(scDir);
}
} else {
// 全部清理模式:
// 前两个:仅保留 GraphicsSettings
// 其他:仅保留 GraphicsSettings
}
}
}
Future<void> _cleanShaderCache(BuildContext context) async {
state = state.copyWith(working: true);
await cleanShaderCache();
if (!context.mounted) return;
loadToolsCard(context, skipPathScan: true);
state = state.copyWith(working: false);
await showDialog<String>(
context: context,
builder: (dialogContext) => ShaderCacheCleanDialog(
onClean: (mode) async {
state = state.copyWith(working: true);
if (mode == "keep_latest") {
await cleanShaderCacheKeepLatest();
} else {
await cleanShaderCache();
}
if (!context.mounted) return;
loadToolsCard(context, skipPathScan: true);
state = state.copyWith(working: false);
Navigator.of(dialogContext).pop();
},
),
);
}
/// 清理着色器缓存目录,保留 GraphicsSettings 文件夹

View File

@@ -10,11 +10,11 @@ part of 'tools_ui_model.dart';
// ignore_for_file: type=lint, type=warning
@ProviderFor(ToolsUIModel)
const toolsUIModelProvider = ToolsUIModelProvider._();
final toolsUIModelProvider = ToolsUIModelProvider._();
final class ToolsUIModelProvider
extends $NotifierProvider<ToolsUIModel, ToolsUIState> {
const ToolsUIModelProvider._()
ToolsUIModelProvider._()
: super(
from: null,
argument: null,
@@ -41,14 +41,13 @@ final class ToolsUIModelProvider
}
}
String _$toolsUIModelHash() => r'd50710acb3cf2e858128541dbfe8389ea9db4452';
String _$toolsUIModelHash() => r'85ccd6ff3fe9622f9d2bfbf3c8385fd8c9363e7e';
abstract class _$ToolsUIModel extends $Notifier<ToolsUIState> {
ToolsUIState build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<ToolsUIState, ToolsUIState>;
final element =
ref.element
@@ -58,6 +57,6 @@ abstract class _$ToolsUIModel extends $Notifier<ToolsUIState> {
Object?,
Object?
>;
element.handleValue(ref, created);
element.handleCreate(ref, build);
}
}

View File

@@ -4,10 +4,10 @@ project(runner 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 "app")
set(BINARY_NAME "starcitizen_doctor")
# The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
set(APPLICATION_ID "com.example.app")
set(APPLICATION_ID "com.starcitizen_doctor")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.
@@ -53,6 +53,7 @@ add_subdirectory(${FLUTTER_MANAGED_DIR})
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
pkg_check_modules(GDK_PIXBUF REQUIRED IMPORTED_TARGET gdk-pixbuf-2.0)
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
@@ -73,6 +74,7 @@ apply_standard_settings(${BINARY_NAME})
# Add dependency libraries. Add any application-specific dependencies here.
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GDK_PIXBUF)
# Run the Flutter tool portions of the build. This must not be removed.
add_dependencies(${BINARY_NAME} flutter_assemble)
@@ -143,3 +145,13 @@ if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()
# Install application icons
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/icons/hicolor"
DESTINATION "share/icons"
COMPONENT Runtime)
# Install desktop file
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/com.starcitizen_doctor.desktop"
DESTINATION "share/applications"
COMPONENT Runtime)

View File

@@ -0,0 +1,9 @@
[Desktop Entry]
Type=Application
Name=SC汉化盒子
Comment=Star Citizen Tool Box for Chinese players
Exec=starcitizen_doctor
Icon=starcitizen_doctor
Categories=Utility;Game;
Terminal=false
StartupNotify=true

View File

@@ -8,6 +8,7 @@
#include <desktop_multi_window/desktop_multi_window_plugin.h>
#include <flutter_acrylic/flutter_acrylic_plugin.h>
#include <gtk/gtk_plugin.h>
#include <screen_retriever_linux/screen_retriever_linux_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
#include <window_manager/window_manager_plugin.h>
@@ -19,6 +20,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) flutter_acrylic_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterAcrylicPlugin");
flutter_acrylic_plugin_register_with_registrar(flutter_acrylic_registrar);
g_autoptr(FlPluginRegistrar) gtk_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin");
gtk_plugin_register_with_registrar(gtk_registrar);
g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin");
screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar);

View File

@@ -5,6 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
desktop_multi_window
flutter_acrylic
gtk
screen_retriever_linux
url_launcher_linux
window_manager

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 879 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -4,6 +4,7 @@
#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
#endif
#include <glib.h>
#include "flutter/generated_plugin_registrant.h"
@@ -19,6 +20,13 @@ G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
// Implements GApplication::activate.
static void my_application_activate(GApplication* application) {
MyApplication* self = MY_APPLICATION(application);
GList* windows = gtk_application_get_windows(GTK_APPLICATION(application));
if (windows) {
gtk_window_present(GTK_WINDOW(windows->data));
return;
}
GtkWindow* window =
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
@@ -42,11 +50,11 @@ static void my_application_activate(GApplication* application) {
if (use_header_bar) {
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_widget_show(GTK_WIDGET(header_bar));
gtk_header_bar_set_title(header_bar, "app");
gtk_header_bar_set_title(header_bar, "SC汉化盒子");
gtk_header_bar_set_show_close_button(header_bar, TRUE);
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
} else {
gtk_window_set_title(window, "app");
gtk_window_set_title(window, "SC汉化盒子");
}
gtk_window_set_default_size(window, 1280, 720);
@@ -84,7 +92,7 @@ static gboolean my_application_local_command_line(GApplication* application, gch
g_application_activate(application);
*exit_status = 0;
return TRUE;
return FALSE;
}
// Implements GApplication::startup.
@@ -125,6 +133,6 @@ static void my_application_init(MyApplication* self) {}
MyApplication* my_application_new() {
return MY_APPLICATION(g_object_new(my_application_get_type(),
"application-id", APPLICATION_ID,
"flags", G_APPLICATION_NON_UNIQUE,
"flags", G_APPLICATION_HANDLES_COMMAND_LINE | G_APPLICATION_HANDLES_OPEN,
nullptr));
}

10
linux/sctoolbox.desktop Normal file
View File

@@ -0,0 +1,10 @@
[Desktop Entry]
Version=1.0
Type=Application
Name=StarCitizen ToolBox
Comment=StarCitizen ToolBox Application
Exec=app %u
Icon=app
Terminal=false
Categories=Utility;
MimeType=x-scheme-handler/sctoolbox;

View File

@@ -5,6 +5,7 @@
import FlutterMacOS
import Foundation
import app_links
import desktop_multi_window
import device_info_plus
import file_picker
@@ -15,6 +16,7 @@ import url_launcher_macos
import window_manager
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
FlutterMultiWindowPlugin.register(with: registry.registrar(forPlugin: "FlutterMultiWindowPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))

View File

@@ -1,4 +1,6 @@
PODS:
- app_links (6.4.1):
- FlutterMacOS
- desktop_multi_window (0.0.1):
- FlutterMacOS
- device_info_plus (0.0.1):
@@ -21,6 +23,7 @@ PODS:
- FlutterMacOS
DEPENDENCIES:
- app_links (from `Flutter/ephemeral/.symlinks/plugins/app_links/macos`)
- desktop_multi_window (from `Flutter/ephemeral/.symlinks/plugins/desktop_multi_window/macos`)
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
- file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`)
@@ -33,6 +36,8 @@ DEPENDENCIES:
- window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`)
EXTERNAL SOURCES:
app_links:
:path: Flutter/ephemeral/.symlinks/plugins/app_links/macos
desktop_multi_window:
:path: Flutter/ephemeral/.symlinks/plugins/desktop_multi_window/macos
device_info_plus:
@@ -55,6 +60,7 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos
SPEC CHECKSUMS:
app_links: 05a6ec2341985eb05e9f97dc63f5837c39895c3f
desktop_multi_window: 93667594ccc4b88d91a97972fd3b1b89667fa80a
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a

View File

@@ -31,6 +31,17 @@
<string>$(PRODUCT_COPYRIGHT)</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.xkeyc.sctoolbox</string>
<key>CFBundleURLSchemes</key>
<array>
<string>sctoolbox</string>
</array>
</dict>
</array>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>

View File

@@ -9,6 +9,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "91.0.0"
analysis_server_plugin:
dependency: transitive
description:
name: analysis_server_plugin
sha256: "26844e7f977087567135d62532b67d5639fe206c5194c3f410ba75e1a04a2747"
url: "https://pub.dev"
source: hosted
version: "0.3.3"
analyzer:
dependency: transitive
description:
@@ -41,6 +49,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.2.0"
app_links:
dependency: "direct main"
description:
name: app_links
sha256: "3462d9defc61565fde4944858b59bec5be2b9d5b05f20aed190adb3ad08a7abc"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
app_links_linux:
dependency: transitive
description:
name: app_links_linux
sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81
url: "https://pub.dev"
source: hosted
version: "1.0.3"
app_links_platform_interface:
dependency: transitive
description:
name: app_links_platform_interface
sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
app_links_web:
dependency: transitive
description:
name: app_links_web
sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555
url: "https://pub.dev"
source: hosted
version: "1.0.4"
archive:
dependency: "direct main"
description:
@@ -109,10 +149,10 @@ packages:
dependency: "direct dev"
description:
name: build_runner
sha256: "110c56ef29b5eb367b4d17fc79375fa8c18a6cd7acd92c05bb3986c17a079057"
sha256: b4d854962a32fd9f8efc0b76f98214790b833af8b2e9b2df6bfc927c0415a072
url: "https://pub.dev"
source: hosted
version: "2.10.4"
version: "2.10.5"
built_collection:
dependency: transitive
description:
@@ -265,14 +305,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.8.1"
custom_lint_builder:
dependency: transitive
description:
name: custom_lint_builder
sha256: "1128db6f58e71d43842f3b9be7465c83f0c47f4dd8918f878dd6ad3b72a32072"
url: "https://pub.dev"
source: hosted
version: "0.8.1"
custom_lint_core:
dependency: transitive
description:
@@ -333,10 +365,10 @@ packages:
dependency: "direct main"
description:
name: dio
sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
sha256: b9d46faecab38fc8cc286f80bc4d61a3bb5d4ac49e51ed877b4d6706efe57b25
url: "https://pub.dev"
source: hosted
version: "5.9.0"
version: "5.9.1"
dio_web_adapter:
dependency: transitive
description:
@@ -373,10 +405,10 @@ packages:
dependency: "direct main"
description:
name: ffi
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c
url: "https://pub.dev"
source: hosted
version: "2.1.4"
version: "2.1.5"
ffigen:
dependency: "direct dev"
description:
@@ -397,10 +429,10 @@ packages:
dependency: "direct main"
description:
name: file_picker
sha256: d974b6ba2606371ac71dd94254beefb6fa81185bde0b59bdc1df09885da85fde
sha256: "57d9a1dd5063f85fa3107fb42d1faffda52fdc948cefd5fe5ea85267a5fc7343"
url: "https://pub.dev"
source: hosted
version: "10.3.8"
version: "10.3.10"
file_sizes:
dependency: "direct main"
description:
@@ -479,10 +511,10 @@ packages:
dependency: "direct main"
description:
name: flutter_riverpod
sha256: "9e2d6907f12cc7d23a846847615941bddee8709bf2bfd274acdf5e80bcf22fde"
sha256: "38ec6c303e2c83ee84512f5fc2a82ae311531021938e63d7137eccc107bf3c02"
url: "https://pub.dev"
source: hosted
version: "3.0.3"
version: "3.1.0"
flutter_rust_bridge:
dependency: "direct main"
description:
@@ -561,10 +593,10 @@ packages:
dependency: transitive
description:
name: get_it
sha256: ae78de7c3f2304b8d81f2bb6e320833e5e81de942188542328f074978cc0efa9
sha256: "1d648d2dd2047d7f7450d5727ca24ee435f240385753d90b49650e3cdff32e56"
url: "https://pub.dev"
source: hosted
version: "8.3.0"
version: "9.2.0"
glob:
dependency: transitive
description:
@@ -613,6 +645,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.1.0"
gtk:
dependency: transitive
description:
name: gtk
sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c
url: "https://pub.dev"
source: hosted
version: "2.1.0"
hexcolor:
dependency: "direct main"
description:
@@ -633,26 +673,18 @@ packages:
dependency: "direct main"
description:
name: hive_ce
sha256: "412c638aeac0f003bba664884e3048b9547e541aaca13f10cc639da788184bed"
sha256: b844955c89f61f479170632b971dcf6fbb8e7233d2a5c2e3c7b89e1b2986bdb5
url: "https://pub.dev"
source: hosted
version: "2.16.0"
version: "2.19.1"
hooks_riverpod:
dependency: "direct main"
description:
name: hooks_riverpod
sha256: ae4a2f6d82dd895379f9b95457e090ac2d2fef9446f9325f8d31b9c86cadc131
sha256: b880efcd17757af0aa242e5dceac2fb781a014c22a32435a5daa8f17e9d5d8a9
url: "https://pub.dev"
source: hosted
version: "3.0.3"
hotreloader:
dependency: transitive
description:
name: hotreloader
sha256: bc167a1163807b03bada490bfe2df25b0d744df359227880220a5cbd04e5734b
url: "https://pub.dev"
source: hosted
version: "4.3.0"
version: "3.1.0"
html:
dependency: "direct main"
description:
@@ -729,10 +761,10 @@ packages:
dependency: transitive
description:
name: isolate_channel
sha256: f3d36f783b301e6b312c3450eeb2656b0e7d1db81331af2a151d9083a3f6b18d
sha256: "000d617d021a608186b468584bbc6df2509ecba048f08510f832fdb9cf7aafbe"
url: "https://pub.dev"
source: hosted
version: "0.2.2+1"
version: "0.4.1"
isolate_contactor:
dependency: transitive
description:
@@ -897,10 +929,10 @@ packages:
dependency: "direct dev"
description:
name: msix
sha256: f88033fcb9e0dd8de5b18897cbebbd28ea30596810f4a7c86b12b0c03ace87e5
sha256: b6b08e7a7b5d1845f2b1d31216d5b1fb558e98251efefe54eb79ed00d27bc2ac
url: "https://pub.dev"
source: hosted
version: "3.16.12"
version: "3.16.13"
node_preamble:
dependency: transitive
description:
@@ -1097,42 +1129,42 @@ packages:
dependency: transitive
description:
name: riverpod
sha256: c406de02bff19d920b832bddfb8283548bfa05ce41c59afba57ce643e116aa59
sha256: "16ff608d21e8ea64364f2b7c049c94a02ab81668f78845862b6e88b71dd4935a"
url: "https://pub.dev"
source: hosted
version: "3.0.3"
version: "3.1.0"
riverpod_analyzer_utils:
dependency: transitive
description:
name: riverpod_analyzer_utils
sha256: a0f68adb078b790faa3c655110a017f9a7b7b079a57bbd40f540e80dce5fcd29
sha256: "947b05d04c52a546a2ac6b19ef2a54b08520ff6bdf9f23d67957a4c8df1c3bc0"
url: "https://pub.dev"
source: hosted
version: "1.0.0-dev.7"
version: "1.0.0-dev.8"
riverpod_annotation:
dependency: "direct main"
description:
name: riverpod_annotation
sha256: "7230014155777fc31ba3351bc2cb5a3b5717b11bfafe52b1553cb47d385f8897"
sha256: cc1474bc2df55ec3c1da1989d139dcef22cd5e2bd78da382e867a69a8eca2e46
url: "https://pub.dev"
source: hosted
version: "3.0.3"
version: "4.0.0"
riverpod_generator:
dependency: "direct dev"
description:
name: riverpod_generator
sha256: "49894543a42cf7a9954fc4e7366b6d3cb2e6ec0fa07775f660afcdd92d097702"
sha256: e43b1537229cc8f487f09b0c20d15dba840acbadcf5fc6dad7ad5e8ab75950dc
url: "https://pub.dev"
source: hosted
version: "3.0.3"
version: "4.0.0+1"
riverpod_lint:
dependency: "direct dev"
description:
name: riverpod_lint
sha256: "7ef9c43469e9b5ac4e4c3b24d7c30642e47ce1b12cd7dcdd643534db0a72ed13"
sha256: "4d2eb0d19bbe7e3323bd0ce4553b2e6170d161a13914bfdd85a3612329edcb43"
url: "https://pub.dev"
source: hosted
version: "3.0.3"
version: "3.1.0"
rust_builder:
dependency: "direct main"
description:
@@ -1532,10 +1564,10 @@ packages:
dependency: "direct main"
description:
name: watcher
sha256: f52385d4f73589977c80797e60fe51014f7f2b957b5e9a62c3f6ada439889249
sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
version: "1.2.1"
web:
dependency: transitive
description:
@@ -1618,5 +1650,5 @@ packages:
source: hosted
version: "2.2.3"
sdks:
dart: ">=3.9.0 <4.0.0"
flutter: ">=3.35.0"
dart: ">=3.10.0 <4.0.0"
flutter: ">=3.38.1"

View File

@@ -2,7 +2,7 @@ name: starcitizen_doctor
description: Designed for "StarCitizen" players, providing many convenient functions.
publish_to: 'none'
version: 3.0.1+78
version: 3.1.0+80
environment:
sdk: ^3.8.0
@@ -13,10 +13,10 @@ dependencies:
flutter_localizations:
sdk: flutter
flutter_riverpod: ^3.0.3
riverpod_annotation: ^3.0.3
flutter_riverpod: ^3.1.0
riverpod_annotation: ^4.0.0
flutter_hooks: ^0.21.3+1
hooks_riverpod: ^3.0.3
hooks_riverpod: ^3.1.0
json_annotation: ^4.9.0
go_router: ^17.0.1
window_manager:
@@ -30,15 +30,15 @@ dependencies:
url_launcher: ^6.3.2
font_awesome_flutter: ^10.12.0
cupertino_icons: ^1.0.8
hive_ce: ^2.16.0
hive_ce: ^2.19.1
path_provider: ^2.1.5
dio: ^5.9.0
dio: ^5.9.1
markdown: ^7.3.0
markdown_widget: ^2.3.2+8
extended_image: ^10.0.1
animate_do: ^4.2.0
device_info_plus: ^12.3.0
file_picker: ^10.3.8
file_picker: ^10.3.10
file_sizes: ^1.0.6
flutter_svg: ^2.2.3
archive: ^4.0.7
@@ -46,7 +46,7 @@ dependencies:
uuid: ^4.5.2
flutter_tilt: ^3.3.2
card_swiper: ^3.0.1
ffi: ^2.1.4
ffi: ^2.1.5
flutter_rust_bridge: ^2.11.1
freezed_annotation: ^3.1.0
meta: ^1.17.0
@@ -65,14 +65,15 @@ dependencies:
shelf: ^1.4.2
qr_flutter: ^4.1.0
desktop_multi_window: ^0.3.0
watcher: ^1.2.0
watcher: ^1.2.1
path: ^1.9.1
crypto: ^3.0.7
xml: ^6.6.1
# gRPC and protobuf
grpc: ^5.0.0
grpc: ^5.1.0
protobuf: ^6.0.0
app_links: ^7.0.0
dependency_overrides:
http: ^1.6.0
intl: ^0.20.2
@@ -81,13 +82,13 @@ dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^6.0.0
msix: ^3.16.12
build_runner: ^2.10.4
msix: ^3.16.13
build_runner: ^2.10.5
freezed: ^3.2.3
json_serializable: ^6.11.2
riverpod_generator: ^3.0.3
riverpod_generator: ^4.0.0+1
custom_lint: ^0.8.1
riverpod_lint: ^3.0.3
riverpod_lint: ^3.1.0
ffigen: ^20.1.1
sct_dev_tools:
path: ./packages/sct_dev_tools
@@ -112,11 +113,12 @@ msix_config:
publisher_display_name: xkeyC Studio
identity_name: 56575xkeyC.MSE
publisher: CN=B54C897B-C263-4680-B6AB-4913C603DF87
msix_version: 3.0.9.0
msix_version: 3.1.0.0
logo_path: ./assets/app_logo.png
capabilities: internetClient,allowElevation
languages: zh-cn
windows_build_args: --dart-define="MSE=true"
store: true
protocol_activation: sctoolbox
flutter_intl:
enabled: true

228
rust/Cargo.lock generated
View File

@@ -146,9 +146,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.100"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
[[package]]
name = "arbitrary"
@@ -683,9 +683,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]]
name = "bytes"
version = "1.11.0"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
[[package]]
name = "bzip2"
@@ -763,7 +763,7 @@ version = "0.15.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02"
dependencies = [
"smallvec 1.15.1",
"smallvec",
"target-lexicon",
]
@@ -1186,7 +1186,7 @@ dependencies = [
"phf 0.10.1",
"proc-macro2",
"quote",
"smallvec 1.15.1",
"smallvec",
"syn 1.0.109",
]
@@ -1710,18 +1710,6 @@ dependencies = [
"rustc_version",
]
[[package]]
name = "filetime"
version = "0.2.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed"
dependencies = [
"cfg-if",
"libc",
"libredox",
"windows-sys 0.60.2",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.5"
@@ -2160,7 +2148,7 @@ dependencies = [
"libc",
"once_cell",
"pin-project-lite",
"smallvec 1.15.1",
"smallvec",
"thiserror 1.0.69",
]
@@ -2196,7 +2184,7 @@ dependencies = [
"libc",
"memchr",
"once_cell",
"smallvec 1.15.1",
"smallvec",
"thiserror 1.0.69",
]
@@ -2284,7 +2272,7 @@ dependencies = [
"portable-atomic",
"quanta",
"rand 0.9.2",
"smallvec 1.15.1",
"smallvec",
"spinning_top",
"web-time",
]
@@ -2447,7 +2435,7 @@ dependencies = [
"parking_lot",
"rand 0.9.2",
"resolv-conf",
"smallvec 1.15.1",
"smallvec",
"thiserror 2.0.17",
"tokio",
"tracing",
@@ -2462,6 +2450,12 @@ dependencies = [
"digest",
]
[[package]]
name = "hmac-sha256"
version = "1.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad6880c8d4a9ebf39c6e8b77007ce223f646a4d21ce29d99f70cb16420545425"
[[package]]
name = "html5ever"
version = "0.29.1"
@@ -2537,7 +2531,7 @@ dependencies = [
"itoa",
"pin-project-lite",
"pin-utils",
"smallvec 1.15.1",
"smallvec",
"tokio",
"want",
]
@@ -2661,7 +2655,7 @@ dependencies = [
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec 1.15.1",
"smallvec",
"zerovec",
]
@@ -2719,7 +2713,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
dependencies = [
"idna_adapter",
"smallvec 1.15.1",
"smallvec",
"utf8_iter",
]
@@ -2804,7 +2798,7 @@ version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "270bc34e57047cab801a8c871c124d9dc7132f6473c6401f645524f4e6edd111"
dependencies = [
"smallvec 1.15.1",
"smallvec",
]
[[package]]
@@ -2988,13 +2982,12 @@ checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50"
dependencies = [
"bitflags 2.10.0",
"libc",
"redox_syscall 0.6.0",
]
[[package]]
name = "librqbit"
version = "9.0.0-beta.1"
source = "git+https://github.com/StarCitizenToolBox/rqbit?rev=f8c0b0927904e1d8b0e28e708bd69fd8069d413a#f8c0b0927904e1d8b0e28e708bd69fd8069d413a"
version = "9.0.0-beta.2"
source = "git+https://github.com/StarCitizenToolBox/rqbit?rev=42adca125d3c4781bcef14c94a899b53814178c0#42adca125d3c4781bcef14c94a899b53814178c0"
dependencies = [
"anyhow",
"arc-swap",
@@ -3058,7 +3051,7 @@ dependencies = [
[[package]]
name = "librqbit-bencode"
version = "3.1.0"
source = "git+https://github.com/StarCitizenToolBox/rqbit?rev=f8c0b0927904e1d8b0e28e708bd69fd8069d413a#f8c0b0927904e1d8b0e28e708bd69fd8069d413a"
source = "git+https://github.com/StarCitizenToolBox/rqbit?rev=42adca125d3c4781bcef14c94a899b53814178c0#42adca125d3c4781bcef14c94a899b53814178c0"
dependencies = [
"anyhow",
"arrayvec",
@@ -3074,7 +3067,7 @@ dependencies = [
[[package]]
name = "librqbit-buffers"
version = "4.2.0"
source = "git+https://github.com/StarCitizenToolBox/rqbit?rev=f8c0b0927904e1d8b0e28e708bd69fd8069d413a#f8c0b0927904e1d8b0e28e708bd69fd8069d413a"
source = "git+https://github.com/StarCitizenToolBox/rqbit?rev=42adca125d3c4781bcef14c94a899b53814178c0#42adca125d3c4781bcef14c94a899b53814178c0"
dependencies = [
"bytes",
"librqbit-clone-to-owned",
@@ -3085,7 +3078,7 @@ dependencies = [
[[package]]
name = "librqbit-clone-to-owned"
version = "3.0.1"
source = "git+https://github.com/StarCitizenToolBox/rqbit?rev=f8c0b0927904e1d8b0e28e708bd69fd8069d413a#f8c0b0927904e1d8b0e28e708bd69fd8069d413a"
source = "git+https://github.com/StarCitizenToolBox/rqbit?rev=42adca125d3c4781bcef14c94a899b53814178c0#42adca125d3c4781bcef14c94a899b53814178c0"
dependencies = [
"bytes",
]
@@ -3093,7 +3086,7 @@ dependencies = [
[[package]]
name = "librqbit-core"
version = "5.0.0"
source = "git+https://github.com/StarCitizenToolBox/rqbit?rev=f8c0b0927904e1d8b0e28e708bd69fd8069d413a#f8c0b0927904e1d8b0e28e708bd69fd8069d413a"
source = "git+https://github.com/StarCitizenToolBox/rqbit?rev=42adca125d3c4781bcef14c94a899b53814178c0#42adca125d3c4781bcef14c94a899b53814178c0"
dependencies = [
"anyhow",
"bytes",
@@ -3122,7 +3115,7 @@ dependencies = [
[[package]]
name = "librqbit-dht"
version = "5.3.0"
source = "git+https://github.com/StarCitizenToolBox/rqbit?rev=f8c0b0927904e1d8b0e28e708bd69fd8069d413a#f8c0b0927904e1d8b0e28e708bd69fd8069d413a"
source = "git+https://github.com/StarCitizenToolBox/rqbit?rev=42adca125d3c4781bcef14c94a899b53814178c0#42adca125d3c4781bcef14c94a899b53814178c0"
dependencies = [
"anyhow",
"backon",
@@ -3169,7 +3162,7 @@ dependencies = [
[[package]]
name = "librqbit-lsd"
version = "0.1.0"
source = "git+https://github.com/StarCitizenToolBox/rqbit?rev=f8c0b0927904e1d8b0e28e708bd69fd8069d413a#f8c0b0927904e1d8b0e28e708bd69fd8069d413a"
source = "git+https://github.com/StarCitizenToolBox/rqbit?rev=42adca125d3c4781bcef14c94a899b53814178c0#42adca125d3c4781bcef14c94a899b53814178c0"
dependencies = [
"anyhow",
"atoi",
@@ -3189,7 +3182,7 @@ dependencies = [
[[package]]
name = "librqbit-peer-protocol"
version = "4.3.0"
source = "git+https://github.com/StarCitizenToolBox/rqbit?rev=f8c0b0927904e1d8b0e28e708bd69fd8069d413a#f8c0b0927904e1d8b0e28e708bd69fd8069d413a"
source = "git+https://github.com/StarCitizenToolBox/rqbit?rev=42adca125d3c4781bcef14c94a899b53814178c0#42adca125d3c4781bcef14c94a899b53814178c0"
dependencies = [
"anyhow",
"bitvec",
@@ -3209,7 +3202,7 @@ dependencies = [
[[package]]
name = "librqbit-sha1-wrapper"
version = "4.1.0"
source = "git+https://github.com/StarCitizenToolBox/rqbit?rev=f8c0b0927904e1d8b0e28e708bd69fd8069d413a#f8c0b0927904e1d8b0e28e708bd69fd8069d413a"
source = "git+https://github.com/StarCitizenToolBox/rqbit?rev=42adca125d3c4781bcef14c94a899b53814178c0#42adca125d3c4781bcef14c94a899b53814178c0"
dependencies = [
"assert_cfg",
"aws-lc-rs",
@@ -3218,7 +3211,7 @@ dependencies = [
[[package]]
name = "librqbit-tracker-comms"
version = "3.0.0"
source = "git+https://github.com/StarCitizenToolBox/rqbit?rev=f8c0b0927904e1d8b0e28e708bd69fd8069d413a#f8c0b0927904e1d8b0e28e708bd69fd8069d413a"
source = "git+https://github.com/StarCitizenToolBox/rqbit?rev=42adca125d3c4781bcef14c94a899b53814178c0#42adca125d3c4781bcef14c94a899b53814178c0"
dependencies = [
"anyhow",
"async-stream",
@@ -3246,7 +3239,7 @@ dependencies = [
[[package]]
name = "librqbit-upnp"
version = "1.0.0"
source = "git+https://github.com/StarCitizenToolBox/rqbit?rev=f8c0b0927904e1d8b0e28e708bd69fd8069d413a#f8c0b0927904e1d8b0e28e708bd69fd8069d413a"
source = "git+https://github.com/StarCitizenToolBox/rqbit?rev=42adca125d3c4781bcef14c94a899b53814178c0#42adca125d3c4781bcef14c94a899b53814178c0"
dependencies = [
"anyhow",
"bstr",
@@ -3345,6 +3338,12 @@ dependencies = [
"sha2",
]
[[package]]
name = "lzma-rust2"
version = "0.15.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17f7337d278fec032975dc884152491580dd23750ee957047856735fe0e61ede"
[[package]]
name = "mac"
version = "0.1.1"
@@ -3525,7 +3524,7 @@ dependencies = [
"equivalent",
"parking_lot",
"portable-atomic",
"smallvec 1.15.1",
"smallvec",
"tagptr",
"uuid",
]
@@ -3581,24 +3580,9 @@ dependencies = [
[[package]]
name = "ndarray"
version = "0.16.1"
version = "0.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841"
dependencies = [
"matrixmultiply",
"num-complex 0.4.6",
"num-integer",
"num-traits",
"portable-atomic",
"portable-atomic-util",
"rawpointer",
]
[[package]]
name = "ndarray"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c7c9125e8f6f10c9da3aad044cc918cf8784fa34de857b1aa68038eb05a50a9"
checksum = "520080814a7a6b4a6e9070823bb24b4531daac8c4627e08ba5de8c5ef2f2752d"
dependencies = [
"matrixmultiply",
"num-complex 0.4.6",
@@ -3694,9 +3678,9 @@ checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21"
[[package]]
name = "notify-rust"
version = "4.11.7"
version = "4.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6442248665a5aa2514e794af3b39661a8e73033b1cc5e59899e1276117ee4400"
checksum = "21af20a1b50be5ac5861f74af1a863da53a11c38684d9818d82f1c42f7fdc6c2"
dependencies = [
"futures-lite",
"log",
@@ -3740,9 +3724,9 @@ dependencies = [
[[package]]
name = "num-conv"
version = "0.1.0"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
[[package]]
name = "num-integer"
@@ -4012,26 +3996,25 @@ dependencies = [
[[package]]
name = "ort"
version = "2.0.0-rc.10"
version = "2.0.0-rc.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa7e49bd669d32d7bc2a15ec540a527e7764aec722a45467814005725bcd721"
checksum = "4a5df903c0d2c07b56950f1058104ab0c8557159f2741782223704de9be73c3c"
dependencies = [
"ndarray 0.16.1",
"ndarray",
"ort-sys",
"smallvec 2.0.0-alpha.10",
"smallvec",
"tracing",
"ureq",
]
[[package]]
name = "ort-sys"
version = "2.0.0-rc.10"
version = "2.0.0-rc.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2aba9f5c7c479925205799216e7e5d07cc1d4fa76ea8058c60a9a30f6a4e890"
checksum = "06503bb33f294c5f1ba484011e053bfa6ae227074bdb841e9863492dc5960d4b"
dependencies = [
"flate2",
"pkg-config",
"sha2",
"tar",
"hmac-sha256",
"lzma-rust2 0.15.6",
"ureq",
]
@@ -4101,8 +4084,8 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.5.18",
"smallvec 1.15.1",
"redox_syscall",
"smallvec",
"windows-link 0.2.1",
]
@@ -4747,15 +4730,6 @@ dependencies = [
"bitflags 2.10.0",
]
[[package]]
name = "redox_syscall"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5"
dependencies = [
"bitflags 2.10.0",
]
[[package]]
name = "redox_users"
version = "0.5.2"
@@ -4918,7 +4892,7 @@ dependencies = [
"hickory-resolver",
"image",
"librqbit",
"ndarray 0.17.1",
"ndarray",
"notify-rust",
"once_cell",
"ort",
@@ -5108,7 +5082,7 @@ dependencies = [
"phf_codegen 0.8.0",
"precomputed-hash",
"servo_arc",
"smallvec 1.15.1",
"smallvec",
]
[[package]]
@@ -5162,15 +5136,15 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.146"
version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "217ca874ae0207aac254aa02c957ded05585a90892cc8d87f9e5fa49669dadd8"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
"serde_core",
"zmij",
]
[[package]]
@@ -5343,12 +5317,6 @@ version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "smallvec"
version = "2.0.0-alpha.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d44cfb396c3caf6fbfd0ab422af02631b69ddd96d2eff0b0f0724f9024051b"
[[package]]
name = "socket2"
version = "0.5.10"
@@ -5616,17 +5584,6 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tar"
version = "0.4.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a"
dependencies = [
"filetime",
"libc",
"xattr",
]
[[package]]
name = "target-lexicon"
version = "0.12.16"
@@ -5729,30 +5686,30 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.44"
version = "0.3.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
dependencies = [
"deranged",
"itoa",
"num-conv",
"powerfmt",
"serde",
"serde_core",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.6"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
[[package]]
name = "time-macros"
version = "0.2.24"
version = "0.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
dependencies = [
"num-conv",
"time-core",
@@ -5818,9 +5775,9 @@ dependencies = [
[[package]]
name = "tokio"
version = "1.48.0"
version = "1.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
dependencies = [
"bytes",
"libc",
@@ -6125,7 +6082,7 @@ version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43f613e4fa046e69818dd287fdc4bc78175ff20331479dab6e1b0f98d57062de"
dependencies = [
"smallvec 1.15.1",
"smallvec",
]
[[package]]
@@ -6230,14 +6187,15 @@ dependencies = [
[[package]]
name = "url"
version = "2.5.7"
version = "2.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b"
checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
"serde",
"serde_derive",
]
[[package]]
@@ -6266,9 +6224,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.19.0"
version = "1.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f"
dependencies = [
"getrandom 0.3.4",
"js-sys",
@@ -6454,9 +6412,9 @@ dependencies = [
[[package]]
name = "webkit2gtk"
version = "2.0.1"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a"
checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793"
dependencies = [
"bitflags 1.3.2",
"cairo-rs",
@@ -6478,9 +6436,9 @@ dependencies = [
[[package]]
name = "webkit2gtk-sys"
version = "2.0.1"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c"
checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5"
dependencies = [
"bitflags 1.3.2",
"cairo-sys-rs",
@@ -7140,9 +7098,9 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "wmi"
version = "0.18.0"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71d1d435f7745ba9ed55c43049d47b5fbd1104449beaa2afbc80a1e10a4a018"
checksum = "746791db82f029aaefc774ccbb8e61306edba18ef2c8998337cadccc0b8067f7"
dependencies = [
"chrono",
"futures",
@@ -7161,9 +7119,9 @@ checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
[[package]]
name = "wry"
version = "0.53.5"
version = "0.54.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728b7d4c8ec8d81cab295e0b5b8a4c263c0d41a785fb8f8c4df284e5411140a2"
checksum = "5ed1a195b0375491dd15a7066a10251be217ce743cf4bbbbdcf5391d6473bee0"
dependencies = [
"base64 0.22.1",
"block2",
@@ -7234,16 +7192,6 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "xattr"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
dependencies = [
"libc",
"rustix",
]
[[package]]
name = "yoke"
version = "0.8.1"
@@ -7438,7 +7386,7 @@ dependencies = [
"getrandom 0.3.4",
"hmac",
"indexmap 2.12.1",
"lzma-rust2",
"lzma-rust2 0.13.0",
"memchr",
"pbkdf2",
"ppmd-rust",
@@ -7455,6 +7403,12 @@ version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3"
[[package]]
name = "zmij"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f4a4e8e9dc5c62d159f04fcdbe07f4c3fb710415aab4754bf11505501e3251d"
[[package]]
name = "zopfli"
version = "0.8.3"

View File

@@ -15,34 +15,55 @@ crate-type = ["cdylib", "staticlib"]
[dependencies]
flutter_rust_bridge = "=2.11.1"
tokio = { version = "1.48.0", features = ["rt", "rt-multi-thread", "macros", "process", "sync"] }
futures = { version = "0.3.31", default-features = false, features = ["executor"] }
url = "2.5.7"
tokio = { version = "1.49.0", features = [
"rt",
"rt-multi-thread",
"macros",
"process",
"sync",
] }
futures = { version = "0.3.31", default-features = false, features = [
"executor",
] }
url = "2.5.8"
once_cell = "1.21.3"
reqwest = { version = "0.12.26", features = ["rustls-tls-webpki-roots", "cookies", "gzip", "json", "stream"] }
reqwest = { version = "0.12.26", features = [
"rustls-tls-webpki-roots",
"cookies",
"gzip",
"json",
"stream",
] }
hickory-resolver = { version = "0.25.2" }
anyhow = "1.0.100"
anyhow = "1.0.101"
scopeguard = "1.0"
notify-rust = "4.11.7"
notify-rust = "4.12.0"
asar = "0.3.0"
walkdir = "2.5.0"
ort = { version = "2.0.0-rc.10", features = ["xnnpack", "download-binaries", "ndarray"] }
tokenizers = { version = "0.22.2", default-features = false, features = ["onig"] }
ndarray = "0.17.1"
serde_json = "1.0.145"
ort = { version = "2.0.0-rc.11", features = [
"xnnpack",
"download-binaries",
"ndarray",
] }
tokenizers = { version = "0.22.2", default-features = false, features = [
"onig",
] }
ndarray = "0.17.2"
serde_json = "1.0.149"
serde = { version = "1.0.228", features = ["derive"] }
unp4k_rs = { git = "https://github.com/StarCitizenToolBox/unp4k_rs", rev = "29430002d6f3a8d87feabac26c03ae0311c81263" }
#unp4k_rs = { path = "../../unp4k_rs" }
uuid = { version = "1.19.0", features = ["v4"] }
uuid = { version = "1.20.0", features = ["v4"] }
rayon = "1.10"
parking_lot = "0.12.5"
crossbeam-channel = "0.5.15"
librqbit = { git = "https://github.com/StarCitizenToolBox/rqbit", rev = "f8c0b0927904e1d8b0e28e708bd69fd8069d413a" }
bytes = "1.10"
librqbit = { git = "https://github.com/StarCitizenToolBox/rqbit", rev = "42adca125d3c4781bcef14c94a899b53814178c0" }
# librqbit = { path = "../../rqbit/crates/librqbit" }
bytes = "1.11"
# WebView
[target.'cfg(not(target_os = "macos"))'.dependencies]
wry = "0.53.5"
wry = "0.54.1"
tao = { version = "0.34.5", features = ["serde"] }
image = { version = "0.25.9", default-features = false, features = ["ico"] }
@@ -61,7 +82,7 @@ windows = { version = "0.62.2", features = [
"Win32_System_Variant",
"Win32_Security",
"Win32_System_IO",
"Win32_System_Ioctl"
"Win32_System_Ioctl",
] }
win32job = "2.0.3"
wmi = "0.18"

View File

@@ -0,0 +1,400 @@
use std::env;
/// Applinks URL scheme registration result
#[derive(Debug, Clone)]
pub struct ApplinksRegistrationResult {
/// Whether registration was successful
pub success: bool,
/// Detailed message about the operation
pub message: String,
/// Whether the registry was modified (false if already configured correctly)
pub was_modified: bool,
}
/// Check if the URL scheme is already registered with the correct executable path
#[cfg(target_os = "windows")]
pub fn check_applinks_registration(scheme: String) -> anyhow::Result<ApplinksRegistrationResult> {
use windows::core::{HSTRING, PCWSTR};
use windows::Win32::System::Registry::{
RegCloseKey, RegOpenKeyExW, RegQueryValueExW, HKEY_CURRENT_USER, KEY_READ,
REG_VALUE_TYPE,
};
let app_path = env::current_exe()
.map_err(|e| anyhow::anyhow!("Failed to get current executable path: {}", e))?
.to_string_lossy()
.to_string();
let expected_command = format!("\"{}\" \"%1\"", app_path);
let protocol_key_path = format!("Software\\Classes\\{}", scheme);
let command_key_path = format!("{}\\shell\\open\\command", protocol_key_path);
unsafe {
// Check if URL Protocol value exists
let mut protocol_key = std::mem::zeroed();
let protocol_key_hstring = HSTRING::from(&protocol_key_path);
if RegOpenKeyExW(
HKEY_CURRENT_USER,
PCWSTR(protocol_key_hstring.as_ptr()),
Some(0),
KEY_READ,
&mut protocol_key,
)
.is_err()
{
return Ok(ApplinksRegistrationResult {
success: false,
message: format!("URL scheme '{}' is not registered", scheme),
was_modified: false,
});
}
// Check URL Protocol value
let url_protocol_name = HSTRING::from("URL Protocol");
let mut data_type: REG_VALUE_TYPE = REG_VALUE_TYPE::default();
let mut data_size: u32 = 0;
if RegQueryValueExW(
protocol_key,
PCWSTR(url_protocol_name.as_ptr()),
None,
Some(&mut data_type),
None,
Some(&mut data_size),
)
.is_err()
{
let _ = RegCloseKey(protocol_key);
return Ok(ApplinksRegistrationResult {
success: false,
message: format!("URL Protocol value not found for scheme '{}'", scheme),
was_modified: false,
});
}
let _ = RegCloseKey(protocol_key);
// Check command key
let mut command_key = std::mem::zeroed();
let command_key_hstring = HSTRING::from(&command_key_path);
if RegOpenKeyExW(
HKEY_CURRENT_USER,
PCWSTR(command_key_hstring.as_ptr()),
Some(0),
KEY_READ,
&mut command_key,
)
.is_err()
{
return Ok(ApplinksRegistrationResult {
success: false,
message: format!("Command key not found for scheme '{}'", scheme),
was_modified: false,
});
}
// Read command value (default value with empty name)
let empty_name = HSTRING::from("");
let mut data_size: u32 = 0;
if RegQueryValueExW(
command_key,
PCWSTR(empty_name.as_ptr()),
None,
Some(&mut data_type),
None,
Some(&mut data_size),
)
.is_err()
|| data_size == 0
{
let _ = RegCloseKey(command_key);
return Ok(ApplinksRegistrationResult {
success: false,
message: format!("Command value not found for scheme '{}'", scheme),
was_modified: false,
});
}
// Read the actual command value
let mut buffer: Vec<u8> = vec![0; data_size as usize];
if RegQueryValueExW(
command_key,
PCWSTR(empty_name.as_ptr()),
None,
Some(&mut data_type),
Some(buffer.as_mut_ptr()),
Some(&mut data_size),
)
.is_err()
{
let _ = RegCloseKey(command_key);
return Ok(ApplinksRegistrationResult {
success: false,
message: format!("Failed to read command value for scheme '{}'", scheme),
was_modified: false,
});
}
let _ = RegCloseKey(command_key);
// Convert buffer to string (UTF-16 to UTF-8)
let command_value = String::from_utf16_lossy(
&buffer
.chunks_exact(2)
.map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
.take_while(|&c| c != 0)
.collect::<Vec<u16>>(),
);
// Compare with expected command (case-insensitive for path)
if command_value.to_lowercase() == expected_command.to_lowercase() {
Ok(ApplinksRegistrationResult {
success: true,
message: format!("URL scheme '{}' is already registered correctly", scheme),
was_modified: false,
})
} else {
Ok(ApplinksRegistrationResult {
success: false,
message: format!(
"URL scheme '{}' is registered but with different path. Current: '{}', Expected: '{}'",
scheme, command_value, expected_command
),
was_modified: false,
})
}
}
}
#[cfg(not(target_os = "windows"))]
pub fn check_applinks_registration(scheme: String) -> anyhow::Result<ApplinksRegistrationResult> {
Ok(ApplinksRegistrationResult {
success: false,
message: format!(
"URL scheme registration check is not supported on this platform for scheme '{}'",
scheme
),
was_modified: false,
})
}
/// Register URL scheme in Windows registry
/// This will create or update the registry keys for the custom URL scheme
///
/// # Arguments
/// * `scheme` - The URL scheme to register (e.g., "sctoolbox")
/// * `app_name` - Optional application display name (e.g., "SCToolBox"). If provided,
/// the registry will show "URL:{app_name} Protocol" as the scheme description.
#[cfg(target_os = "windows")]
pub fn register_applinks(scheme: String, app_name: Option<String>) -> anyhow::Result<ApplinksRegistrationResult> {
use windows::core::{HSTRING, PCWSTR};
use windows::Win32::System::Registry::{
RegCloseKey, RegCreateKeyExW, RegSetValueExW, HKEY_CURRENT_USER, KEY_WRITE,
REG_CREATE_KEY_DISPOSITION, REG_OPTION_NON_VOLATILE, REG_SZ,
};
// First check if already registered correctly
let check_result = check_applinks_registration(scheme.clone())?;
if check_result.success {
return Ok(check_result);
}
let app_path = env::current_exe()
.map_err(|e| anyhow::anyhow!("Failed to get current executable path: {}", e))?
.to_string_lossy()
.to_string();
let command_value = format!("\"{}\" \"%1\"", app_path);
let protocol_key_path = format!("Software\\Classes\\{}", scheme);
let command_key_path = format!("{}\\shell\\open\\command", protocol_key_path);
unsafe {
// Create protocol key
let mut protocol_key = std::mem::zeroed();
let protocol_key_hstring = HSTRING::from(&protocol_key_path);
let mut disposition: REG_CREATE_KEY_DISPOSITION = REG_CREATE_KEY_DISPOSITION::default();
if RegCreateKeyExW(
HKEY_CURRENT_USER,
PCWSTR(protocol_key_hstring.as_ptr()),
Some(0),
PCWSTR::null(),
REG_OPTION_NON_VOLATILE,
KEY_WRITE,
None,
&mut protocol_key,
Some(&mut disposition),
)
.is_err()
{
return Err(anyhow::anyhow!(
"Failed to create registry key '{}'",
protocol_key_path
));
}
// Set default value (display name) if app_name is provided
if let Some(ref name) = app_name {
let display_name = format!("URL:{} Protocol", name);
let empty_name = HSTRING::from("");
let display_name_bytes: Vec<u8> = display_name
.encode_utf16()
.chain(std::iter::once(0))
.flat_map(|c| c.to_le_bytes())
.collect();
if RegSetValueExW(
protocol_key,
PCWSTR(empty_name.as_ptr()),
Some(0),
REG_SZ,
Some(&display_name_bytes),
)
.is_err()
{
let _ = RegCloseKey(protocol_key);
return Err(anyhow::anyhow!("Failed to set display name value"));
}
}
// Set URL Protocol value (empty string)
let url_protocol_name = HSTRING::from("URL Protocol");
let empty_value: [u8; 2] = [0, 0]; // Empty UTF-16 string
if RegSetValueExW(
protocol_key,
PCWSTR(url_protocol_name.as_ptr()),
Some(0),
REG_SZ,
Some(&empty_value),
)
.is_err()
{
let _ = RegCloseKey(protocol_key);
return Err(anyhow::anyhow!("Failed to set URL Protocol value"));
}
let _ = RegCloseKey(protocol_key);
// Create command key
let mut command_key = std::mem::zeroed();
let command_key_hstring = HSTRING::from(&command_key_path);
if RegCreateKeyExW(
HKEY_CURRENT_USER,
PCWSTR(command_key_hstring.as_ptr()),
Some(0),
PCWSTR::null(),
REG_OPTION_NON_VOLATILE,
KEY_WRITE,
None,
&mut command_key,
Some(&mut disposition),
)
.is_err()
{
return Err(anyhow::anyhow!(
"Failed to create command key '{}'",
command_key_path
));
}
// Set command value
let empty_name = HSTRING::from("");
let command_bytes: Vec<u8> = command_value
.encode_utf16()
.chain(std::iter::once(0))
.flat_map(|c| c.to_le_bytes())
.collect();
if RegSetValueExW(
command_key,
PCWSTR(empty_name.as_ptr()),
Some(0),
REG_SZ,
Some(&command_bytes),
)
.is_err()
{
let _ = RegCloseKey(command_key);
return Err(anyhow::anyhow!("Failed to set command value"));
}
let _ = RegCloseKey(command_key);
Ok(ApplinksRegistrationResult {
success: true,
message: format!(
"Successfully registered URL scheme '{}' with command '{}'",
scheme, command_value
),
was_modified: true,
})
}
}
#[cfg(not(target_os = "windows"))]
pub fn register_applinks(scheme: String, _app_name: Option<String>) -> anyhow::Result<ApplinksRegistrationResult> {
Ok(ApplinksRegistrationResult {
success: false,
message: format!(
"URL scheme registration is not supported on this platform for scheme '{}'",
scheme
),
was_modified: false,
})
}
/// Unregister URL scheme from Windows registry
#[cfg(target_os = "windows")]
pub fn unregister_applinks(scheme: String) -> anyhow::Result<ApplinksRegistrationResult> {
use windows::core::{HSTRING, PCWSTR};
use windows::Win32::System::Registry::{RegDeleteTreeW, HKEY_CURRENT_USER};
let protocol_key_path = format!("Software\\Classes\\{}", scheme);
unsafe {
let protocol_key_hstring = HSTRING::from(&protocol_key_path);
let result = RegDeleteTreeW(HKEY_CURRENT_USER, PCWSTR(protocol_key_hstring.as_ptr()));
if result.is_err() {
// Check if the key doesn't exist (not an error in this case)
let error_code = result.0 as u32;
if error_code == 2 {
// ERROR_FILE_NOT_FOUND
return Ok(ApplinksRegistrationResult {
success: true,
message: format!("URL scheme '{}' was not registered", scheme),
was_modified: false,
});
}
return Err(anyhow::anyhow!(
"Failed to delete registry key '{}': error code {}",
protocol_key_path,
error_code
));
}
Ok(ApplinksRegistrationResult {
success: true,
message: format!("Successfully unregistered URL scheme '{}'", scheme),
was_modified: true,
})
}
}
#[cfg(not(target_os = "windows"))]
pub fn unregister_applinks(scheme: String) -> anyhow::Result<ApplinksRegistrationResult> {
Ok(ApplinksRegistrationResult {
success: false,
message: format!(
"URL scheme unregistration is not supported on this platform for scheme '{}'",
scheme
),
was_modified: false,
})
}

View File

@@ -72,7 +72,12 @@ pub async fn get_rsi_launcher_asar_data(asar_path: &str) -> anyhow::Result<RsiLa
asar.files().iter().for_each(|v| {
let (path, file) = v;
let path_string = path.clone().into_os_string().into_string().unwrap();
if path_string.starts_with("app\\static\\js\\main.") && path_string.ends_with(".js") {
let path_start = if cfg!(target_os = "windows") {
"app\\static\\js\\main."
} else {
"app/static/js/main."
};
if path_string.starts_with(path_start) && path_string.ends_with(".js") {
main_js_path = path_string;
main_js_content = file.data().to_vec();
}

View File

@@ -143,6 +143,7 @@ pub async fn downloader_init(
disable_cooldown_secs: 600,
adaptive_increase_threshold: 5,
adaptive_decrease_threshold: 10,
user_agent: Some(String::from("SCToolBox/librqbit/1.0")),
..Default::default()
}),
..Default::default()

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