From 5340851755b59ce267347b6e20393f79d5a2406e Mon Sep 17 00:00:00 2001 From: Gergely Sallai Date: Sun, 17 Jul 2022 10:42:12 +0200 Subject: [PATCH 1/6] Add androidx shortcuts library to UI module --- build.gradle | 1 + ui/build.gradle | 1 + 2 files changed, 2 insertions(+) diff --git a/build.gradle b/build.gradle index 4aa755abc..9b0064bde 100644 --- a/build.gradle +++ b/build.gradle @@ -18,6 +18,7 @@ buildscript { materialComponentsVersion = '1.6.1' preferenceVersion = '1.2.0' zxingEmbeddedVersion = '4.3.0' + shortcutsVersion = '1.0.0' groupName = 'com.wireguard.android' } diff --git a/ui/build.gradle b/ui/build.gradle index b48f01211..15ac93f60 100644 --- a/ui/build.gradle +++ b/ui/build.gradle @@ -71,6 +71,7 @@ dependencies { implementation "androidx.coordinatorlayout:coordinatorlayout:$coordinatorLayoutVersion" implementation "androidx.biometric:biometric:$biometricVersion" implementation "androidx.core:core-ktx:$coreKtxVersion" + implementation "androidx.core:core-google-shortcuts:$shortcutsVersion" implementation "androidx.fragment:fragment-ktx:$fragmentVersion" implementation "androidx.preference:preference-ktx:$preferenceVersion" implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleRuntimeKtxVersion" From 1fc1b9f007d85eb29912dd1a8cb517df54ac408a Mon Sep 17 00:00:00 2001 From: Gergely Sallai Date: Sun, 17 Jul 2022 10:43:55 +0200 Subject: [PATCH 2/6] Add icons and transitions for "add shortcuts" button --- .../main/res/drawable/avd_star_to_border.xml | 22 ++++++++++++++++ ui/src/main/res/drawable/avd_star_to_full.xml | 22 ++++++++++++++++ .../main/res/drawable/ic_action_shortcut.xml | 26 +++++++++++++++++++ .../ic_baseline_arrow_circle_down_24.xml | 5 ++++ .../ic_baseline_arrow_circle_up_24.xml | 5 ++++ ui/src/main/res/drawable/ic_baseline_star.xml | 17 ++++++++++++ .../res/drawable/ic_baseline_star_border.xml | 16 ++++++++++++ ui/src/main/res/values/pathdata.xml | 9 +++++++ 8 files changed, 122 insertions(+) create mode 100644 ui/src/main/res/drawable/avd_star_to_border.xml create mode 100644 ui/src/main/res/drawable/avd_star_to_full.xml create mode 100644 ui/src/main/res/drawable/ic_action_shortcut.xml create mode 100644 ui/src/main/res/drawable/ic_baseline_arrow_circle_down_24.xml create mode 100644 ui/src/main/res/drawable/ic_baseline_arrow_circle_up_24.xml create mode 100644 ui/src/main/res/drawable/ic_baseline_star.xml create mode 100644 ui/src/main/res/drawable/ic_baseline_star_border.xml create mode 100644 ui/src/main/res/values/pathdata.xml diff --git a/ui/src/main/res/drawable/avd_star_to_border.xml b/ui/src/main/res/drawable/avd_star_to_border.xml new file mode 100644 index 000000000..9bf8bedbc --- /dev/null +++ b/ui/src/main/res/drawable/avd_star_to_border.xml @@ -0,0 +1,22 @@ + + + + + + + + + + diff --git a/ui/src/main/res/drawable/avd_star_to_full.xml b/ui/src/main/res/drawable/avd_star_to_full.xml new file mode 100644 index 000000000..011c8957d --- /dev/null +++ b/ui/src/main/res/drawable/avd_star_to_full.xml @@ -0,0 +1,22 @@ + + + + + + + + + + diff --git a/ui/src/main/res/drawable/ic_action_shortcut.xml b/ui/src/main/res/drawable/ic_action_shortcut.xml new file mode 100644 index 000000000..a7bc85f72 --- /dev/null +++ b/ui/src/main/res/drawable/ic_action_shortcut.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/ui/src/main/res/drawable/ic_baseline_arrow_circle_down_24.xml b/ui/src/main/res/drawable/ic_baseline_arrow_circle_down_24.xml new file mode 100644 index 000000000..772735f00 --- /dev/null +++ b/ui/src/main/res/drawable/ic_baseline_arrow_circle_down_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/ui/src/main/res/drawable/ic_baseline_arrow_circle_up_24.xml b/ui/src/main/res/drawable/ic_baseline_arrow_circle_up_24.xml new file mode 100644 index 000000000..b9950afa2 --- /dev/null +++ b/ui/src/main/res/drawable/ic_baseline_arrow_circle_up_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/ui/src/main/res/drawable/ic_baseline_star.xml b/ui/src/main/res/drawable/ic_baseline_star.xml new file mode 100644 index 000000000..94ec88cd5 --- /dev/null +++ b/ui/src/main/res/drawable/ic_baseline_star.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/ui/src/main/res/drawable/ic_baseline_star_border.xml b/ui/src/main/res/drawable/ic_baseline_star_border.xml new file mode 100644 index 000000000..0d8b535f4 --- /dev/null +++ b/ui/src/main/res/drawable/ic_baseline_star_border.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/ui/src/main/res/values/pathdata.xml b/ui/src/main/res/values/pathdata.xml new file mode 100644 index 000000000..111c9f7ee --- /dev/null +++ b/ui/src/main/res/values/pathdata.xml @@ -0,0 +1,9 @@ + + + + M 12 17.27 L 18.18 21 L 16.54 13.97 L 19.27 11.605 L 22 9.24 L 14.81 8.63 L 12 2 L 9.19 8.63 L 2 9.24 L 7.46 13.97 L 5.82 21 L 12 17.27 M 12.005 12.495 L 12.005 12.495 L 12.005 12.495 L 12.005 12.495 L 12.005 12.495 L 12.005 12.495 L 12.005 12.495 L 12.005 12.495 L 12.005 12.495 L 12.005 12.495 L 12.005 12.495 L 12.005 12.495 + M 12 17.27 L 18.18 21 L 16.55 13.97 L 22 9.24 L 22 9.24 L 14.81 8.62 L 12 2 L 9.19 8.63 L 2 9.24 L 7.46 13.97 L 5.82 21 L 12 17.27 M 12 15.4 L 8.24 17.67 L 9.24 13.39 L 5.92 10.51 L 10.3 10.13 L 12 6.1 L 13.71 10.14 L 18.09 10.52 L 14.77 13.4 L 15.77 17.68 L 12 15.4 L 12 15.4 + \ No newline at end of file From 298f1dba8f77e95756d9ed04caee31fb530b96fd Mon Sep 17 00:00:00 2001 From: Gergely Sallai Date: Sun, 17 Jul 2022 11:17:06 +0200 Subject: [PATCH 3/6] Introduce basics of adding dynamic shortcuts: - `tunnel_list_item` got a new checkbox to toggle shortcut display for specific tunnel - `ObservableTunnel` got updated to support querying and setting shortcuts. Responsibility delegated to `TunnelManager` - `TunnelManager` got updated to handle shortcuts when a tunnel changes. Actual shortcut logic will be handled by `ShortcutManager` - Added skeleton for `ShortcutManager` - `strings.xml` updated with tooltip text for the shortcut checkbox. This tooltip only works for API 26+ --- ui/sampledata/interface_names.json | 13 ++++++++++ .../android/fragment/BaseFragment.kt | 13 ++++++++++ .../android/model/ObservableTunnel.kt | 18 +++++++++++++ .../android/model/ShortcutManager.kt | 25 ++++++++++++++++++ .../wireguard/android/model/TunnelManager.kt | 26 +++++++++++++++++++ ui/src/main/res/layout/tunnel_list_item.xml | 16 +++++++++++- ui/src/main/res/values/strings.xml | 1 + 7 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 ui/src/main/java/com/wireguard/android/model/ShortcutManager.kt diff --git a/ui/sampledata/interface_names.json b/ui/sampledata/interface_names.json index 1c41cb224..52bb652c7 100644 --- a/ui/sampledata/interface_names.json +++ b/ui/sampledata/interface_names.json @@ -28,6 +28,19 @@ { "checked": true }, { "checked": false }, { "checked": true } + ], + "shortcut": [ + { "shortcut": true }, + { "shortcut": false }, + { "shortcut": false }, + { "shortcut": false }, + { "shortcut": false }, + { "shortcut": false }, + { "shortcut": true }, + { "shortcut": true }, + { "shortcut": true }, + { "shortcut": false }, + { "shortcut": false } ] } ] diff --git a/ui/src/main/java/com/wireguard/android/fragment/BaseFragment.kt b/ui/src/main/java/com/wireguard/android/fragment/BaseFragment.kt index 783f5722b..bea23d647 100644 --- a/ui/src/main/java/com/wireguard/android/fragment/BaseFragment.kt +++ b/ui/src/main/java/com/wireguard/android/fragment/BaseFragment.kt @@ -7,6 +7,7 @@ package com.wireguard.android.fragment import android.content.Context import android.util.Log import android.view.View +import android.widget.CompoundButton import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.databinding.DataBindingUtil @@ -100,6 +101,18 @@ abstract class BaseFragment : Fragment(), OnSelectedTunnelChangedListener { } } + fun setShortcutState(view: CompoundButton, checked: Boolean) { + val tunnel = when (val binding = DataBindingUtil.findBinding(view)) { + is TunnelDetailFragmentBinding -> binding.tunnel + is TunnelListItemBinding -> binding.item + else -> return + } ?: return + val activity = activity ?: return + activity.lifecycleScope.launch { + tunnel.setShortcutsAsync(checked) + } + } + companion object { private const val TAG = "WireGuard/BaseFragment" } diff --git a/ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt b/ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt index 252e87597..9353a79d7 100644 --- a/ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt +++ b/ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt @@ -139,6 +139,24 @@ class ObservableTunnel internal constructor( suspend fun deleteAsync() = manager.delete(this) + suspend fun setShortcutsAsync(hasShortcuts: Boolean) = manager.setShortcuts(this, hasShortcuts) + + @get:Bindable + var hasShortcut: Boolean? = null + get() { + if (field == null) + // Opportunistically fetch this if we don't have a cached one, and rely on data bindings to update it eventually + applicationScope.launch { + try { + field = manager.hasShortcut(this@ObservableTunnel) + } catch (e: Throwable) { + Log.e(TAG, Log.getStackTraceString(e)) + } + } + return field + } + private set + companion object { private const val TAG = "WireGuard/ObservableTunnel" diff --git a/ui/src/main/java/com/wireguard/android/model/ShortcutManager.kt b/ui/src/main/java/com/wireguard/android/model/ShortcutManager.kt new file mode 100644 index 000000000..38bac7903 --- /dev/null +++ b/ui/src/main/java/com/wireguard/android/model/ShortcutManager.kt @@ -0,0 +1,25 @@ +/* + * Copyright © 2017-2022 WireGuard LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.wireguard.android.model + +import android.content.Context +import androidx.core.content.pm.ShortcutManagerCompat + +class ShortcutManager(private val context: Context) { + + private fun upIdFor(name: String): String = "$name-UP" + private fun downIdFor(name: String): String = "$name-DOWN" + + fun addShortcuts(name: String) { + } + + fun removeShortcuts(name: String) { + ShortcutManagerCompat.removeDynamicShortcuts(context, listOf(upIdFor(name), downIdFor(name))) + } + + fun hasShortcut(name: String) = + ShortcutManagerCompat.getDynamicShortcuts(context).any { it.id.startsWith(name) } +} \ No newline at end of file diff --git a/ui/src/main/java/com/wireguard/android/model/TunnelManager.kt b/ui/src/main/java/com/wireguard/android/model/TunnelManager.kt index ec7961645..1022a0dde 100644 --- a/ui/src/main/java/com/wireguard/android/model/TunnelManager.kt +++ b/ui/src/main/java/com/wireguard/android/model/TunnelManager.kt @@ -41,6 +41,7 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() { private val tunnels = CompletableDeferred>() private val context: Context = get() private val tunnelMap: ObservableSortedKeyedArrayList = ObservableSortedKeyedArrayList(TunnelComparator) + private val shortcutManager = ShortcutManager(context) private var haveLoaded = false private fun addToList(name: String, config: Config?, state: Tunnel.State): ObservableTunnel { @@ -65,6 +66,10 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() { // Make sure nothing touches the tunnel. if (wasLastUsed) lastUsedTunnel = null + // Make sure we also remove any existing shortcuts. + if (shortcutManager.hasShortcut(tunnel.name)) { + shortcutManager.removeShortcuts(tunnel.name) + } tunnelMap.remove(tunnel) try { if (originalState == Tunnel.State.UP) @@ -169,6 +174,11 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() { // Make sure nothing touches the tunnel. if (wasLastUsed) lastUsedTunnel = null + // Make sure we also remove any existing shortcuts. We will add them back with the new name. + val hadShortcuts = shortcutManager.hasShortcut(tunnel.name) + if (hadShortcuts) { + shortcutManager.removeShortcuts(tunnel.name) + } tunnelMap.remove(tunnel) var throwable: Throwable? = null var newName: String? = null @@ -188,6 +198,10 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() { tunnelMap.add(tunnel) if (wasLastUsed) lastUsedTunnel = tunnel + // Add back previous shortcuts with the new name (if any). + if (hadShortcuts) { + shortcutManager.addShortcuts(tunnel.name) + } if (throwable != null) throw throwable newName!! @@ -210,6 +224,18 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() { newState } + suspend fun setShortcuts(tunnel: ObservableTunnel, hasShortcuts: Boolean) = withContext(Dispatchers.Main.immediate) { + if (hasShortcuts) { + shortcutManager.addShortcuts(tunnel.name) + } else { + shortcutManager.removeShortcuts(tunnel.name) + } + } + + suspend fun hasShortcut(tunnel: ObservableTunnel) = withContext(Dispatchers.Main.immediate) { + return@withContext shortcutManager.hasShortcut(tunnel.name) + } + class IntentReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent?) { applicationScope.launch { diff --git a/ui/src/main/res/layout/tunnel_list_item.xml b/ui/src/main/res/layout/tunnel_list_item.xml index 9c9517a7d..e3baf99bf 100644 --- a/ui/src/main/res/layout/tunnel_list_item.xml +++ b/ui/src/main/res/layout/tunnel_list_item.xml @@ -33,7 +33,7 @@ android:background="@drawable/list_item_background" android:descendantFocusability="beforeDescendants" android:focusable="true" - android:nextFocusRight="@+id/tunnel_switch" + android:nextFocusRight="@+id/tunnel_shortcut_toggle" android:padding="16dp"> + + Authenticate to view private key Authentication failure Authentication failure: %s + Add shortcuts From 1175e4d0cadf22af5af0e89d3a1420590b059c5a Mon Sep 17 00:00:00 2001 From: Gergely Sallai Date: Sun, 17 Jul 2022 13:35:06 +0200 Subject: [PATCH 4/6] Implement logic for on-demand dynamic shortcut addition. - `ShortcutManager` now able to add 2 kinds of dynamic shortcuts for a specific tunnel (tunnel up and tunnel down). - Modified `TunnelToggleActivity` so that it can be called from the shortcuts. Instead of only being able to toggle last used tunnel, now it can be instructed to 'up' or 'down' any specified tunnel. --- .../android/activity/TunnelToggleActivity.kt | 14 +++++++-- .../android/model/ShortcutManager.kt | 30 +++++++++++++++++++ ui/src/main/res/values/strings.xml | 4 +++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/ui/src/main/java/com/wireguard/android/activity/TunnelToggleActivity.kt b/ui/src/main/java/com/wireguard/android/activity/TunnelToggleActivity.kt index ebf059a50..6a3252e3d 100644 --- a/ui/src/main/java/com/wireguard/android/activity/TunnelToggleActivity.kt +++ b/ui/src/main/java/com/wireguard/android/activity/TunnelToggleActivity.kt @@ -27,10 +27,20 @@ class TunnelToggleActivity : AppCompatActivity() { private val permissionActivityResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { toggleTunnelWithPermissionsResult() } private fun toggleTunnelWithPermissionsResult() { - val tunnel = Application.getTunnelManager().lastUsedTunnel ?: return lifecycleScope.launch { + val tunnelAction = when(intent.action) { + "com.wireguard.android.action.SET_TUNNEL_UP" -> Tunnel.State.UP + "com.wireguard.android.action.SET_TUNNEL_DOWN" -> Tunnel.State.DOWN + else -> Tunnel.State.TOGGLE // Implicit toggle to keep previous behaviour + } + + val tunnel = when(val tunnelName = intent.getStringExtra("tunnel")) { + null -> Application.getTunnelManager().lastUsedTunnel + else -> Application.getTunnelManager().getTunnels().find { it.name == tunnelName } + } ?: return@launch // If we failed to identify the tunnel, just return + try { - tunnel.setStateAsync(Tunnel.State.TOGGLE) + tunnel.setStateAsync(tunnelAction) } catch (e: Throwable) { TileService.requestListeningState(this@TunnelToggleActivity, ComponentName(this@TunnelToggleActivity, QuickTileService::class.java)) val error = ErrorMessages[e] diff --git a/ui/src/main/java/com/wireguard/android/model/ShortcutManager.kt b/ui/src/main/java/com/wireguard/android/model/ShortcutManager.kt index 38bac7903..b1c821704 100644 --- a/ui/src/main/java/com/wireguard/android/model/ShortcutManager.kt +++ b/ui/src/main/java/com/wireguard/android/model/ShortcutManager.kt @@ -6,14 +6,44 @@ package com.wireguard.android.model import android.content.Context +import android.content.Intent +import androidx.core.content.pm.ShortcutInfoCompat import androidx.core.content.pm.ShortcutManagerCompat +import androidx.core.graphics.drawable.IconCompat +import com.wireguard.android.BuildConfig +import com.wireguard.android.R +import com.wireguard.android.activity.TunnelToggleActivity class ShortcutManager(private val context: Context) { private fun upIdFor(name: String): String = "$name-UP" private fun downIdFor(name: String): String = "$name-DOWN" + private fun createShortcutIntent(action: String, tunnelName: String): Intent = + Intent(context, TunnelToggleActivity::class.java).apply { + setPackage(BuildConfig.APPLICATION_ID) + setAction(action) + putExtra("tunnel", tunnelName) + } + fun addShortcuts(name: String) { + val upIntent = createShortcutIntent("com.wireguard.android.action.SET_TUNNEL_UP", name) + val shortcutUp = ShortcutInfoCompat.Builder(context, upIdFor(name)) + .setShortLabel(context.getString(R.string.shortcut_label_short_up, name)) + .setLongLabel(context.getString(R.string.shortcut_label_long_up, name)) + .setIcon(IconCompat.createWithResource(context, R.drawable.ic_baseline_arrow_circle_up_24)) + .setIntent(upIntent) + .build() + ShortcutManagerCompat.pushDynamicShortcut(context, shortcutUp) + + val downIntent = createShortcutIntent("com.wireguard.android.action.SET_TUNNEL_DOWN", name) + val shortcutDown = ShortcutInfoCompat.Builder(context, downIdFor(name)) + .setShortLabel(context.getString(R.string.shortcut_label_short_down, name)) + .setLongLabel(context.getString(R.string.shortcut_label_long_down, name)) + .setIcon(IconCompat.createWithResource(context, R.drawable.ic_baseline_arrow_circle_down_24)) + .setIntent(downIntent) + .build() + ShortcutManagerCompat.pushDynamicShortcut(context, shortcutDown) } fun removeShortcuts(name: String) { diff --git a/ui/src/main/res/values/strings.xml b/ui/src/main/res/values/strings.xml index 4cc4e97ea..3afd794d2 100644 --- a/ui/src/main/res/values/strings.xml +++ b/ui/src/main/res/values/strings.xml @@ -238,4 +238,8 @@ Authentication failure Authentication failure: %s Add shortcuts + %s UP + Tunnel %s up + %s DOWN + Tunnel %s down From c49c0fa8c5efcfb6812cc49bf4d64083b497dfa2 Mon Sep 17 00:00:00 2001 From: Gergely Sallai Date: Sun, 17 Jul 2022 13:38:50 +0200 Subject: [PATCH 5/6] Make TunnelToggleActivity generally available. Previously it was API 24+, because TileService is only available there. But now `TunnelToggleActivity` can also be used from shortcuts, meaning we need it to work on older APIs too. --- .../android/activity/TunnelToggleActivity.kt | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/ui/src/main/java/com/wireguard/android/activity/TunnelToggleActivity.kt b/ui/src/main/java/com/wireguard/android/activity/TunnelToggleActivity.kt index 6a3252e3d..37eb8037b 100644 --- a/ui/src/main/java/com/wireguard/android/activity/TunnelToggleActivity.kt +++ b/ui/src/main/java/com/wireguard/android/activity/TunnelToggleActivity.kt @@ -22,7 +22,6 @@ import com.wireguard.android.backend.Tunnel import com.wireguard.android.util.ErrorMessages import kotlinx.coroutines.launch -@RequiresApi(Build.VERSION_CODES.N) class TunnelToggleActivity : AppCompatActivity() { private val permissionActivityResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { toggleTunnelWithPermissionsResult() } @@ -42,7 +41,7 @@ class TunnelToggleActivity : AppCompatActivity() { try { tunnel.setStateAsync(tunnelAction) } catch (e: Throwable) { - TileService.requestListeningState(this@TunnelToggleActivity, ComponentName(this@TunnelToggleActivity, QuickTileService::class.java)) + updateTileService() val error = ErrorMessages[e] val message = getString(R.string.toggle_error, error) Log.e(TAG, message, e) @@ -50,11 +49,21 @@ class TunnelToggleActivity : AppCompatActivity() { finishAffinity() return@launch } - TileService.requestListeningState(this@TunnelToggleActivity, ComponentName(this@TunnelToggleActivity, QuickTileService::class.java)) + updateTileService() finishAffinity() } } + /** + * TileService is only available for API 24+, if it's available it'll be updated, + * otherwise it's ignored. + */ + private fun updateTileService() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + TileService.requestListeningState(this@TunnelToggleActivity, ComponentName(this@TunnelToggleActivity, QuickTileService::class.java)) + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lifecycleScope.launch { From e065a21a6149f14994254b65c20169a272b40803 Mon Sep 17 00:00:00 2001 From: Gergely Sallai Date: Sun, 17 Jul 2022 16:36:39 +0200 Subject: [PATCH 6/6] Now using `colorSecondary` to match the design of the switch next to. Also, `colorOnPrimary` was "white" on my physical device (Samsung S21 API 31) and blended into the background, but looked fine in emulator (API29). I did not investigate further. The above should look the same as the ToggleSwitch next to it. As far as I could test this will look "secondary blue" on older devices, and adhere to Material You dynamic colors on newer devices. --- ui/src/main/res/layout/tunnel_list_item.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/main/res/layout/tunnel_list_item.xml b/ui/src/main/res/layout/tunnel_list_item.xml index e3baf99bf..1a761fad0 100644 --- a/ui/src/main/res/layout/tunnel_list_item.xml +++ b/ui/src/main/res/layout/tunnel_list_item.xml @@ -55,7 +55,7 @@ android:layout_alignBaseline="@+id/tunnel_name" android:layout_toStartOf="@id/tunnel_switch" android:button="@drawable/ic_action_shortcut" - app:buttonTint="?attr/colorOnPrimary" + app:buttonTint="?attr/colorSecondary" android:checked="@{item.hasShortcut == true}" android:onCheckedChanged="@{fragment::setShortcutState}" android:nextFocusRight="@+id/tunnel_switch"