* fix(phone-connect): zh-Hans locale, switcher aliasMap, box padding - Rename zh_hans -> zh-Hans (plugin.toml value, en.json key) and add the missing translations/zh-Hans.json. The zh_hans tag does not match BCP 47, so the framework's materialize step dropped the Chinese translation file and t() returned raw dot keys in the UI. - Add missing aliasMap definition in switcher() so the multi-device panel no longer errors on aliasMap[id]. Fixes #176. - Replace ui.box({padding=...}) with ui.row({padding=...}); box does not support the padding prop, causing ui-tree warnings on open. Supersedes #190 (which covers only the aliasMap fix). * fix(phone-connect): volume quoting, config data loss, interval, dead settings, slider, UI issues 1. media_set_volume: shellQuote the <int32> variant arg (was unquoted, shell treated < as redirect; same fix as media_seek). 2. onConfigChanged: track last-applied custom_image/device_alias values so unrelated config edits (e.g. language switch) no longer wipe a device's image/alias. Seed trackers at init so reload is safe too. 3. state_update_interval=0: return 3600000ms + guard in update() to skip auto-refresh, matching the '0 disables' description. 4. Remove 4 dead settings (show_ongoing_media, show_device_placeholder, scan_subdirectories, max_recent_images) declared but never read, plus orphan recent_images translation keys. 5. Media slider: freeze value during drag via per-device dragState so the 1s service refresh doesn't yank the slider back. Also fixes onDragEnd seeking to a stale seekPos. 6. Minor: runCli reports cli_failed not no_backend; browse uses runAsync not runInTerminal; charging color priority over low-battery; device name falls back to d.name; fix comment mismatches. * chore: bump version to 0.1.1 * fix: use lowercase zh-hans translation key to pass CI validation CI rejects uppercase in translation keys (zh-Hans). The translation key and label_key are now lowercase zh-hans, while the file name stays zh-Hans.json (required by the framework's materialize step) and the select value stays zh-Hans (used to build the file path).
134 lines
4.3 KiB
Luau
134 lines
4.3 KiB
Luau
-- widget.luau
|
|
-- Phone Connect - bar widget (thin UI entry).
|
|
--
|
|
-- Renders a bar pill reflecting the selected device: device glyph + battery
|
|
-- percent, colored by state (charging -> primary, low -> error, offline ->
|
|
-- dimmed). Left-click opens the details panel; right-click opens settings.
|
|
-- Reads state from the service entry; no DBus logic here.
|
|
|
|
-- Local translation: reads from pc.trTable (published by service) so users
|
|
-- can switch language at runtime, unlike noctalia.tr() which follows system locale.
|
|
local function t(key)
|
|
local table = noctalia.state.get("pc.trTable") or {}
|
|
return table[key] or key
|
|
end
|
|
|
|
local function panelId()
|
|
local mode = noctalia.getConfig("panel_placement")
|
|
if mode == "floating" then
|
|
return "icefish/phone-connect:details_floating"
|
|
elseif mode == "widget" then
|
|
return "icefish/phone-connect:details_widget"
|
|
end
|
|
return "icefish/phone-connect:details"
|
|
end
|
|
|
|
local function glyphFor(d)
|
|
if not d or not d.isReachable then return "phone-off" end
|
|
local t = d.type
|
|
if t == "tablet" then return "device-tablet" end
|
|
if t == "laptop" then return "device-laptop" end
|
|
if t == "desktop" or t == "computer" then return "device-desktop" end
|
|
if t == "tv" then return "device-tv" end
|
|
return "device-mobile"
|
|
end
|
|
|
|
local function batteryGlyph(charge, charging)
|
|
if charging then return "battery-charging" end
|
|
if type(charge) ~= "number" then return "battery" end
|
|
if charge >= 90 then return "battery" end
|
|
if charge >= 60 then return "battery-3" end
|
|
if charge >= 30 then return "battery-2" end
|
|
if charge > 0 then return "battery-1" end
|
|
return "battery-off"
|
|
end
|
|
|
|
local function selectedDevice()
|
|
local devices = noctalia.state.get("pc.devices") or {}
|
|
local order = noctalia.state.get("pc.order") or {}
|
|
local sel = noctalia.state.get("pc.selected")
|
|
local id = sel
|
|
if not id or not devices[id] then id = order[1] end
|
|
if id then return devices[id] end
|
|
return nil
|
|
end
|
|
|
|
local function render()
|
|
local backend = noctalia.state.get("pc.backend") or { available = false }
|
|
local d = selectedDevice()
|
|
local enableCharging = noctalia.getConfig("enable_charging_animation")
|
|
|
|
local children
|
|
if not backend.available then
|
|
-- no backend: dim phone-off glyph + localized status
|
|
children = {
|
|
ui.glyph({ name = "phone-off", size = 14, color = "on_surface/0.4" }),
|
|
ui.label({ text = t("status.no_backend"), fontSize = 10,
|
|
color = "on_surface/0.5" }),
|
|
}
|
|
elseif not d then
|
|
children = {
|
|
ui.glyph({ name = "phone-off", size = 14, color = "on_surface/0.4" }),
|
|
ui.label({ text = t("status.no_devices"), fontSize = 10,
|
|
color = "on_surface/0.5" }),
|
|
}
|
|
else
|
|
local reachable = d.isReachable == true
|
|
local charge = d.batteryCharge
|
|
local charging = d.batteryCharging == true
|
|
local hasCharge = type(charge) == "number" and charge >= 0
|
|
|
|
-- device glyph: primary when charging, dim when offline, normal otherwise
|
|
local glyphColor = "on_surface"
|
|
if reachable and charging and enableCharging then
|
|
glyphColor = "primary"
|
|
elseif not reachable then
|
|
glyphColor = "on_surface/0.4"
|
|
end
|
|
|
|
local kids = { ui.glyph({ name = glyphFor(d), size = 14, color = glyphColor }) }
|
|
|
|
if reachable and hasCharge then
|
|
-- battery glyph + percent, colored by level
|
|
local batColor = "on_surface"
|
|
if charging then batColor = "primary"
|
|
elseif charge <= 20 then batColor = "error" end
|
|
table.insert(kids, ui.glyph({ name = batteryGlyph(charge, charging),
|
|
size = 12, color = batColor }))
|
|
table.insert(kids, ui.label({
|
|
text = tostring(charge) .. "%", fontSize = 11,
|
|
fontWeight = "medium", color = batColor,
|
|
}))
|
|
elseif not reachable and d.isPaired then
|
|
-- offline but paired: show a small dimmed "offline" tag
|
|
table.insert(kids, ui.label({ text = t("status.offline"),
|
|
fontSize = 10, color = "on_surface/0.5" }))
|
|
end
|
|
children = kids
|
|
end
|
|
|
|
local container = barWidget.isVertical() and ui.column or ui.row
|
|
barWidget.render(container({ gap = 5, align = "center" }, children))
|
|
end
|
|
|
|
function update()
|
|
noctalia.setUpdateInterval(2000)
|
|
render()
|
|
end
|
|
|
|
function onClick()
|
|
noctalia.togglePanel(panelId())
|
|
end
|
|
|
|
function onRightClick()
|
|
noctalia.openSettings()
|
|
end
|
|
|
|
noctalia.state.watch("pc.devices", render)
|
|
noctalia.state.watch("pc.order", render)
|
|
noctalia.state.watch("pc.selected", render)
|
|
noctalia.state.watch("pc.backend", render)
|
|
noctalia.state.watch("pc.trTable", render)
|
|
|
|
render()
|