feat(shell): первый Slint-кадр на SDK (срезы C03/04/05/07/02) + slint GPL exception
theme::resolve_night (TDD); slint! AppWindow (статус-бар часы+сеть + грид тайлов + тема день/ночь); main — best-effort чтение Settings/Power через sdk (без шины — дефолты, #4); часы UTC (локаль tz — позже). deny.toml: GPL-3.0 exceptions для slint-крейтов (вариант A, финал к v4) + BSL-1.0 (error-code). Slint тянет zbus5/thiserror2 — дубли версий (bans=warn). Реальный screenshot кадра — План 5 E2E. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Alexander <akotenev2003@gmail.com>
This commit is contained in:
Generated
+4391
-38
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,7 @@ members = [
|
||||
"crates/core/shturman-firstboot",
|
||||
"crates/core/shturman-settings",
|
||||
"crates/core/shturman-power",
|
||||
"crates/apps/shturman-shell",
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
@@ -28,6 +29,7 @@ tracing-journald = "0.3"
|
||||
anyhow = "1"
|
||||
thiserror = "1"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
slint = { version = "1", default-features = false, features = ["compat-1-2", "std", "backend-winit", "renderer-software"] }
|
||||
# dev
|
||||
tempfile = "3"
|
||||
futures-util = "0.3"
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "shturman-shell"
|
||||
version = "0.0.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
shturman-sdk = { path = "../../shturman-sdk" }
|
||||
shturman-common = { path = "../../shturman-common" }
|
||||
tokio.workspace = true
|
||||
anyhow.workspace = true
|
||||
tracing.workspace = true
|
||||
slint.workspace = true
|
||||
@@ -0,0 +1,147 @@
|
||||
//! `shturman-shell` — первый Slint-кадр (срезы C03/C04/C05/C07/C02). На SDK (architecture §1).
|
||||
//! v0: одноразовое чтение `ui.theme`/Power при старте (best-effort; без шины — дефолты, #4); рендер.
|
||||
//! Live-обновления (Changed/AccChanged) и локальная tz часов — позже (v0.5 / a-base §7).
|
||||
|
||||
mod theme;
|
||||
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
slint::slint! {
|
||||
import { VerticalBox, HorizontalBox } from "std-widgets.slint";
|
||||
|
||||
export component AppWindow inherits Window {
|
||||
in property <bool> is-night: false;
|
||||
in property <string> clock: "--:--";
|
||||
in property <string> network: "unknown";
|
||||
in property <string> ignition: "unknown";
|
||||
in property <[string]> tiles: ["Навигация", "Музыка", "Телефон", "Ассистент", "Машина", "Настройки"];
|
||||
|
||||
title: "Штурман";
|
||||
width: 1024px;
|
||||
height: 600px;
|
||||
background: root.is-night ? #0e1014 : #f4f5f7;
|
||||
|
||||
VerticalBox {
|
||||
padding: 16px;
|
||||
spacing: 16px;
|
||||
|
||||
HorizontalBox {
|
||||
height: 44px;
|
||||
Text {
|
||||
text: root.clock;
|
||||
font-size: 22px;
|
||||
color: root.is-night ? #f0f0f0 : #1a1a1a;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
Rectangle { }
|
||||
Text {
|
||||
text: "сеть: " + root.network;
|
||||
color: root.is-night ? #9aa0a6 : #5f6368;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
Text {
|
||||
text: "зажигание: " + root.ignition;
|
||||
color: root.is-night ? #9aa0a6 : #5f6368;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalBox {
|
||||
spacing: 16px;
|
||||
for tile in root.tiles : Rectangle {
|
||||
background: root.is-night ? #1b1e24 : #ffffff;
|
||||
border-radius: 16px;
|
||||
Text {
|
||||
text: tile;
|
||||
font-size: 18px;
|
||||
color: root.is-night ? #e8eaed : #202124;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Initial {
|
||||
theme: String,
|
||||
ignition: String,
|
||||
network: String,
|
||||
}
|
||||
|
||||
impl Default for Initial {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
theme: "auto".into(),
|
||||
ignition: "unknown".into(),
|
||||
network: "unknown".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Одноразовое чтение состояния с шины (best-effort). Без шины/сервисов — дефолты (#4).
|
||||
fn read_initial() -> Initial {
|
||||
let rt = match tokio::runtime::Runtime::new() {
|
||||
Ok(rt) => rt,
|
||||
Err(_) => return Initial::default(),
|
||||
};
|
||||
rt.block_on(async {
|
||||
match connect_and_read().await {
|
||||
Ok(i) => i,
|
||||
Err(e) => {
|
||||
tracing::warn!(error = %e, "нет шины/сервисов — дефолты кадра");
|
||||
Initial::default()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn connect_and_read() -> anyhow::Result<Initial> {
|
||||
let conn = shturman_sdk::connect().await?;
|
||||
let settings = shturman_sdk::SettingsClient::new(&conn).await?;
|
||||
let theme = settings
|
||||
.get("ui.theme")
|
||||
.await
|
||||
.ok()
|
||||
.and_then(|v| String::try_from(v).ok())
|
||||
.unwrap_or_else(|| "auto".into());
|
||||
let power = shturman_sdk::PowerClient::new(&conn).await?;
|
||||
let ignition = power
|
||||
.ignition_state()
|
||||
.await
|
||||
.map(|i| i.as_str().to_string())
|
||||
.unwrap_or_else(|_| "unknown".into());
|
||||
Ok(Initial {
|
||||
theme,
|
||||
ignition,
|
||||
network: "unknown".into(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Часы UTC `HH:MM` без tz-зависимостей (локальная tz — позже, a-base §7). Возвращает (час, строка).
|
||||
fn utc_hh_mm() -> (u8, String) {
|
||||
let secs = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map(|d| d.as_secs())
|
||||
.unwrap_or(0);
|
||||
let h = ((secs / 3600) % 24) as u8;
|
||||
let m = ((secs / 60) % 60) as u8;
|
||||
(h, format!("{h:02}:{m:02}"))
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
shturman_common::init_tracing("shturman-shell");
|
||||
let initial = read_initial();
|
||||
let (hour, clock) = utc_hh_mm();
|
||||
|
||||
let ui = AppWindow::new()?;
|
||||
ui.set_is_night(theme::resolve_night(&initial.theme, hour));
|
||||
ui.set_clock(clock.into());
|
||||
ui.set_network(initial.network.into());
|
||||
ui.set_ignition(initial.ignition.into());
|
||||
tracing::info!("первый Slint-кадр");
|
||||
ui.run()?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//! Тема день/ночь (C §6). v0: по часу (UTC); локальная tz и GPS-восход — позже (a-base §7 / домен K).
|
||||
|
||||
/// `day`→день, `night`→ночь, `auto`/неизвестное → ночь если `hour < 7 || hour >= 20` (🟡 пороги).
|
||||
pub fn resolve_night(theme: &str, hour_utc: u8) -> bool {
|
||||
match theme {
|
||||
"day" => false,
|
||||
"night" => true,
|
||||
_ => !(7..20).contains(&hour_utc),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn explicit_day_night() {
|
||||
assert!(!resolve_night("day", 3));
|
||||
assert!(resolve_night("night", 12));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_by_hour() {
|
||||
assert!(resolve_night("auto", 2)); // ночь
|
||||
assert!(resolve_night("auto", 23)); // ночь
|
||||
assert!(!resolve_night("auto", 12)); // день
|
||||
assert!(!resolve_night("auto", 7)); // день (нижняя граница)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_falls_back_to_auto() {
|
||||
assert_eq!(resolve_night("bogus", 2), resolve_night("auto", 2));
|
||||
assert_eq!(resolve_night("bogus", 12), resolve_night("auto", 12));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
# Лицензионная гигиена (#12) + advisories.
|
||||
# Заражающий дистрибуцию копилефт (GPL/AGPL) — НЕ в allow. LGPL — гранулярно (точечно при появлении).
|
||||
# slint GPL-3.0-exception добавится в Плане 4 (когда slint войдёт в граф зависимостей).
|
||||
# Заражающий дистрибуцию копилефт (GPL/AGPL) — НЕ в общем allow. LGPL — гранулярно (точечно при появлении).
|
||||
#
|
||||
# Slint лицензирован "GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0".
|
||||
# Для embedded royalty-free неприменим → берём GPL-3.0 (вариант A; финал по UI-тулкиту/лицензии — к v4,
|
||||
# см. docs/specs/v0.1-v0.6-foundation.md §12). Шипимый UI-бинарь прод-образа (v4) будет копилефтным.
|
||||
# Точечные exceptions для slint-крейтов ниже — НЕ глобальный allow GPL.
|
||||
|
||||
[advisories]
|
||||
version = 2
|
||||
@@ -16,8 +20,21 @@ allow = [
|
||||
"Unicode-3.0",
|
||||
"Unicode-DFS-2016",
|
||||
"Zlib",
|
||||
"BSL-1.0",
|
||||
]
|
||||
confidence-threshold = 0.9
|
||||
exceptions = [
|
||||
{ name = "slint", allow = ["GPL-3.0-only"] },
|
||||
{ name = "slint-macros", allow = ["GPL-3.0-only"] },
|
||||
{ name = "i-slint-core", allow = ["GPL-3.0-only"] },
|
||||
{ name = "i-slint-core-macros", allow = ["GPL-3.0-only"] },
|
||||
{ name = "i-slint-common", allow = ["GPL-3.0-only"] },
|
||||
{ name = "i-slint-compiler", allow = ["GPL-3.0-only"] },
|
||||
{ name = "i-slint-backend-selector", allow = ["GPL-3.0-only"] },
|
||||
{ name = "i-slint-backend-winit", allow = ["GPL-3.0-only"] },
|
||||
{ name = "i-slint-renderer-software", allow = ["GPL-3.0-only"] },
|
||||
{ name = "i-slint-renderer-skia", allow = ["GPL-3.0-only"] },
|
||||
]
|
||||
|
||||
[bans]
|
||||
multiple-versions = "warn"
|
||||
|
||||
Reference in New Issue
Block a user