Skip to content

Commit bdebf3f

Browse files
author
Kame
committed
Polish remotes home UX, app shortcuts, predictive back, and localization
1 parent f3ead89 commit bdebf3f

73 files changed

Lines changed: 11711 additions & 1062 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.flutter-plugins-dependencies

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

android/app/src/main/AndroidManifest.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@
4646
<application
4747
android:label="IR Blaster Remote"
4848
android:name="${applicationName}"
49-
android:icon="@mipmap/ic_launcher">
49+
android:icon="@mipmap/ic_launcher"
50+
android:enableOnBackInvokedCallback="true">
5051

5152
<!-- Mitigation for old GPU drivers: opt-out of Impeller -->
5253
<meta-data

android/app/src/main/kotlin/org/nslabs/irblaster/MainActivity.kt

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import android.content.Context
66
import android.content.Intent
77
import android.content.IntentFilter
88
import android.content.pm.PackageManager
9+
import android.content.pm.ShortcutInfo
10+
import android.content.pm.ShortcutManager
11+
import android.graphics.drawable.Icon
912
import android.hardware.ConsumerIrManager
1013
import android.hardware.usb.UsbDevice
1114
import android.hardware.usb.UsbManager
@@ -64,6 +67,8 @@ class MainActivity : FlutterActivity() {
6467
private var pendingControlButtonId: String? = null
6568
private var quickTileChannel: MethodChannel? = null
6669
private var pendingQuickTileChooserKey: String? = null
70+
private var shortcutsChannel: MethodChannel? = null
71+
private var pendingShortcutAction: String? = null
6772

6873
private fun loadTxTypeFromPrefs(): TxType {
6974
val v = prefs.getString("tx_type", TxType.INTERNAL.name) ?: TxType.INTERNAL.name
@@ -390,6 +395,8 @@ class MainActivity : FlutterActivity() {
390395
private const val EVENT_CHANNEL = "org.nslabs/irtransmitter_events"
391396
private const val CONTROL_CHANNEL = "org.nslabs/irtransmitter_controls"
392397
private const val QUICK_TILE_CHANNEL = "org.nslabs/irtransmitter_quick_tile"
398+
private const val SHORTCUTS_CHANNEL = "org.nslabs/app_shortcuts"
399+
private const val EXTRA_SHORTCUT_ACTION = "org.nslabs.irblaster.SHORTCUT_ACTION"
393400
private const val DEFAULT_HEX_FREQUENCY = 38000
394401
private const val MIN_IR_HZ = 15000
395402
private const val MAX_IR_HZ = 60000
@@ -478,10 +485,68 @@ class MainActivity : FlutterActivity() {
478485
dispatchQuickTileChooser(key)
479486
}
480487

488+
shortcutsChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, SHORTCUTS_CHANNEL)
489+
shortcutsChannel?.setMethodCallHandler { call, result ->
490+
when (call.method) {
491+
"updateDynamicShortcuts" -> handleUpdateDynamicShortcuts(call, result)
492+
"consumeInitialShortcutAction" -> {
493+
val action = pendingShortcutAction
494+
pendingShortcutAction = null
495+
result.success(action)
496+
}
497+
else -> result.notImplemented()
498+
}
499+
}
500+
481501
emitTxStatus("startup_done")
482502
emitTxStatusDelayed("startup_done_delayed", 350L)
483503
}
484504

505+
private fun handleUpdateDynamicShortcuts(call: MethodCall, result: MethodChannel.Result) {
506+
if (Build.VERSION.SDK_INT < 25) {
507+
result.success(false)
508+
return
509+
}
510+
511+
val shortcutManager = getSystemService(ShortcutManager::class.java)
512+
if (shortcutManager == null) {
513+
result.success(false)
514+
return
515+
}
516+
517+
val args = call.arguments as? Map<*, *>
518+
val rawItems = args?.get("items") as? List<*>
519+
val shortcuts = rawItems
520+
?.mapIndexedNotNull { index, raw -> buildDynamicShortcut(raw as? Map<*, *>, index) }
521+
?.take(4)
522+
?: emptyList()
523+
524+
shortcutManager.dynamicShortcuts = shortcuts
525+
result.success(true)
526+
}
527+
528+
private fun buildDynamicShortcut(raw: Map<*, *>?, rank: Int): ShortcutInfo? {
529+
if (raw == null || Build.VERSION.SDK_INT < 25) return null
530+
val id = (raw["id"] as? String)?.trim().orEmpty()
531+
val shortLabel = (raw["shortLabel"] as? String)?.trim().orEmpty()
532+
val longLabel = (raw["longLabel"] as? String)?.trim().orEmpty()
533+
if (id.isEmpty() || shortLabel.isEmpty()) return null
534+
535+
val intent = Intent(applicationContext, MainActivity::class.java).apply {
536+
action = Intent.ACTION_VIEW
537+
putExtra(EXTRA_SHORTCUT_ACTION, id)
538+
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
539+
}
540+
541+
return ShortcutInfo.Builder(applicationContext, id)
542+
.setShortLabel(shortLabel)
543+
.setLongLabel(if (longLabel.isNotEmpty()) longLabel else shortLabel)
544+
.setIcon(Icon.createWithResource(applicationContext, R.mipmap.ic_launcher))
545+
.setIntent(intent)
546+
.setRank(rank)
547+
.build()
548+
}
549+
485550
private fun handlePerformHaptic(call: MethodCall, result: MethodChannel.Result) {
486551
val type = call.argument<String>("type") ?: "selection"
487552
val intensity = (call.argument<Int>("intensity") ?: 2).coerceIn(1, 3)
@@ -686,12 +751,14 @@ class MainActivity : FlutterActivity() {
686751
super.onNewIntent(intent)
687752
handleControlIntent(intent)
688753
handleQuickTileIntent(intent)
754+
handleRuntimeShortcutIntent(intent)
689755
}
690756

691757
override fun onCreate(savedInstanceState: android.os.Bundle?) {
692758
super.onCreate(savedInstanceState)
693759
handleControlIntent(intent)
694760
handleQuickTileIntent(intent)
761+
captureInitialShortcutIntent(intent)
695762
}
696763

697764
private fun handleControlIntent(intent: Intent?) {
@@ -723,6 +790,27 @@ class MainActivity : FlutterActivity() {
723790
ch.invokeMethod("openChooser", mapOf("tileKey" to tileKey))
724791
}
725792

793+
private fun captureInitialShortcutIntent(intent: Intent?) {
794+
val action = intent?.getStringExtra(EXTRA_SHORTCUT_ACTION)?.trim().orEmpty()
795+
if (action.isEmpty()) return
796+
pendingShortcutAction = action
797+
}
798+
799+
private fun handleRuntimeShortcutIntent(intent: Intent?) {
800+
val action = intent?.getStringExtra(EXTRA_SHORTCUT_ACTION)?.trim().orEmpty()
801+
if (action.isEmpty()) return
802+
dispatchShortcutAction(action)
803+
}
804+
805+
private fun dispatchShortcutAction(action: String) {
806+
val ch = shortcutsChannel
807+
if (ch == null) {
808+
pendingShortcutAction = action
809+
return
810+
}
811+
ch.invokeMethod("openShortcut", mapOf("action" to action))
812+
}
813+
726814
override fun onDestroy() {
727815
try {
728816
applicationContext.unregisterReceiver(usbReceiver)

android/local.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
sdk.dir=/usr/lib/android-sdk
22
flutter.sdk=/home/intra/snap/flutter/common/flutter
33
flutter.buildMode=release
4-
flutter.versionName=2.1.10
5-
flutter.versionCode=32
4+
flutter.versionName=2.1.11
5+
flutter.versionCode=33
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
- Improved the Remotes home screen with a faster and cleaner top strip for last remote and pinned remotes
2+
- Added launcher shortcuts for Last Remote, Signal Tester, Last Macro, and Universal Power
3+
- Added predictive back support handling for Remote View, Signal Tester, and macro screens
4+
- Improved localization support, added more app languages, and fixed fallback-to-English behavior for unsupported system languages
5+
- Fixed Polish plural and count grammar in key UI labels and summaries
6+
- Improved USB IR dongle support, including additional ElkSmart device detection
7+
- Reduced bulky top-of-screen UI so remotes are easier to reach on small phones

lib/l10n/app_ar.arb

Lines changed: 177 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@
322322
"remoteLayoutSummary": "{count} زر · {layout}",
323323
"@remoteLayoutSummary": {
324324
"placeholders": {
325-
"count": {},
325+
"count": {"type": "int"},
326326
"layout": {}
327327
}
328328
},
@@ -396,7 +396,7 @@
396396
"remoteButtonCountSummary": "{count} زر",
397397
"@remoteButtonCountSummary": {
398398
"placeholders": {
399-
"count": {}
399+
"count": {"type": "int"}
400400
}
401401
},
402402
"remoteOrientationFlippedTooltip": "Orientation: flipped (tap إلى normal)",
@@ -424,6 +424,7 @@
424424
"buttonDuplicated": "زر duplicated.",
425425
"loopRunningForButton": "التكرار يعمل لهذا الزر.",
426426
"loopTip": "ملاحظة: Use Loop إلى repeat until you stop it.",
427+
"loopingBadge": "Looping",
427428
"codeCopied": "Code copied.",
428429
"copyCode": "نسخ code",
429430
"startLoop": "بدء loop",
@@ -1030,6 +1031,11 @@
10301031
"noSignalInfo": "No signal info",
10311032
"proceed": "Proceed",
10321033
"discard": "Discard",
1034+
"continueEditing": "Continue editing",
1035+
"unsavedChangesTitle": "Unsaved changes",
1036+
"unsavedMacroChangesMessage": "Discard your macro changes and leave this screen?",
1037+
"stopMacroBeforeLeaving": "Stop the macro before leaving this screen.",
1038+
"stopTestingBeforeLeaving": "Stop testing before leaving this screen.",
10331039
"idle": "خامل",
10341040
"start": "بدء",
10351041
"resume": "استئناف",
@@ -1863,5 +1869,174 @@
18631869
"type": "int"
18641870
}
18651871
}
1872+
},
1873+
"continueSectionTitle": "Continue",
1874+
"@continueSectionTitle": {
1875+
"description": "Title for the recent continue strip on the remotes screen."
1876+
},
1877+
"continueSectionSubtitle": "Pick up where you left off.",
1878+
"@continueSectionSubtitle": {
1879+
"description": "Subtitle for the continue strip on the remotes screen."
1880+
},
1881+
"continueLastRemoteTitle": "Last remote",
1882+
"@continueLastRemoteTitle": {
1883+
"description": "Eyebrow label for the last opened remote card."
1884+
},
1885+
"continueLastMacroTitle": "Last macro",
1886+
"@continueLastMacroTitle": {
1887+
"description": "Eyebrow label for the last run macro card."
1888+
},
1889+
"continueLastIrFinderHitTitle": "Last IR Finder hit",
1890+
"@continueLastIrFinderHitTitle": {
1891+
"description": "Eyebrow label for the last saved IR Finder result card."
1892+
},
1893+
"continueTargetUnavailable": "That item is no longer available.",
1894+
"@continueTargetUnavailable": {
1895+
"description": "Shown when a continue card points to a deleted or missing target."
1896+
},
1897+
"continueUniversalPowerAllBrands": "All brands",
1898+
"@continueUniversalPowerAllBrands": {
1899+
"description": "Fallback label when the saved universal power context has no selected brand."
1900+
},
1901+
"untitledMacro": "Untitled Macro",
1902+
"@untitledMacro": {
1903+
"description": "Fallback title for a macro without a saved name."
1904+
},
1905+
"pinnedRemotesTitle": "Pinned remotes",
1906+
"@pinnedRemotesTitle": {
1907+
"description": "Title for the pinned remotes section on the remotes screen."
1908+
},
1909+
"pinnedRemotesSubtitle": "Keep your most important remotes one tap away.",
1910+
"@pinnedRemotesSubtitle": {
1911+
"description": "Subtitle for the pinned remotes section on the remotes screen."
1912+
},
1913+
"recentlyUsedRemotesTitle": "Recently used",
1914+
"@recentlyUsedRemotesTitle": {
1915+
"description": "Title for the recently used remotes section on the remotes screen."
1916+
},
1917+
"recentlyUsedRemotesSubtitle": "Jump back into the remotes you opened most recently.",
1918+
"@recentlyUsedRemotesSubtitle": {
1919+
"description": "Subtitle for the recently used remotes section on the remotes screen."
1920+
},
1921+
"pinRemote": "Pin remote",
1922+
"@pinRemote": {
1923+
"description": "Action label to pin a remote for quick access."
1924+
},
1925+
"unpinRemote": "Unpin remote",
1926+
"@unpinRemote": {
1927+
"description": "Action label to remove a remote from the pinned section."
1928+
},
1929+
"pinRemoteSubtitle": "Keep this remote at the top for faster access.",
1930+
"@pinRemoteSubtitle": {
1931+
"description": "Subtitle shown under the pin/unpin remote action."
1932+
},
1933+
"remoteAddedToPinned": "Remote pinned.",
1934+
"@remoteAddedToPinned": {
1935+
"description": "Snackbar shown after pinning a remote."
1936+
},
1937+
"remoteRemovedFromPinned": "Remote removed from pinned.",
1938+
"@remoteRemovedFromPinned": {
1939+
"description": "Snackbar shown after unpinning a remote."
1940+
},
1941+
"homeDeviceControlsTitle": "Quick controls",
1942+
"@homeDeviceControlsTitle": {
1943+
"description": "Title for the compact device controls row on the home surface."
1944+
},
1945+
"homeDeviceControlsSubtitle": "Power, mute, and volume without opening a remote.",
1946+
"@homeDeviceControlsSubtitle": {
1947+
"description": "Subtitle for the compact device controls row when controls are configured."
1948+
},
1949+
"homeDeviceControlsEmptySubtitle": "Set up power, mute, and volume buttons in Device Controls.",
1950+
"@homeDeviceControlsEmptySubtitle": {
1951+
"description": "Subtitle for the compact device controls row when no matching controls are configured."
1952+
},
1953+
"showDeviceControlsOnHome": "Show quick controls on home",
1954+
"@showDeviceControlsOnHome": {
1955+
"description": "Settings toggle title for showing the compact device controls row on the home surface."
1956+
},
1957+
"showDeviceControlsOnHomeSubtitle": "Show the compact Power, Mute, and Volume row on the main screen.",
1958+
"@showDeviceControlsOnHomeSubtitle": {
1959+
"description": "Settings toggle subtitle for showing the compact device controls row on the home surface."
1960+
},
1961+
"homeDeviceControlsShown": "Quick controls shown on home.",
1962+
"@homeDeviceControlsShown": {
1963+
"description": "Snackbar shown when the home quick controls row is enabled."
1964+
},
1965+
"homeDeviceControlsHidden": "Quick controls hidden from home.",
1966+
"@homeDeviceControlsHidden": {
1967+
"description": "Snackbar shown when the home quick controls row is disabled."
1968+
},
1969+
"power": "Power",
1970+
"@power": {
1971+
"description": "Short label for the power control."
1972+
},
1973+
"mute": "Mute",
1974+
"@mute": {
1975+
"description": "Short label for the mute control."
1976+
},
1977+
"volumeUp": "Vol +",
1978+
"@volumeUp": {
1979+
"description": "Short label for the volume up control."
1980+
},
1981+
"volumeDown": "Vol -",
1982+
"@volumeDown": {
1983+
"description": "Short label for the volume down control."
1984+
},
1985+
"manage": "Manage",
1986+
"@manage": {
1987+
"description": "Short action label for opening a management screen."
1988+
},
1989+
"hide": "Hide",
1990+
"@hide": {
1991+
"description": "Short action label for hiding a surface or section."
1992+
},
1993+
"lastActionTitle": "Last action",
1994+
"@lastActionTitle": {
1995+
"description": "Title for the compact last action strip shown after sending IR."
1996+
},
1997+
"lastActionSent": "Sent {title}",
1998+
"@lastActionSent": {
1999+
"description": "Compact message in the last action strip when only the button title is known.",
2000+
"placeholders": {
2001+
"title": {
2002+
"type": "String"
2003+
}
2004+
}
2005+
},
2006+
"lastActionSentTo": "Sent {remoteName} -> {title}",
2007+
"@lastActionSentTo": {
2008+
"description": "Compact message in the last action strip when both remote name and button title are known.",
2009+
"placeholders": {
2010+
"remoteName": {
2011+
"type": "String"
2012+
},
2013+
"title": {
2014+
"type": "String"
2015+
}
2016+
}
2017+
},
2018+
"repeatAction": "Repeat",
2019+
"@repeatAction": {
2020+
"description": "Short action label to resend the last IR action."
2021+
},
2022+
"globalSearchTitle": "Search everything",
2023+
"@globalSearchTitle": {
2024+
"description": "Title or tooltip for global search across remotes, buttons, and macros."
2025+
},
2026+
"globalSearchNoResults": "No results found.",
2027+
"@globalSearchNoResults": {
2028+
"description": "Empty state message for global search."
2029+
},
2030+
"globalSearchTypeRemote": "REMOTE",
2031+
"@globalSearchTypeRemote": {
2032+
"description": "Badge label for a remote search result."
2033+
},
2034+
"globalSearchTypeButton": "BUTTON",
2035+
"@globalSearchTypeButton": {
2036+
"description": "Badge label for a button search result."
2037+
},
2038+
"globalSearchTypeMacro": "MACRO",
2039+
"@globalSearchTypeMacro": {
2040+
"description": "Badge label for a macro search result."
18662041
}
18672042
}

0 commit comments

Comments
 (0)