feat(scaffold): create product workspace + Rust crates + Flutter app
Implements the canonical implementation directory layout adopted by
DEC-022 (register v0.9.5). Closes the scaffolding phase; no PoC code
has been promoted in yet (per proof-of-concept-plan.md §4 a PoC is
not product code unless explicitly promoted).
Rust workspace
==============
Top-level Cargo.toml declares seven workspace members:
core/chanora_core top-level Rust API + orchestration
crates/chanora_protocol tsclientlib isolation (SAD-067, SysDes-011/029)
crates/chanora_state state sync, reducers, deltas
crates/chanora_audio capture, DSP, Opus, jitter, mixer, playback
crates/chanora_storage non-secret DB + platform secure store
crates/chanora_diagnostics logs, redaction, export
crates/chanora_bridge typed Flutter/Rust DTOs
Workspace-wide pins:
license = "MIT OR Apache-2.0" (DEC-020)
rust-version = "1.95"
edition = "2021"
The Flutter app (apps/chanora_flutter) is NOT a Cargo workspace
member; it is owned by the Flutter / Gradle toolchain and is in the
workspace exclude array along with every poc/* spike.
Each crate ships:
* a Cargo.toml referring to workspace.dependencies pins;
* a lib.rs with #![forbid(unsafe_code)] + #![warn(missing_docs)],
a typed Error enum, and the public types relevant to the
subsystem's role per SAD §7.2;
* minimal unit tests so
running 1 test
test tests::defaults_match_decisions ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
running 1 test
test tests::it_compiles ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
running 1 test
test tests::session_can_be_constructed ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
running 1 test
test tests::marker_matches_poc ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
running 1 test
test tests::it_compiles ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
running 1 test
test tests::state_transitions_compile ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
running 1 test
test tests::it_compiles ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s is non-empty.
chanora_core's CoreError type-wraps every subsystem error via
#[from] so callers can match on origin without parsing strings.
chanora_audio's AudioEffects struct defaults all four effects to
true, matching DEC-007 (AEC), DEC-008 (AGC), DEC-009 (NS),
DEC-010 (HPF). A unit test pins this so a future regression that
flips a default fails immediately.
chanora_diagnostics exports REDACTION_MARKER = "[REDACTED]",
identical to the PoC's marker so audit grep patterns survive the
promotion.
chanora_bridge's BridgeError is Serialize + Deserialize so it can
flow across the FRB 2.x boundary (DEC-014).
Empirical verification: cargo check --workspace clean, cargo test
--workspace clean (7 unit tests + 7 doc-test runners, all passing)
against Rust 1.95.0 stable.
Flutter app
===========
apps/chanora_flutter created with .
DEC-004 applied: minSdk overridden to 28 in
android/app/build.gradle.kts with a comment that points back at the
decision register and forbids lowering it without re-opening DEC-004.
DEC-015 applied: shipped English + Simplified Chinese at MVP.
- pubspec.yaml gains flutter_localizations + intl + generate:true.
- l10n.yaml emits lib/l10n/generated/AppL10n (no synthetic package
— that was removed in Flutter 3.41+).
- lib/l10n/app_en.arb is the source of truth; lib/l10n/app_zh.arb
mirrors the key set in zh-Hans. ARB metadata explicitly
reaffirms ADR-008: server-provided content (channel names,
nicknames, welcome banners) is preserved verbatim and never
translated.
lib/main.dart and test/widget_test.dart were rewritten from the
template counter into a minimal localized scaffold that proves
both locales render correctly.
Empirical verification: reports no issues;
runs the two locale smoke tests and both pass.
This commit is contained in:
Generated
+194
@@ -0,0 +1,194 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "chanora_audio"
|
||||
version = "0.0.1-pre"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chanora_bridge"
|
||||
version = "0.0.1-pre"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chanora_core"
|
||||
version = "0.0.1-pre"
|
||||
dependencies = [
|
||||
"chanora_audio",
|
||||
"chanora_bridge",
|
||||
"chanora_diagnostics",
|
||||
"chanora_protocol",
|
||||
"chanora_state",
|
||||
"chanora_storage",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chanora_diagnostics"
|
||||
version = "0.0.1-pre"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chanora_protocol"
|
||||
version = "0.0.1-pre"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chanora_state"
|
||||
version = "0.0.1-pre"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chanora_storage"
|
||||
version = "0.0.1-pre"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
# Chanora — top-level Cargo workspace.
|
||||
#
|
||||
# Layout authority: DEC-022 in
|
||||
# docs/governance/product-decision-register.md, which adopts the
|
||||
# README's sketch as canonical:
|
||||
#
|
||||
# apps/chanora_flutter/ — Flutter application (separate toolchain)
|
||||
# core/chanora_core/ — top-level Rust API + orchestration
|
||||
# crates/chanora_protocol/ — tsclientlib isolation
|
||||
# crates/chanora_state/ — snapshot, deltas, reducers
|
||||
# crates/chanora_audio/ — capture, DSP, Opus, jitter, mixer
|
||||
# crates/chanora_storage/ — bookmarks, settings, identity refs
|
||||
# crates/chanora_diagnostics/ — logs, redaction, export
|
||||
# crates/chanora_bridge/ — Flutter/Rust typed DTOs + glue
|
||||
#
|
||||
# The Flutter app is not part of this Cargo workspace; it is owned by
|
||||
# Gradle / Xcode / Flutter tooling under apps/chanora_flutter/.
|
||||
#
|
||||
# Non-promotion reminder (proof-of-concept-plan.md §4): nothing under
|
||||
# poc/ is product code. The crates here are blank scaffolds at this
|
||||
# stage; promotion of PoC code happens crate-by-crate with explicit
|
||||
# audit-trail commits.
|
||||
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"core/chanora_core",
|
||||
"crates/chanora_protocol",
|
||||
"crates/chanora_state",
|
||||
"crates/chanora_audio",
|
||||
"crates/chanora_storage",
|
||||
"crates/chanora_diagnostics",
|
||||
"crates/chanora_bridge",
|
||||
]
|
||||
|
||||
# Excludes: the PoC trees stay outside the product workspace so that
|
||||
# `cargo check --workspace` at the root does not pull in PoC builds.
|
||||
exclude = [
|
||||
"poc/tsclientlib-connect-spike",
|
||||
"poc/secure-storage-spike",
|
||||
"poc/sqlite-storage-spike",
|
||||
"poc/diagnostics-redaction-spike",
|
||||
"poc/audio-capture-playback-spike",
|
||||
"poc/audio-capture-playback-android-spike",
|
||||
"poc/flutter_rust_bridge_hello",
|
||||
"apps/chanora_flutter",
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.0.1-pre"
|
||||
edition = "2021"
|
||||
rust-version = "1.95"
|
||||
authors = ["The Chanora Project Contributors"]
|
||||
license = "MIT OR Apache-2.0" # DEC-020
|
||||
repository = "https://github.com/anomalyco/opencode"
|
||||
publish = false # not on crates.io
|
||||
readme = "README.md"
|
||||
|
||||
# Shared dependency pinnings. Members refer to these via
|
||||
# `package.dependencies.<x>.workspace = true` so versions stay aligned.
|
||||
[workspace.dependencies]
|
||||
thiserror = "2"
|
||||
tracing = "0.1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
@@ -0,0 +1,45 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.build/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
.swiftpm/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins-dependencies
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
/coverage/
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
@@ -0,0 +1,33 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "00b0c91f06209d9e4a41f71b7a512d6eb3b9c694"
|
||||
channel: "stable"
|
||||
|
||||
project_type: app
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694
|
||||
base_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694
|
||||
- platform: android
|
||||
create_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694
|
||||
base_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694
|
||||
- platform: linux
|
||||
create_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694
|
||||
base_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||
@@ -0,0 +1,17 @@
|
||||
# chanora_flutter
|
||||
|
||||
Chanora — cross-platform voice client for TeamSpeak-compatible servers.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a starting point for a Flutter application.
|
||||
|
||||
A few resources to get you started if this is your first Flutter project:
|
||||
|
||||
- [Learn Flutter](https://docs.flutter.dev/get-started/learn-flutter)
|
||||
- [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||
- [Flutter learning resources](https://docs.flutter.dev/reference/learning-resources)
|
||||
|
||||
For help getting started with Flutter development, view the
|
||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
@@ -0,0 +1,28 @@
|
||||
# This file configures the analyzer, which statically analyzes Dart code to
|
||||
# check for errors, warnings, and lints.
|
||||
#
|
||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||
# invoked from the command line by running `flutter analyze`.
|
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps,
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
# included above or to enable additional rules. A list of all available lints
|
||||
# and their documentation is published at https://dart.dev/lints.
|
||||
#
|
||||
# Instead of disabling a lint rule for the entire project in the
|
||||
# section below, it can also be suppressed for a single line of code
|
||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
rules:
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
@@ -0,0 +1,14 @@
|
||||
gradle-wrapper.jar
|
||||
/.gradle
|
||||
/captures/
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
||||
.cxx/
|
||||
|
||||
# Remember to never publicly share your keystore.
|
||||
# See https://flutter.dev/to/reference-keystore
|
||||
key.properties
|
||||
**/*.keystore
|
||||
**/*.jks
|
||||
@@ -0,0 +1,49 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("kotlin-android")
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "app.chanora.chanora_flutter"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_17.toString()
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "app.chanora.chanora_flutter"
|
||||
// DEC-004 (register v0.9.5): Android minimum API 28 (Android 9.0).
|
||||
// Raised from the original recommendation of API 24 by owner ruling
|
||||
// on 2026-05-14 for a simpler audio path (AAudio stable from API 28)
|
||||
// and a narrower compatibility / scoped-storage surface. Do NOT
|
||||
// lower without re-opening DEC-004.
|
||||
minSdk = 28
|
||||
// DEC-005: target the Google Play-required API level on upload date.
|
||||
// Flutter's default is kept; release-time CI must verify this still
|
||||
// satisfies the current Play policy.
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
@@ -0,0 +1,45 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application
|
||||
android:label="chanora_flutter"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility and
|
||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||
|
||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package app.chanora.chanora_flutter
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity : FlutterActivity()
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 544 B |
Binary file not shown.
|
After Width: | Height: | Size: 442 B |
Binary file not shown.
|
After Width: | Height: | Size: 721 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
@@ -0,0 +1,24 @@
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
val newBuildDir: Directory =
|
||||
rootProject.layout.buildDirectory
|
||||
.dir("../../build")
|
||||
.get()
|
||||
rootProject.layout.buildDirectory.value(newBuildDir)
|
||||
|
||||
subprojects {
|
||||
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
|
||||
project.layout.buildDirectory.value(newSubprojectBuildDir)
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
tasks.register<Delete>("clean") {
|
||||
delete(rootProject.layout.buildDirectory)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip
|
||||
@@ -0,0 +1,26 @@
|
||||
pluginManagement {
|
||||
val flutterSdkPath =
|
||||
run {
|
||||
val properties = java.util.Properties()
|
||||
file("local.properties").inputStream().use { properties.load(it) }
|
||||
val flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
|
||||
flutterSdkPath
|
||||
}
|
||||
|
||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||
id("com.android.application") version "8.11.1" apply false
|
||||
id("org.jetbrains.kotlin.android") version "2.2.20" apply false
|
||||
}
|
||||
|
||||
include(":app")
|
||||
@@ -0,0 +1,14 @@
|
||||
# Flutter l10n config — see https://docs.flutter.dev/ui/accessibility-and-internationalization/internationalization
|
||||
#
|
||||
# DEC-015 (register v0.9.5): Chanora ships English + Chinese
|
||||
# Simplified at MVP, i18n-ready architecture for further languages.
|
||||
# Server-provided content (channel names, nicknames, welcome banners)
|
||||
# is preserved verbatim per ADR-008 UTF-8 boundary rule and is NOT
|
||||
# translated.
|
||||
|
||||
arb-dir: lib/l10n
|
||||
template-arb-file: app_en.arb
|
||||
output-localization-file: app_localizations.dart
|
||||
output-class: AppL10n
|
||||
output-dir: lib/l10n/generated
|
||||
nullable-getter: false
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"@@locale": "en",
|
||||
"@@x-source-of-truth": "DEC-015 (register v0.9.5). Template ARB for English. Other locales must reference these keys. Server-provided content is NOT translated (ADR-008 / DEC-015).",
|
||||
|
||||
"appTitle": "Chanora",
|
||||
"@appTitle": {
|
||||
"description": "Application title shown in launchers and the app bar. The product name `Chanora` is fixed by DEC-018 and must not be translated."
|
||||
},
|
||||
|
||||
"homeGreeting": "Welcome to Chanora",
|
||||
"@homeGreeting": {
|
||||
"description": "Greeting shown on the empty home screen before any server is connected."
|
||||
},
|
||||
|
||||
"homeNotProductionReadyBanner": "Chanora is currently in early development. This build is not production-ready.",
|
||||
"@homeNotProductionReadyBanner": {
|
||||
"description": "Plain-language banner informing testers that this build is not for end-user use. Mirrors README.md's status line."
|
||||
},
|
||||
|
||||
"connectAction": "Connect to a server",
|
||||
"@connectAction": {
|
||||
"description": "Label for the primary action that opens the connect-to-server flow."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"@@locale": "zh",
|
||||
"@@x-source-of-truth": "DEC-015 (register v0.9.5). Simplified Chinese translations for the MVP key set. Keys must match app_en.arb; server-provided content is NOT translated (ADR-008 / DEC-015).",
|
||||
|
||||
"appTitle": "Chanora",
|
||||
"homeGreeting": "欢迎使用 Chanora",
|
||||
"homeNotProductionReadyBanner": "Chanora 目前处于早期开发阶段,此版本尚未达到正式发布质量。",
|
||||
"connectAction": "连接到服务器"
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
|
||||
import 'app_localizations_en.dart';
|
||||
import 'app_localizations_zh.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
/// Callers can lookup localized strings with an instance of AppL10n
|
||||
/// returned by `AppL10n.of(context)`.
|
||||
///
|
||||
/// Applications need to include `AppL10n.delegate()` in their app's
|
||||
/// `localizationDelegates` list, and the locales they support in the app's
|
||||
/// `supportedLocales` list. For example:
|
||||
///
|
||||
/// ```dart
|
||||
/// import 'generated/app_localizations.dart';
|
||||
///
|
||||
/// return MaterialApp(
|
||||
/// localizationsDelegates: AppL10n.localizationsDelegates,
|
||||
/// supportedLocales: AppL10n.supportedLocales,
|
||||
/// home: MyApplicationHome(),
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// ## Update pubspec.yaml
|
||||
///
|
||||
/// Please make sure to update your pubspec.yaml to include the following
|
||||
/// packages:
|
||||
///
|
||||
/// ```yaml
|
||||
/// dependencies:
|
||||
/// # Internationalization support.
|
||||
/// flutter_localizations:
|
||||
/// sdk: flutter
|
||||
/// intl: any # Use the pinned version from flutter_localizations
|
||||
///
|
||||
/// # Rest of dependencies
|
||||
/// ```
|
||||
///
|
||||
/// ## iOS Applications
|
||||
///
|
||||
/// iOS applications define key application metadata, including supported
|
||||
/// locales, in an Info.plist file that is built into the application bundle.
|
||||
/// To configure the locales supported by your app, you’ll need to edit this
|
||||
/// file.
|
||||
///
|
||||
/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file.
|
||||
/// Then, in the Project Navigator, open the Info.plist file under the Runner
|
||||
/// project’s Runner folder.
|
||||
///
|
||||
/// Next, select the Information Property List item, select Add Item from the
|
||||
/// Editor menu, then select Localizations from the pop-up menu.
|
||||
///
|
||||
/// Select and expand the newly-created Localizations item then, for each
|
||||
/// locale your application supports, add a new item and select the locale
|
||||
/// you wish to add from the pop-up menu in the Value field. This list should
|
||||
/// be consistent with the languages listed in the AppL10n.supportedLocales
|
||||
/// property.
|
||||
abstract class AppL10n {
|
||||
AppL10n(String locale)
|
||||
: localeName = intl.Intl.canonicalizedLocale(locale.toString());
|
||||
|
||||
final String localeName;
|
||||
|
||||
static AppL10n of(BuildContext context) {
|
||||
return Localizations.of<AppL10n>(context, AppL10n)!;
|
||||
}
|
||||
|
||||
static const LocalizationsDelegate<AppL10n> delegate = _AppL10nDelegate();
|
||||
|
||||
/// A list of this localizations delegate along with the default localizations
|
||||
/// delegates.
|
||||
///
|
||||
/// Returns a list of localizations delegates containing this delegate along with
|
||||
/// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate,
|
||||
/// and GlobalWidgetsLocalizations.delegate.
|
||||
///
|
||||
/// Additional delegates can be added by appending to this list in
|
||||
/// MaterialApp. This list does not have to be used at all if a custom list
|
||||
/// of delegates is preferred or required.
|
||||
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates =
|
||||
<LocalizationsDelegate<dynamic>>[
|
||||
delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
];
|
||||
|
||||
/// A list of this localizations delegate's supported locales.
|
||||
static const List<Locale> supportedLocales = <Locale>[
|
||||
Locale('en'),
|
||||
Locale('zh'),
|
||||
];
|
||||
|
||||
/// Application title shown in launchers and the app bar. The product name `Chanora` is fixed by DEC-018 and must not be translated.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Chanora'**
|
||||
String get appTitle;
|
||||
|
||||
/// Greeting shown on the empty home screen before any server is connected.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Welcome to Chanora'**
|
||||
String get homeGreeting;
|
||||
|
||||
/// Plain-language banner informing testers that this build is not for end-user use. Mirrors README.md's status line.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Chanora is currently in early development. This build is not production-ready.'**
|
||||
String get homeNotProductionReadyBanner;
|
||||
|
||||
/// Label for the primary action that opens the connect-to-server flow.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Connect to a server'**
|
||||
String get connectAction;
|
||||
}
|
||||
|
||||
class _AppL10nDelegate extends LocalizationsDelegate<AppL10n> {
|
||||
const _AppL10nDelegate();
|
||||
|
||||
@override
|
||||
Future<AppL10n> load(Locale locale) {
|
||||
return SynchronousFuture<AppL10n>(lookupAppL10n(locale));
|
||||
}
|
||||
|
||||
@override
|
||||
bool isSupported(Locale locale) =>
|
||||
<String>['en', 'zh'].contains(locale.languageCode);
|
||||
|
||||
@override
|
||||
bool shouldReload(_AppL10nDelegate old) => false;
|
||||
}
|
||||
|
||||
AppL10n lookupAppL10n(Locale locale) {
|
||||
// Lookup logic when only language code is specified.
|
||||
switch (locale.languageCode) {
|
||||
case 'en':
|
||||
return AppL10nEn();
|
||||
case 'zh':
|
||||
return AppL10nZh();
|
||||
}
|
||||
|
||||
throw FlutterError(
|
||||
'AppL10n.delegate failed to load unsupported locale "$locale". This is likely '
|
||||
'an issue with the localizations generation tool. Please file an issue '
|
||||
'on GitHub with a reproducible sample app and the gen-l10n configuration '
|
||||
'that was used.',
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// ignore: unused_import
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'app_localizations.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
/// The translations for English (`en`).
|
||||
class AppL10nEn extends AppL10n {
|
||||
AppL10nEn([String locale = 'en']) : super(locale);
|
||||
|
||||
@override
|
||||
String get appTitle => 'Chanora';
|
||||
|
||||
@override
|
||||
String get homeGreeting => 'Welcome to Chanora';
|
||||
|
||||
@override
|
||||
String get homeNotProductionReadyBanner =>
|
||||
'Chanora is currently in early development. This build is not production-ready.';
|
||||
|
||||
@override
|
||||
String get connectAction => 'Connect to a server';
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// ignore: unused_import
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'app_localizations.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
/// The translations for Chinese (`zh`).
|
||||
class AppL10nZh extends AppL10n {
|
||||
AppL10nZh([String locale = 'zh']) : super(locale);
|
||||
|
||||
@override
|
||||
String get appTitle => 'Chanora';
|
||||
|
||||
@override
|
||||
String get homeGreeting => '欢迎使用 Chanora';
|
||||
|
||||
@override
|
||||
String get homeNotProductionReadyBanner =>
|
||||
'Chanora 目前处于早期开发阶段,此版本尚未达到正式发布质量。';
|
||||
|
||||
@override
|
||||
String get connectAction => '连接到服务器';
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Chanora Flutter application entry point.
|
||||
//
|
||||
// Scaffold only. This file is the minimum viable wiring to demonstrate
|
||||
// the i18n architecture (DEC-015: English + Chinese Simplified) and to
|
||||
// give `flutter analyze` and `flutter build linux --debug` something
|
||||
// to compile. Real product UI lands later when the Chanora Design
|
||||
// System is implemented per docs/ui-ux/.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'l10n/generated/app_localizations.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const ChanoraApp());
|
||||
}
|
||||
|
||||
class ChanoraApp extends StatelessWidget {
|
||||
const ChanoraApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
onGenerateTitle: (ctx) => AppL10n.of(ctx).appTitle,
|
||||
theme: ThemeData(
|
||||
useMaterial3: true,
|
||||
colorSchemeSeed: const Color(0xFF3F51B5),
|
||||
),
|
||||
// DEC-015 (register v0.9.5): English + Chinese Simplified at MVP.
|
||||
// i18n-ready architecture; additional locales add an ARB file
|
||||
// under lib/l10n and a Locale entry here.
|
||||
localizationsDelegates: AppL10n.localizationsDelegates,
|
||||
supportedLocales: AppL10n.supportedLocales,
|
||||
home: const _HomeScreen(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _HomeScreen extends StatelessWidget {
|
||||
const _HomeScreen();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppL10n.of(context);
|
||||
final theme = Theme.of(context);
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(l10n.appTitle)),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(l10n.homeGreeting, style: theme.textTheme.headlineSmall),
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(l10n.homeNotProductionReadyBanner),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
FilledButton(
|
||||
onPressed: null, // wired in a later phase
|
||||
child: Text(l10n.connectAction),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
flutter/ephemeral
|
||||
@@ -0,0 +1,128 @@
|
||||
# Project-level configuration.
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
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 "chanora_flutter")
|
||||
# The unique GTK application identifier for this application. See:
|
||||
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
|
||||
set(APPLICATION_ID "app.chanora.chanora_flutter")
|
||||
|
||||
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
|
||||
# versions of CMake.
|
||||
cmake_policy(SET CMP0063 NEW)
|
||||
|
||||
# Load bundled libraries from the lib/ directory relative to the binary.
|
||||
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
|
||||
|
||||
# Root filesystem for cross-building.
|
||||
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
|
||||
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
|
||||
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
endif()
|
||||
|
||||
# Define build configuration options.
|
||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
set(CMAKE_BUILD_TYPE "Debug" CACHE
|
||||
STRING "Flutter build mode" FORCE)
|
||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
|
||||
"Debug" "Profile" "Release")
|
||||
endif()
|
||||
|
||||
# Compilation settings that should be applied to most targets.
|
||||
#
|
||||
# Be cautious about adding new options here, as plugins use this function by
|
||||
# default. In most cases, you should add new options to specific targets instead
|
||||
# of modifying this function.
|
||||
function(APPLY_STANDARD_SETTINGS TARGET)
|
||||
target_compile_features(${TARGET} PUBLIC cxx_std_14)
|
||||
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
|
||||
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
|
||||
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
|
||||
endfunction()
|
||||
|
||||
# Flutter library and tool build rules.
|
||||
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
|
||||
add_subdirectory(${FLUTTER_MANAGED_DIR})
|
||||
|
||||
# System-level dependencies.
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||
|
||||
# Application build; see runner/CMakeLists.txt.
|
||||
add_subdirectory("runner")
|
||||
|
||||
# Run the Flutter tool portions of the build. This must not be removed.
|
||||
add_dependencies(${BINARY_NAME} flutter_assemble)
|
||||
|
||||
# Only the install-generated bundle's copy of the executable will launch
|
||||
# correctly, since the resources must in the right relative locations. To avoid
|
||||
# people trying to run the unbundled copy, put it in a subdirectory instead of
|
||||
# the default top-level location.
|
||||
set_target_properties(${BINARY_NAME}
|
||||
PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
|
||||
)
|
||||
|
||||
|
||||
# Generated plugin build rules, which manage building the plugins and adding
|
||||
# them to the application.
|
||||
include(flutter/generated_plugins.cmake)
|
||||
|
||||
|
||||
# === Installation ===
|
||||
# By default, "installing" just makes a relocatable bundle in the build
|
||||
# directory.
|
||||
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
|
||||
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
|
||||
endif()
|
||||
|
||||
# Start with a clean build bundle directory every time.
|
||||
install(CODE "
|
||||
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
|
||||
" COMPONENT Runtime)
|
||||
|
||||
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
|
||||
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
|
||||
|
||||
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
|
||||
install(FILES "${bundled_library}"
|
||||
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
endforeach(bundled_library)
|
||||
|
||||
# Copy the native assets provided by the build.dart from all packages.
|
||||
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
|
||||
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
|
||||
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
# Fully re-copy the assets directory on each build to avoid having stale files
|
||||
# from a previous install.
|
||||
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
|
||||
install(CODE "
|
||||
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
|
||||
" COMPONENT Runtime)
|
||||
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
|
||||
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
|
||||
|
||||
# Install the AOT library on non-Debug builds only.
|
||||
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
|
||||
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
endif()
|
||||
@@ -0,0 +1,88 @@
|
||||
# This file controls Flutter-level build steps. It should not be edited.
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
|
||||
|
||||
# Configuration provided via flutter tool.
|
||||
include(${EPHEMERAL_DIR}/generated_config.cmake)
|
||||
|
||||
# TODO: Move the rest of this into files in ephemeral. See
|
||||
# https://github.com/flutter/flutter/issues/57146.
|
||||
|
||||
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
|
||||
# which isn't available in 3.10.
|
||||
function(list_prepend LIST_NAME PREFIX)
|
||||
set(NEW_LIST "")
|
||||
foreach(element ${${LIST_NAME}})
|
||||
list(APPEND NEW_LIST "${PREFIX}${element}")
|
||||
endforeach(element)
|
||||
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# === Flutter Library ===
|
||||
# System-level dependencies.
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
|
||||
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
|
||||
|
||||
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
|
||||
|
||||
# Published to parent scope for install step.
|
||||
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
|
||||
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
|
||||
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
|
||||
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
|
||||
|
||||
list(APPEND FLUTTER_LIBRARY_HEADERS
|
||||
"fl_basic_message_channel.h"
|
||||
"fl_binary_codec.h"
|
||||
"fl_binary_messenger.h"
|
||||
"fl_dart_project.h"
|
||||
"fl_engine.h"
|
||||
"fl_json_message_codec.h"
|
||||
"fl_json_method_codec.h"
|
||||
"fl_message_codec.h"
|
||||
"fl_method_call.h"
|
||||
"fl_method_channel.h"
|
||||
"fl_method_codec.h"
|
||||
"fl_method_response.h"
|
||||
"fl_plugin_registrar.h"
|
||||
"fl_plugin_registry.h"
|
||||
"fl_standard_message_codec.h"
|
||||
"fl_standard_method_codec.h"
|
||||
"fl_string_codec.h"
|
||||
"fl_value.h"
|
||||
"fl_view.h"
|
||||
"flutter_linux.h"
|
||||
)
|
||||
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
|
||||
add_library(flutter INTERFACE)
|
||||
target_include_directories(flutter INTERFACE
|
||||
"${EPHEMERAL_DIR}"
|
||||
)
|
||||
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
|
||||
target_link_libraries(flutter INTERFACE
|
||||
PkgConfig::GTK
|
||||
PkgConfig::GLIB
|
||||
PkgConfig::GIO
|
||||
)
|
||||
add_dependencies(flutter flutter_assemble)
|
||||
|
||||
# === Flutter tool backend ===
|
||||
# _phony_ is a non-existent file to force this command to run every time,
|
||||
# since currently there's no way to get a full input/output list from the
|
||||
# flutter tool.
|
||||
add_custom_command(
|
||||
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/_phony_
|
||||
COMMAND ${CMAKE_COMMAND} -E env
|
||||
${FLUTTER_TOOL_ENVIRONMENT}
|
||||
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
|
||||
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_target(flutter_assemble DEPENDS
|
||||
"${FLUTTER_LIBRARY}"
|
||||
${FLUTTER_LIBRARY_HEADERS}
|
||||
)
|
||||
@@ -0,0 +1,23 @@
|
||||
#
|
||||
# Generated file, do not edit.
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
||||
endforeach(plugin)
|
||||
|
||||
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
|
||||
endforeach(ffi_plugin)
|
||||
@@ -0,0 +1,26 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(runner LANGUAGES CXX)
|
||||
|
||||
# Define the application target. To change its name, change BINARY_NAME in the
|
||||
# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
|
||||
# work.
|
||||
#
|
||||
# Any new source files that you add to the application should be added here.
|
||||
add_executable(${BINARY_NAME}
|
||||
"main.cc"
|
||||
"my_application.cc"
|
||||
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
|
||||
)
|
||||
|
||||
# Apply the standard set of build settings. This can be removed for applications
|
||||
# that need different build settings.
|
||||
apply_standard_settings(${BINARY_NAME})
|
||||
|
||||
# Add preprocessor definitions for the application ID.
|
||||
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
|
||||
|
||||
# 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_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
|
||||
@@ -0,0 +1,6 @@
|
||||
#include "my_application.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
g_autoptr(MyApplication) app = my_application_new();
|
||||
return g_application_run(G_APPLICATION(app), argc, argv);
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
#include "my_application.h"
|
||||
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
#include <gdk/gdkx.h>
|
||||
#endif
|
||||
|
||||
#include "flutter/generated_plugin_registrant.h"
|
||||
|
||||
struct _MyApplication {
|
||||
GtkApplication parent_instance;
|
||||
char** dart_entrypoint_arguments;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
|
||||
|
||||
// Called when first Flutter frame received.
|
||||
static void first_frame_cb(MyApplication* self, FlView* view) {
|
||||
gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view)));
|
||||
}
|
||||
|
||||
// Implements GApplication::activate.
|
||||
static void my_application_activate(GApplication* application) {
|
||||
MyApplication* self = MY_APPLICATION(application);
|
||||
GtkWindow* window =
|
||||
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
|
||||
|
||||
// Use a header bar when running in GNOME as this is the common style used
|
||||
// by applications and is the setup most users will be using (e.g. Ubuntu
|
||||
// desktop).
|
||||
// If running on X and not using GNOME then just use a traditional title bar
|
||||
// in case the window manager does more exotic layout, e.g. tiling.
|
||||
// If running on Wayland assume the header bar will work (may need changing
|
||||
// if future cases occur).
|
||||
gboolean use_header_bar = TRUE;
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
GdkScreen* screen = gtk_window_get_screen(window);
|
||||
if (GDK_IS_X11_SCREEN(screen)) {
|
||||
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
|
||||
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
|
||||
use_header_bar = FALSE;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
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, "chanora_flutter");
|
||||
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, "chanora_flutter");
|
||||
}
|
||||
|
||||
gtk_window_set_default_size(window, 1280, 720);
|
||||
|
||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||
fl_dart_project_set_dart_entrypoint_arguments(
|
||||
project, self->dart_entrypoint_arguments);
|
||||
|
||||
FlView* view = fl_view_new(project);
|
||||
GdkRGBA background_color;
|
||||
// Background defaults to black, override it here if necessary, e.g. #00000000
|
||||
// for transparent.
|
||||
gdk_rgba_parse(&background_color, "#000000");
|
||||
fl_view_set_background_color(view, &background_color);
|
||||
gtk_widget_show(GTK_WIDGET(view));
|
||||
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
|
||||
|
||||
// Show the window when Flutter renders.
|
||||
// Requires the view to be realized so we can start rendering.
|
||||
g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb),
|
||||
self);
|
||||
gtk_widget_realize(GTK_WIDGET(view));
|
||||
|
||||
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
|
||||
|
||||
gtk_widget_grab_focus(GTK_WIDGET(view));
|
||||
}
|
||||
|
||||
// Implements GApplication::local_command_line.
|
||||
static gboolean my_application_local_command_line(GApplication* application,
|
||||
gchar*** arguments,
|
||||
int* exit_status) {
|
||||
MyApplication* self = MY_APPLICATION(application);
|
||||
// Strip out the first argument as it is the binary name.
|
||||
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
|
||||
|
||||
g_autoptr(GError) error = nullptr;
|
||||
if (!g_application_register(application, nullptr, &error)) {
|
||||
g_warning("Failed to register: %s", error->message);
|
||||
*exit_status = 1;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
g_application_activate(application);
|
||||
*exit_status = 0;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Implements GApplication::startup.
|
||||
static void my_application_startup(GApplication* application) {
|
||||
// MyApplication* self = MY_APPLICATION(object);
|
||||
|
||||
// Perform any actions required at application startup.
|
||||
|
||||
G_APPLICATION_CLASS(my_application_parent_class)->startup(application);
|
||||
}
|
||||
|
||||
// Implements GApplication::shutdown.
|
||||
static void my_application_shutdown(GApplication* application) {
|
||||
// MyApplication* self = MY_APPLICATION(object);
|
||||
|
||||
// Perform any actions required at application shutdown.
|
||||
|
||||
G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application);
|
||||
}
|
||||
|
||||
// Implements GObject::dispose.
|
||||
static void my_application_dispose(GObject* object) {
|
||||
MyApplication* self = MY_APPLICATION(object);
|
||||
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
|
||||
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
|
||||
}
|
||||
|
||||
static void my_application_class_init(MyApplicationClass* klass) {
|
||||
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
|
||||
G_APPLICATION_CLASS(klass)->local_command_line =
|
||||
my_application_local_command_line;
|
||||
G_APPLICATION_CLASS(klass)->startup = my_application_startup;
|
||||
G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
|
||||
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
|
||||
}
|
||||
|
||||
static void my_application_init(MyApplication* self) {}
|
||||
|
||||
MyApplication* my_application_new() {
|
||||
// Set the program name to the application ID, which helps various systems
|
||||
// like GTK and desktop environments map this running application to its
|
||||
// corresponding .desktop file. This ensures better integration by allowing
|
||||
// the application to be recognized beyond its binary name.
|
||||
g_set_prgname(APPLICATION_ID);
|
||||
|
||||
return MY_APPLICATION(g_object_new(my_application_get_type(),
|
||||
"application-id", APPLICATION_ID, "flags",
|
||||
G_APPLICATION_NON_UNIQUE, nullptr));
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef FLUTTER_MY_APPLICATION_H_
|
||||
#define FLUTTER_MY_APPLICATION_H_
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
G_DECLARE_FINAL_TYPE(MyApplication,
|
||||
my_application,
|
||||
MY,
|
||||
APPLICATION,
|
||||
GtkApplication)
|
||||
|
||||
/**
|
||||
* my_application_new:
|
||||
*
|
||||
* Creates a new Flutter-based application.
|
||||
*
|
||||
* Returns: a new #MyApplication.
|
||||
*/
|
||||
MyApplication* my_application_new();
|
||||
|
||||
#endif // FLUTTER_MY_APPLICATION_H_
|
||||
@@ -0,0 +1,226 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.1"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cupertino_icons
|
||||
sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.9"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.3"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
flutter_localizations:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: intl
|
||||
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.20.2"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.2"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.10"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.19"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.2"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.10"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.2.0"
|
||||
sdks:
|
||||
dart: ">=3.11.5 <4.0.0"
|
||||
flutter: ">=3.18.0-18.0.pre.54"
|
||||
@@ -0,0 +1,98 @@
|
||||
name: chanora_flutter
|
||||
description: "Chanora — cross-platform voice client for TeamSpeak-compatible servers."
|
||||
# The following line prevents the package from being accidentally published to
|
||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
|
||||
# The following defines the version and build number for your application.
|
||||
# A version number is three numbers separated by dots, like 1.2.43
|
||||
# followed by an optional build number separated by a +.
|
||||
# Both the version and the builder number may be overridden in flutter
|
||||
# build by specifying --build-name and --build-number, respectively.
|
||||
# In Android, build-name is used as versionName while build-number used as versionCode.
|
||||
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
|
||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 1.0.0+1
|
||||
|
||||
environment:
|
||||
sdk: ^3.11.5
|
||||
|
||||
# Dependencies specify other packages that your package needs in order to work.
|
||||
# To automatically upgrade your package dependencies to the latest versions
|
||||
# consider running `flutter pub upgrade --major-versions`. Alternatively,
|
||||
# dependencies can be manually updated by changing the version numbers below to
|
||||
# the latest version available on pub.dev. To see which dependencies have newer
|
||||
# versions available, run `flutter pub outdated`.
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
intl: any
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.8
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
# The "flutter_lints" package below contains a set of recommended lints to
|
||||
# encourage good coding practices. The lint set provided by the package is
|
||||
# activated in the `analysis_options.yaml` file located at the root of your
|
||||
# package. See that file for information about deactivating specific lint
|
||||
# rules and activating additional ones.
|
||||
flutter_lints: ^6.0.0
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
|
||||
# Generate localization bindings from lib/l10n/*.arb at build time.
|
||||
# Per DEC-015 (register v0.9.5) Chanora ships English + Chinese
|
||||
# Simplified at MVP with an i18n-ready architecture for additional
|
||||
# languages.
|
||||
generate: true
|
||||
|
||||
# The following line ensures that the Material Icons font is
|
||||
# included with your application, so that you can use the icons in
|
||||
# the material Icons class.
|
||||
uses-material-design: true
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/to/resolution-aware-images
|
||||
|
||||
# For details regarding adding assets from package dependencies, see
|
||||
# https://flutter.dev/to/asset-from-package
|
||||
|
||||
# To add custom fonts to your application, add a fonts section here,
|
||||
# in this "flutter" section. Each entry in this list should have a
|
||||
# "family" key with the font family name, and a "fonts" key with a
|
||||
# list giving the asset and other descriptors for the font. For
|
||||
# example:
|
||||
# fonts:
|
||||
# - family: Schyler
|
||||
# fonts:
|
||||
# - asset: fonts/Schyler-Regular.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
# style: italic
|
||||
# - family: Trajan Pro
|
||||
# fonts:
|
||||
# - asset: fonts/TrajanPro.ttf
|
||||
# - asset: fonts/TrajanPro_Bold.ttf
|
||||
# weight: 700
|
||||
#
|
||||
# For details regarding fonts from package dependencies,
|
||||
# see https://flutter.dev/to/font-from-package
|
||||
@@ -0,0 +1,33 @@
|
||||
// Smoke test for the scaffold app. Replaces the default counter test
|
||||
// from `flutter create`. Verifies the app builds and the localized
|
||||
// greeting is rendered.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:chanora_flutter/l10n/generated/app_localizations.dart';
|
||||
import 'package:chanora_flutter/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('renders English greeting by default', (tester) async {
|
||||
await tester.pumpWidget(const ChanoraApp());
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Welcome to Chanora'), findsOneWidget);
|
||||
expect(find.text('Chanora'), findsWidgets);
|
||||
});
|
||||
|
||||
testWidgets('renders Simplified Chinese greeting when locale is zh',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
locale: const Locale('zh'),
|
||||
localizationsDelegates: AppL10n.localizationsDelegates,
|
||||
supportedLocales: AppL10n.supportedLocales,
|
||||
home: Builder(builder: (ctx) {
|
||||
final l10n = AppL10n.of(ctx);
|
||||
return Scaffold(body: Text(l10n.homeGreeting));
|
||||
}),
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('欢迎使用 Chanora'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "chanora_core"
|
||||
description = "Chanora — top-level Rust API and orchestration. Hosts the connection manager, command dispatcher, and the integration of the protocol, state, audio, storage, and diagnostics subsystems."
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[dependencies]
|
||||
chanora_protocol = { path = "../../crates/chanora_protocol" }
|
||||
chanora_state = { path = "../../crates/chanora_state" }
|
||||
chanora_audio = { path = "../../crates/chanora_audio" }
|
||||
chanora_storage = { path = "../../crates/chanora_storage" }
|
||||
chanora_diagnostics = { path = "../../crates/chanora_diagnostics" }
|
||||
chanora_bridge = { path = "../../crates/chanora_bridge" }
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
@@ -0,0 +1,96 @@
|
||||
//! # `chanora_core`
|
||||
//!
|
||||
//! Top-level Rust API and orchestration for the Chanora client.
|
||||
//!
|
||||
//! ## Role per SAD §7.2
|
||||
//!
|
||||
//! `chanora_core` is the integration point. It owns no protocol,
|
||||
//! audio, or storage logic directly; instead it composes the
|
||||
//! subsystem crates ([`chanora_protocol`], [`chanora_state`],
|
||||
//! [`chanora_audio`], [`chanora_storage`], [`chanora_diagnostics`])
|
||||
//! behind a stable, typed API consumed by [`chanora_bridge`] (which
|
||||
//! in turn exposes it to Flutter via `flutter_rust_bridge`, per
|
||||
//! DEC-014).
|
||||
//!
|
||||
//! ## Invariants
|
||||
//!
|
||||
//! * Single active server connection at runtime (DEC-006 / SAD-064).
|
||||
//! * Protocol-specific types from `tsclientlib` do not cross out of
|
||||
//! [`chanora_protocol`] (SAD-067).
|
||||
//! * Secret material never lands in [`chanora_storage`]; secrets
|
||||
//! live in [`chanora_diagnostics`]'s `KnownSecretRegistry` *only*
|
||||
//! for redaction and in the platform secure-store (DEC-013.2).
|
||||
//!
|
||||
//! ## Status
|
||||
//!
|
||||
//! This crate is a **scaffold**. No PoC code has been promoted in
|
||||
//! yet. The public surface below is the integration contract; bodies
|
||||
//! are placeholders.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors that can arise during top-level orchestration.
|
||||
///
|
||||
/// Each arm wraps a typed error from the subsystem that produced it,
|
||||
/// so callers can match on the originating layer without parsing
|
||||
/// strings. The variants are intentionally narrow at this stage and
|
||||
/// will expand as the subsystems land.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CoreError {
|
||||
/// Protocol-layer error originating from [`chanora_protocol`].
|
||||
#[error("protocol: {0}")]
|
||||
Protocol(#[from] chanora_protocol::ProtocolError),
|
||||
/// State-synchronisation error from [`chanora_state`].
|
||||
#[error("state: {0}")]
|
||||
State(#[from] chanora_state::StateError),
|
||||
/// Audio-subsystem error from [`chanora_audio`].
|
||||
#[error("audio: {0}")]
|
||||
Audio(#[from] chanora_audio::AudioError),
|
||||
/// Storage error from [`chanora_storage`].
|
||||
#[error("storage: {0}")]
|
||||
Storage(#[from] chanora_storage::StorageError),
|
||||
/// Diagnostics error from [`chanora_diagnostics`].
|
||||
#[error("diagnostics: {0}")]
|
||||
Diagnostics(#[from] chanora_diagnostics::DiagnosticsError),
|
||||
/// Bridge / DTO error from [`chanora_bridge`].
|
||||
#[error("bridge: {0}")]
|
||||
Bridge(#[from] chanora_bridge::BridgeError),
|
||||
/// A precondition was violated (typically caller bug or
|
||||
/// concurrent misuse).
|
||||
#[error("invariant violated: {0}")]
|
||||
Invariant(&'static str),
|
||||
}
|
||||
|
||||
/// The top-level Chanora session. Owns exactly one active server
|
||||
/// connection (DEC-006). Construction does **not** dial the server;
|
||||
/// see [`ChanoraSession::connect`] (scaffolded only).
|
||||
pub struct ChanoraSession {
|
||||
_seal: (),
|
||||
}
|
||||
|
||||
impl ChanoraSession {
|
||||
/// Construct a new session with the default subsystem
|
||||
/// configurations. Performs no I/O.
|
||||
pub fn new() -> Self {
|
||||
Self { _seal: () }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ChanoraSession {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn session_can_be_constructed() {
|
||||
let _ = ChanoraSession::new();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "chanora_audio"
|
||||
description = "Chanora audio subsystem — capture, DSP (HPF/NS/AEC/AGC per DEC-007..010), Opus encode/decode, jitter buffer, mixer, playback. Crate selection: cpal for desktop and Android per DEC-011.1."
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[dependencies]
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
@@ -0,0 +1,83 @@
|
||||
//! # `chanora_audio`
|
||||
//!
|
||||
//! Audio subsystem. Per SAD §7.2:
|
||||
//!
|
||||
//! * capture from the platform default input device
|
||||
//! * DSP chain: high-pass filter → noise suppression → echo
|
||||
//! cancellation → automatic gain control → gate
|
||||
//! (per-effect defaults: DEC-007..010)
|
||||
//! * Opus encode/decode
|
||||
//! * jitter buffer, mixer
|
||||
//! * playback to the platform default output device
|
||||
//!
|
||||
//! Crate selection per DEC-011.1: `cpal` on desktop and Android.
|
||||
//! iOS audio crate remains deferred.
|
||||
//!
|
||||
//! Empirical evidence from PoC:
|
||||
//! `poc/audio-capture-playback-spike` (Linux/PipeWire) +
|
||||
//! `poc/audio-capture-playback-android-spike` (Android 14 arm64-v8a,
|
||||
//! physical device).
|
||||
//!
|
||||
//! ## Status
|
||||
//!
|
||||
//! Scaffold only. PoC code is not promoted here yet.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors raised by the audio subsystem.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum AudioError {
|
||||
/// Platform did not expose a usable default input device.
|
||||
#[error("no default input device")]
|
||||
NoInputDevice,
|
||||
/// Platform did not expose a usable default output device.
|
||||
#[error("no default output device")]
|
||||
NoOutputDevice,
|
||||
/// The audio backend rejected a stream configuration.
|
||||
#[error("stream config rejected: {0}")]
|
||||
StreamConfig(String),
|
||||
/// A backend-specific failure surfaced without a typed mapping.
|
||||
/// Production code must narrow this further as failure modes
|
||||
/// are catalogued.
|
||||
#[error("audio backend: {0}")]
|
||||
Backend(String),
|
||||
}
|
||||
|
||||
/// Audio-effect toggles. Defaults match DEC-007 (AEC),
|
||||
/// DEC-008 (AGC), DEC-009 (NS), DEC-010 (HPF) — all enabled.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct AudioEffects {
|
||||
/// Acoustic echo cancellation (DEC-007).
|
||||
pub aec: bool,
|
||||
/// Automatic gain control (DEC-008).
|
||||
pub agc: bool,
|
||||
/// Noise suppression (DEC-009).
|
||||
pub noise_suppression: bool,
|
||||
/// High-pass filter (DEC-010).
|
||||
pub high_pass: bool,
|
||||
}
|
||||
|
||||
impl Default for AudioEffects {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
aec: true,
|
||||
agc: true,
|
||||
noise_suppression: true,
|
||||
high_pass: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn defaults_match_decisions() {
|
||||
let e = AudioEffects::default();
|
||||
assert!(e.aec && e.agc && e.noise_suppression && e.high_pass);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "chanora_bridge"
|
||||
description = "Chanora typed Flutter/Rust bridge — schema-controlled DTOs for commands, results, and events. Backed by flutter_rust_bridge 2.x per DEC-014."
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[dependencies]
|
||||
thiserror.workspace = true
|
||||
serde.workspace = true
|
||||
tracing.workspace = true
|
||||
@@ -0,0 +1,42 @@
|
||||
//! # `chanora_bridge`
|
||||
//!
|
||||
//! Typed Flutter/Rust bridge. Owns the canonical DTO catalogue for
|
||||
//! commands (Dart → Rust), results (Rust → Dart return values), and
|
||||
//! events (Rust → Dart streams).
|
||||
//!
|
||||
//! Bridge tool per DEC-014: `flutter_rust_bridge` 2.x. The bridge
|
||||
//! integration glue (codegen invocations, the cdylib boundary) is
|
||||
//! added when `apps/chanora_flutter` is wired up; this crate owns
|
||||
//! only the type definitions and error mappings — explicitly *not*
|
||||
//! `tsclientlib` or raw subsystem types (SAD-067, SDD-079).
|
||||
//!
|
||||
//! ## Status
|
||||
//!
|
||||
//! Scaffold only.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors raised at the bridge boundary. Production code must keep
|
||||
/// `BridgeError` user-safe — no secrets, no protocol details, no
|
||||
/// path information beyond what the redaction policy permits.
|
||||
#[derive(Debug, Error, Clone, Serialize, Deserialize)]
|
||||
pub enum BridgeError {
|
||||
/// The caller submitted a malformed command DTO.
|
||||
#[error("invalid command: {0}")]
|
||||
InvalidCommand(String),
|
||||
/// An unknown / unmapped error escaped the subsystem boundary.
|
||||
/// Production callers should never see this; if it appears it is
|
||||
/// a mapping bug in this crate.
|
||||
#[error("unmapped: {0}")]
|
||||
Unmapped(String),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_compiles() {}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "chanora_diagnostics"
|
||||
description = "Chanora diagnostics — logs, redaction policy, user-initiated diagnostic export. Implements REDACT-TC-001..010 from diagnostic-redaction-audit-report.md."
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[dependencies]
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
@@ -0,0 +1,61 @@
|
||||
//! # `chanora_diagnostics`
|
||||
//!
|
||||
//! Application diagnostics. Owns:
|
||||
//!
|
||||
//! * the redaction policy and [`Redactor`] (REDACT-TC-001..010 per
|
||||
//! `docs/security/diagnostic-redaction-audit-report.md` §4)
|
||||
//! * the diagnostic-export bundle (audit report §5)
|
||||
//! * the `KnownSecretRegistry` cross-spike contract (SS-AUD-003
|
||||
//! defence in depth)
|
||||
//! * the `tracing-subscriber` layer integration that enforces
|
||||
//! redaction at write-time (REDACT-FIND-002)
|
||||
//!
|
||||
//! Per DEC-016 diagnostics export is **user-initiated only**; there
|
||||
//! is no automatic upload.
|
||||
//!
|
||||
//! ## Status
|
||||
//!
|
||||
//! Scaffold only.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors raised by the diagnostics subsystem.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum DiagnosticsError {
|
||||
/// Failed to build or serialise a diagnostic export.
|
||||
#[error("export failed: {0}")]
|
||||
Export(String),
|
||||
/// I/O error while writing logs or exports.
|
||||
#[error("io: {0}")]
|
||||
Io(String),
|
||||
}
|
||||
|
||||
/// The replacement marker used for redacted segments, identical to
|
||||
/// the PoC value so audit grep patterns survive the promotion.
|
||||
pub const REDACTION_MARKER: &str = "[REDACTED]";
|
||||
|
||||
/// Placeholder for the redactor that will be promoted from
|
||||
/// `poc/diagnostics-redaction-spike`.
|
||||
pub struct Redactor {
|
||||
_seal: (),
|
||||
}
|
||||
|
||||
impl Redactor {
|
||||
/// Construct a redactor with the default policy.
|
||||
pub fn with_default_policy() -> Self {
|
||||
Self { _seal: () }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn marker_matches_poc() {
|
||||
assert_eq!(REDACTION_MARKER, "[REDACTED]");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "chanora_protocol"
|
||||
description = "Chanora protocol adapter — isolates tsclientlib behind a typed boundary. No tsclientlib types may cross out of this crate per SAD-067 / SysDes-011 / SysDes-029."
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[dependencies]
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
@@ -0,0 +1,51 @@
|
||||
//! # `chanora_protocol`
|
||||
//!
|
||||
//! TeamSpeak-compatible protocol adapter. Isolates `tsclientlib`
|
||||
//! behind a typed boundary so the rest of Chanora is decoupled from
|
||||
//! the upstream library's types (SAD-067, SysDes-011, SysDes-029).
|
||||
//!
|
||||
//! ## Status
|
||||
//!
|
||||
//! Scaffold only. Promotion of `poc/tsclientlib-connect-spike` into
|
||||
//! this crate happens later, with its own audit-trail commit.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors surfaced by the protocol adapter. None of these expose
|
||||
/// `tsclientlib`-specific types; raw upstream errors are mapped here.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ProtocolError {
|
||||
/// Configuration is invalid before any I/O is attempted (bad
|
||||
/// hostname, missing identity, etc.).
|
||||
#[error("invalid protocol configuration: {0}")]
|
||||
Invalid(&'static str),
|
||||
/// The connect handshake failed.
|
||||
#[error("connect failed: {0}")]
|
||||
Connect(String),
|
||||
/// The connection ended unexpectedly.
|
||||
#[error("disconnected: {0}")]
|
||||
Disconnected(String),
|
||||
/// Operation timed out.
|
||||
#[error("protocol timeout")]
|
||||
Timeout,
|
||||
}
|
||||
|
||||
/// Opaque identifier for a server-side channel. Replaces
|
||||
/// `tsclientlib::ChannelId` at the public boundary so its concrete
|
||||
/// representation can change without leaking through the rest of the
|
||||
/// codebase.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct ChannelId(pub u64);
|
||||
|
||||
/// Opaque identifier for a server-side client.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct ClientId(pub u64);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_compiles() {}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "chanora_state"
|
||||
description = "Chanora state synchronisation — authoritative client-side state mirror, reducers that fold protocol events, and deltas the bridge publishes to Flutter view-models."
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[dependencies]
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
@@ -0,0 +1,52 @@
|
||||
//! # `chanora_state`
|
||||
//!
|
||||
//! Authoritative client-side mirror of server state: server identity,
|
||||
//! channel tree, client list, our own connection state. Owns the
|
||||
//! reducers that fold protocol events into a snapshot, and the
|
||||
//! deltas that the bridge publishes to Flutter view-models
|
||||
//! (per SAD §7.2 and SDD §5).
|
||||
//!
|
||||
//! ## Status
|
||||
//!
|
||||
//! Scaffold only.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors raised while reducing protocol events into state or
|
||||
/// producing deltas for the bridge.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum StateError {
|
||||
/// Reducer received an event referencing an unknown entity.
|
||||
#[error("unknown entity: {0}")]
|
||||
Unknown(&'static str),
|
||||
/// A reducer invariant was violated (e.g. two clients claiming
|
||||
/// the same id).
|
||||
#[error("state invariant: {0}")]
|
||||
Invariant(&'static str),
|
||||
}
|
||||
|
||||
/// Coarse connection lifecycle states surfaced to the bridge.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ConnectionState {
|
||||
/// No connection attempt yet, or fully disconnected.
|
||||
Idle,
|
||||
/// Handshake in progress.
|
||||
Connecting,
|
||||
/// Initial state snapshot has been received; ready for use.
|
||||
Ready,
|
||||
/// Connection lost; will not auto-reconnect at this stage.
|
||||
Lost,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn state_transitions_compile() {
|
||||
let _ = ConnectionState::Idle;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "chanora_storage"
|
||||
description = "Chanora storage — non-secret embedded DB (SQLite via rusqlite bundled per DEC-013.1) and platform secure storage (Secret Service preferred + keyutils fallback on Linux per DEC-013.2). Strict SAD-067 separation."
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[dependencies]
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
@@ -0,0 +1,58 @@
|
||||
//! # `chanora_storage`
|
||||
//!
|
||||
//! Two strictly separated repositories per SAD-067:
|
||||
//!
|
||||
//! * [`LocalDatabaseRepository`] — non-secret state (bookmarks,
|
||||
//! settings, identity *references*) via SQLite. Crate choice:
|
||||
//! `rusqlite` bundled (DEC-013.1).
|
||||
//! * [`SecretStorageRepository`] — secret material (identity private
|
||||
//! keys, server passwords) via platform secure storage. Linux
|
||||
//! policy: Secret Service preferred, kernel keyutils fallback
|
||||
//! (DEC-013.2). Other platforms TBD per SS-TC-001/002/004/005.
|
||||
//!
|
||||
//! Secret values **never** appear in the local DB (SS-AUD-001/002);
|
||||
//! bookmarks store only an `identity_ref` lookup name into the
|
||||
//! secure store.
|
||||
//!
|
||||
//! ## Status
|
||||
//!
|
||||
//! Scaffold only. `poc/secure-storage-spike` and
|
||||
//! `poc/sqlite-storage-spike` will be promoted here.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors raised by either storage repository.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum StorageError {
|
||||
/// Looked-up entry does not exist.
|
||||
#[error("not found")]
|
||||
NotFound,
|
||||
/// Schema migration error.
|
||||
#[error("migration: {0}")]
|
||||
Migration(String),
|
||||
/// Underlying SQLite error.
|
||||
#[error("sqlite: {0}")]
|
||||
Sqlite(String),
|
||||
/// Platform secure-storage backend rejected an operation.
|
||||
#[error("secure-store: {0}")]
|
||||
SecureStore(String),
|
||||
}
|
||||
|
||||
/// Marker trait for the non-secret database side. Concrete impl will
|
||||
/// land alongside the `BookmarkRepository` / `SettingsRepository`
|
||||
/// traits promoted from the SQLite PoC.
|
||||
pub trait LocalDatabaseRepository: Send + Sync {}
|
||||
|
||||
/// Marker trait for the platform secure-storage side. Concrete impl
|
||||
/// will land alongside the `Secret` newtype + per-platform adapters
|
||||
/// promoted from the secure-storage PoC.
|
||||
pub trait SecretStorageRepository: Send + Sync {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_compiles() {}
|
||||
}
|
||||
Reference in New Issue
Block a user