Files
community-plugins/phone-connect/widget.luau
T
CSY2569andGitHub 16d66a5b18 fix(phone-connect): zh-Hans locale, aliasMap, box padding, volume, config data loss, interval, dead settings, slider, UI issues (#199)
* 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).
2026-08-01 08:56:54 -04:00

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