feat(infrared): expand remote library and creation flow (#13)
## Summary - replace the crowded add-remote dialog with a full-screen three-step flow - make the first step explicitly choose between adding from the infrared library and creating through learning - add a full-screen remote editor with read-only library configuration, editable custom layouts, and delete confirmation - append newly created remotes to the bottom of the management list and right side of the remote switcher - remove the fan emoji from the device type label - add a curated CC0 Flipper-IRDB preset subset with real NEC/NECext protocol codes - add projector, audio/receiver, set-top box, streaming device, and lighting categories - require a test power command and explicit device-response confirmation before creating library remotes - include source attribution and a repeatable converter for parsed NEC/NECext signals ## Compatibility scope - imported formats: parsed NEC and NECext only - excluded: raw pulse data and other protocols because the ABox irCode encoding contract is not publicly specified for them - existing devices, remotes, and scenes are not migrated or renamed ## Verification - ./gradlew.bat :android:app:assembleDebug --no-daemon - converter reproduced Reviewed-on: #13 Co-authored-by: KremeCN <3265494849@qq.com>
This commit was merged in pull request #13.
This commit is contained in:
+77
@@ -0,0 +1,77 @@
|
||||
package com.flagship.abox.manager.domain.infrared
|
||||
|
||||
internal data class OpenSourceIrPreset(
|
||||
val option: PresetBrandOption,
|
||||
val buttons: List<PresetCodeLibrary.PresetButtonTemplate>,
|
||||
)
|
||||
|
||||
internal object OpenSourceIrCatalog {
|
||||
val presets = listOf(
|
||||
preset("firetv_omni_4k", "Amazon Fire TV Omni 4K", RemoteDeviceType.TV, "TVs/Amazon/FireTV_Omni_Series_4K.ir",
|
||||
"POWER" to "电源" to "0x027D46B9", "HOME" to "主页" to "0x027D9F60", "MENU" to "菜单" to "0x027D45BA",
|
||||
"BACK" to "返回" to "0x027D0DF2", "VOL_UP" to "音量 +" to "0x027D0CF3", "VOL_DOWN" to "音量 -" to "0x027D19E6",
|
||||
"MUTE" to "静音" to "0x027D4CB3", "UP" to "上" to "0x027D48B7", "DOWN" to "下" to "0x027D4DB2",
|
||||
"LEFT" to "左" to "0x027D4EB1", "RIGHT" to "右" to "0x027D49B6", "OK" to "确定" to "0x027D4AB5"),
|
||||
preset("akai_ate_22y604w", "Akai ATE-22Y604W", RemoteDeviceType.TV, "TVs/Akai/AKAI_ATE_22Y604W.ir",
|
||||
"POWER" to "电源" to "0x01721EE1", "MUTE" to "静音" to "0x01725CA3", "VOL_UP" to "音量 +" to "0x01720AF5",
|
||||
"VOL_DOWN" to "音量 -" to "0x017206F9", "CH_UP" to "频道 +" to "0x017248B7", "CH_DOWN" to "频道 -" to "0x017244BB",
|
||||
"SOURCE" to "信号源" to "0x017207F8", "OK" to "确定" to "0x01725FA0", "UP" to "上" to "0x017203FC", "DOWN" to "下" to "0x01725EA1"),
|
||||
preset("acer_h6517st", "Acer H6517ST", RemoteDeviceType.PROJECTOR, "Projectors/Acer/Acer_H6517ST.ir",
|
||||
"POWER" to "电源" to "0x08138778", "SOURCE" to "信号源" to "0x08138C73", "SETUP" to "设置" to "0x08133BC4",
|
||||
"MODE" to "模式" to "0x0813807F", "ZOOM" to "缩放" to "0x08138B74", "MENU" to "菜单" to "0x0813847B",
|
||||
"UP" to "上" to "0x0813827D", "DOWN" to "下" to "0x0813857A", "LEFT" to "左" to "0x0813837C", "RIGHT" to "右" to "0x0813817E"),
|
||||
preset("nebula_capsule", "Anker Nebula Capsule", RemoteDeviceType.PROJECTOR, "Projectors/Anker/Nebula_Capsule_Mini_Projector.ir",
|
||||
"POWER" to "电源" to "0x807F51AE", "OK" to "确定" to "0x807F0DF2", "UP" to "上" to "0x807F26D9",
|
||||
"DOWN" to "下" to "0x807F28D7", "LEFT" to "左" to "0x807F25DA", "RIGHT" to "右" to "0x807F27D8",
|
||||
"MENU" to "菜单" to "0x807F49B6", "BACK" to "返回" to "0x807F1BE4", "HOME" to "主页" to "0x807F53AC"),
|
||||
preset("bose_solo_5", "Bose Solo 5", RemoteDeviceType.AUDIO, "SoundBars/Bose/Bose_Solo_5.ir",
|
||||
"POWER" to "电源" to "0xBAA04CB3", "MUTE" to "静音" to "0xBAA001FE", "VOL_UP" to "音量 +" to "0xBAA003FC",
|
||||
"VOL_DOWN" to "音量 -" to "0xBAA002FD", "PLAY_PAUSE" to "播放/暂停" to "0xBAA055AA", "NEXT" to "下一曲" to "0xBAA05AA5",
|
||||
"PREVIOUS" to "上一曲" to "0xBAA059A6", "BLUETOOTH" to "蓝牙" to "0xBAA0817E", "BASS" to "低音" to "0xBAA0827D"),
|
||||
preset("fosi_zd3", "Fosi Audio ZD3", RemoteDeviceType.AUDIO, "Audio_and_Video_Receivers/Fosi_Audio/Fosi_Audio_ZD3.ir",
|
||||
"POWER" to "电源" to "0xFA0105FA", "MUTE" to "静音" to "0xFA0102FD", "VOL_UP" to "音量 +" to "0xFA0103FC",
|
||||
"VOL_DOWN" to "音量 -" to "0xFA0107F8", "PLAY_PAUSE" to "播放/暂停" to "0xFA010AF5", "NEXT" to "下一曲" to "0xFA0106F9",
|
||||
"PREVIOUS" to "上一曲" to "0xFA0104FB", "OPTICAL" to "光纤" to "0xFA0108F7", "HDMI" to "HDMI" to "0xFA0113EC", "BLUETOOTH" to "蓝牙" to "0xFA0115EA"),
|
||||
preset("humax_generic", "Humax Cable Box", RemoteDeviceType.SET_TOP_BOX, "Cable_Boxes/Humax/Humax_generic_cable_box.ir",
|
||||
"POWER" to "电源" to "0x001000FF", "TV_RADIO" to "电视/广播" to "0x00100DF2", "SOURCE" to "信号源" to "0x001002FD",
|
||||
"VOL_UP" to "音量 +" to "0x00101FE0", "VOL_DOWN" to "音量 -" to "0x001040BF", "CH_UP" to "频道 +" to "0x001010EF", "CH_DOWN" to "频道 -" to "0x00100FF0"),
|
||||
preset("huawei_dn300", "Huawei DN300", RemoteDeviceType.SET_TOP_BOX, "Cable_Boxes/Huawei/Huawei_dn300_mgts(mts).ir",
|
||||
"POWER" to "电源" to "0x00BF43BC", "VOL_UP" to "音量 +" to "0x00BF52AD", "VOL_DOWN" to "音量 -" to "0x00BF53AC",
|
||||
"CH_UP" to "频道 +" to "0x00BF4BB4", "CH_DOWN" to "频道 -" to "0x00BF4CB3", "UP" to "上" to "0x00BF15EA",
|
||||
"DOWN" to "下" to "0x00BF16E9", "LEFT" to "左" to "0x00BF17E8", "RIGHT" to "右" to "0x00BF18E7", "BACK" to "返回" to "0x00BF51AE", "OK" to "确定" to "0x00BF19E6"),
|
||||
preset("firetv_stick_4k_max", "Amazon Fire TV Stick 4K Max", RemoteDeviceType.STREAMING_DEVICE, "Streaming_Devices/Amazon/FireTV_Stick_4K_Max.ir",
|
||||
"POWER" to "电源" to "0x03FC1DE2", "MUTE" to "静音" to "0x007F4EB1", "VOL_UP" to "音量 +" to "0x03FC11EE", "VOL_DOWN" to "音量 -" to "0x03FC15EA"),
|
||||
preset("apple_tv_4k_gen2", "Apple TV 4K Gen 2", RemoteDeviceType.STREAMING_DEVICE, "Streaming_Devices/Apple/Apple_TV_4K_Gen2.ir",
|
||||
"POWER_OFF" to "关机" to "0xE5872AFE", "POWER_ON" to "开机" to "0xE5872CFE", "TV" to "电视" to "0xE58740FE",
|
||||
"OK" to "确定" to "0xEE875C8C", "UP" to "上" to "0xEE870A8C", "DOWN" to "下" to "0xEE870C8C",
|
||||
"LEFT" to "左" to "0xEE87098C", "RIGHT" to "右" to "0xEE87068C", "BACK" to "返回" to "0xEE87038C", "PLAY_PAUSE" to "播放/暂停" to "0xEE875F8C"),
|
||||
preset("atomberg_fan", "Atomberg Fan", RemoteDeviceType.FAN, "Fans/Atomberg/Atomberg_fan.ir",
|
||||
"POWER" to "电源" to "0x00F3916E", "SPEED_1" to "风速 1" to "0x00F38B74", "SPEED_2" to "风速 2" to "0x00F3906F",
|
||||
"SPEED_3" to "风速 3" to "0x00F38A75", "SPEED_4" to "风速 4" to "0x00F3936C", "SPEED_5" to "风速 5" to "0x00F38877",
|
||||
"BOOST" to "强劲" to "0x00F38F70", "TIMER" to "定时" to "0x00F39669", "SLEEP" to "睡眠" to "0x00F38E71"),
|
||||
preset("daewoo_ddv200", "Daewoo DDV200", RemoteDeviceType.FAN, "Fans/Daewoo/Daewoo_DDV200.ir",
|
||||
"POWER" to "电源" to "0x00FF45BA", "SPEED" to "风速" to "0x00FF47B8", "WIND" to "风类" to "0x00FF40BF",
|
||||
"TIMER" to "定时" to "0x00FF19E6", "OSCILLATE" to "摇头" to "0x00FF1CE3"),
|
||||
preset("adj_uc_ir", "ADJ UC-IR Lighting", RemoteDeviceType.LIGHT, "LED_Lighting/ADJ/ADJ_UC-IR.ir",
|
||||
"POWER" to "电源" to "0x00FF47B8", "BRIGHTER" to "调亮" to "0x00FF43BC", "DIMMER" to "调暗" to "0x00FF09F6",
|
||||
"FULL_ON" to "全亮" to "0x00FF44BB", "FADE" to "渐变" to "0x00FF40BF", "STROBE" to "频闪" to "0x00FF07F8", "COLOR" to "颜色" to "0x00FF15EA"),
|
||||
preset("antizer_candles", "Antizer Flameless Candles", RemoteDeviceType.LIGHT, "LED_Lighting/Antizer_Flameless_Candles/Antizer_Flameless_Candles.ir",
|
||||
"ON" to "开启" to "0x08B700FF", "OFF" to "关闭" to "0x08B702FD", "DIMMER" to "调暗" to "0x08B710EF",
|
||||
"BRIGHTER" to "调亮" to "0x08B712ED", "CANDLE" to "烛光模式" to "0x08B70CF3", "LIGHT" to "常亮模式" to "0x08B70EF1"),
|
||||
)
|
||||
|
||||
fun preset(id: String?): OpenSourceIrPreset? = presets.firstOrNull { it.option.id == id }
|
||||
|
||||
private fun preset(
|
||||
id: String,
|
||||
name: String,
|
||||
type: RemoteDeviceType,
|
||||
sourcePath: String,
|
||||
vararg buttons: Pair<Pair<String, String>, String>,
|
||||
) = OpenSourceIrPreset(
|
||||
PresetBrandOption(id, name, type, sourcePath),
|
||||
buttons.map { (identity, code) -> PresetCodeLibrary.PresetButtonTemplate(identity.first, identity.second, code) },
|
||||
)
|
||||
|
||||
private infix fun Pair<String, String>.to(code: String): Pair<Pair<String, String>, String> = Pair(this, code)
|
||||
}
|
||||
+10
-20
@@ -4,30 +4,21 @@ data class PresetBrandOption(
|
||||
val id: String,
|
||||
val displayName: String,
|
||||
val deviceType: RemoteDeviceType,
|
||||
val sourcePath: String? = null,
|
||||
)
|
||||
|
||||
object PresetCodeLibrary {
|
||||
|
||||
val availablePresets: List<PresetBrandOption> = listOf(
|
||||
// 电视预设
|
||||
PresetBrandOption("generic_tv", "通用电视 (Generic TV)", RemoteDeviceType.TV),
|
||||
PresetBrandOption("xiaomi_tv", "小米电视 (Xiaomi TV)", RemoteDeviceType.TV),
|
||||
PresetBrandOption("sony_tv", "索尼电视 (Sony TV)", RemoteDeviceType.TV),
|
||||
PresetBrandOption("samsung_tv", "三星电视 (Samsung TV)", RemoteDeviceType.TV),
|
||||
|
||||
// 空调预设
|
||||
PresetBrandOption("gree_ac", "格力空调 (Gree AC)", RemoteDeviceType.AIR_CONDITIONER),
|
||||
PresetBrandOption("midea_ac", "美的空调 (Midea AC)", RemoteDeviceType.AIR_CONDITIONER),
|
||||
PresetBrandOption("haier_ac", "海尔空调 (Haier AC)", RemoteDeviceType.AIR_CONDITIONER),
|
||||
PresetBrandOption("aux_ac", "奥克斯空调 (AUX AC)", RemoteDeviceType.AIR_CONDITIONER),
|
||||
|
||||
// 风扇预设
|
||||
PresetBrandOption("midea_fan", "美的电风扇 (Midea Fan)", RemoteDeviceType.FAN),
|
||||
PresetBrandOption("generic_fan", "通用风扇 (Generic Fan)", RemoteDeviceType.FAN),
|
||||
|
||||
// 自定义
|
||||
PresetBrandOption("custom_blank", "自定义遥控器 (Custom)", RemoteDeviceType.CUSTOM),
|
||||
)
|
||||
) + OpenSourceIrCatalog.presets.map { it.option }
|
||||
|
||||
data class PresetButtonTemplate(
|
||||
val buttonId: String,
|
||||
@@ -39,8 +30,8 @@ object PresetCodeLibrary {
|
||||
type: RemoteDeviceType,
|
||||
presetId: String? = null,
|
||||
layoutStyle: PanelLayoutStyle? = null,
|
||||
): List<PresetButtonTemplate> = when (type) {
|
||||
RemoteDeviceType.TV -> getTvTemplates(hasHexCode = true)
|
||||
): List<PresetButtonTemplate> = OpenSourceIrCatalog.preset(presetId)?.buttons ?: when (type) {
|
||||
RemoteDeviceType.TV -> emptyList()
|
||||
RemoteDeviceType.AIR_CONDITIONER -> listOf(
|
||||
PresetButtonTemplate("AC_POWER", "电源"),
|
||||
PresetButtonTemplate("AC_TEMP_UP", "升温"),
|
||||
@@ -51,13 +42,12 @@ object PresetCodeLibrary {
|
||||
PresetButtonTemplate("AC_SWING_H", "左右扫风"),
|
||||
PresetButtonTemplate("AC_ECO", "节能"),
|
||||
)
|
||||
RemoteDeviceType.FAN -> listOf(
|
||||
PresetButtonTemplate("FAN_POWER", "电源", "0x01FE02FD"),
|
||||
PresetButtonTemplate("FAN_SPEED", "风速", "0x01FE04FB"),
|
||||
PresetButtonTemplate("FAN_SWING", "摇头", "0x01FE06F9"),
|
||||
PresetButtonTemplate("FAN_TIMER", "定时", "0x01FE08F7"),
|
||||
PresetButtonTemplate("FAN_MODE", "模式", "0x01FE0AF5"),
|
||||
)
|
||||
RemoteDeviceType.FAN,
|
||||
RemoteDeviceType.PROJECTOR,
|
||||
RemoteDeviceType.AUDIO,
|
||||
RemoteDeviceType.SET_TOP_BOX,
|
||||
RemoteDeviceType.STREAMING_DEVICE,
|
||||
RemoteDeviceType.LIGHT -> emptyList()
|
||||
RemoteDeviceType.CUSTOM -> when (layoutStyle) {
|
||||
PanelLayoutStyle.TV_NAV -> getTvTemplates(hasHexCode = false)
|
||||
PanelLayoutStyle.MEDIA_PLAYER -> getMediaPlayerTemplates(hasHexCode = false)
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.flagship.abox.manager.domain.infrared
|
||||
|
||||
import com.flagship.abox.manager.domain.DeviceName
|
||||
import com.flagship.abox.manager.domain.DomainError
|
||||
import com.flagship.abox.manager.domain.DomainResult
|
||||
import com.flagship.abox.manager.domain.InfraredCode
|
||||
import com.flagship.abox.manager.domain.InfraredGateway
|
||||
import com.flagship.abox.manager.domain.ProviderProfileId
|
||||
|
||||
class TestPresetInfraredCode(
|
||||
private val gateway: InfraredGateway,
|
||||
) {
|
||||
suspend operator fun invoke(
|
||||
profileId: ProviderProfileId,
|
||||
deviceName: DeviceName,
|
||||
presetId: String,
|
||||
): DomainResult<Unit> {
|
||||
val preset = OpenSourceIrCatalog.preset(presetId)
|
||||
?: return DomainResult.Failure(DomainError.InvalidRequest(detail = "Preset is not testable"))
|
||||
val testButton = preset.buttons.firstOrNull { button ->
|
||||
button.buttonId.contains("POWER") || button.buttonId == "ON" || button.buttonId == "OFF"
|
||||
} ?: return DomainResult.Failure(DomainError.NoData(detail = "Preset has no test button"))
|
||||
val code = testButton.defaultHexCode
|
||||
?: return DomainResult.Failure(DomainError.NoData(detail = "Preset test code is unavailable"))
|
||||
val scratchSlot = VirtualRemote.DEFAULT_DYNAMIC_SCRATCH_SLOT
|
||||
return when (val download = gateway.downloadCodes(profileId, listOf(InfraredCode(deviceName, scratchSlot, code)))) {
|
||||
is DomainResult.Failure -> download
|
||||
is DomainResult.Success -> gateway.sendKey(profileId, deviceName, scratchSlot)
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
-1
@@ -10,6 +10,11 @@ enum class RemoteDeviceType {
|
||||
TV,
|
||||
AIR_CONDITIONER,
|
||||
FAN,
|
||||
PROJECTOR,
|
||||
AUDIO,
|
||||
SET_TOP_BOX,
|
||||
STREAMING_DEVICE,
|
||||
LIGHT,
|
||||
CUSTOM,
|
||||
}
|
||||
|
||||
@@ -70,7 +75,12 @@ data class VirtualRemote(
|
||||
fun defaultLayoutFor(type: RemoteDeviceType): PanelLayoutStyle = when (type) {
|
||||
RemoteDeviceType.TV -> PanelLayoutStyle.TV_NAV
|
||||
RemoteDeviceType.AIR_CONDITIONER -> PanelLayoutStyle.AC_COMPACT
|
||||
RemoteDeviceType.FAN -> PanelLayoutStyle.GRID
|
||||
RemoteDeviceType.FAN,
|
||||
RemoteDeviceType.PROJECTOR,
|
||||
RemoteDeviceType.AUDIO,
|
||||
RemoteDeviceType.SET_TOP_BOX,
|
||||
RemoteDeviceType.STREAMING_DEVICE,
|
||||
RemoteDeviceType.LIGHT,
|
||||
RemoteDeviceType.CUSTOM -> PanelLayoutStyle.GRID
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user