aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHarsh Shandilya <me@msfjarvis.dev>2021-04-21 18:07:35 +0530
committerGitHub <noreply@github.com>2021-04-21 18:07:35 +0530
commit6ff01f5e1ee90c6203bc9bd0189eea33c42d6db0 (patch)
treeab6300f076404f202b4e4fd378c8c8fd70b03957
parent53c3431ef0d1d5cacadf2ae0d540a6b8189029d4 (diff)
Refactor app shortcut handling (#1392)
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordStore.kt61
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/util/shortcuts/ShortcutHandler.kt106
2 files changed, 112 insertions, 55 deletions
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordStore.kt b/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordStore.kt
index 8717d199..3339fc04 100644
--- a/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordStore.kt
+++ b/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordStore.kt
@@ -8,11 +8,6 @@ import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
-import android.content.pm.ShortcutInfo
-import android.content.pm.ShortcutInfo.Builder
-import android.content.pm.ShortcutManager
-import android.graphics.drawable.Icon
-import android.os.Build
import android.os.Bundle
import android.view.KeyEvent
import android.view.Menu
@@ -21,11 +16,9 @@ import android.view.MenuItem.OnActionExpandListener
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.activity.viewModels
-import androidx.annotation.RequiresApi
import androidx.appcompat.widget.SearchView
import androidx.appcompat.widget.SearchView.OnQueryTextListener
import androidx.core.content.edit
-import androidx.core.content.getSystemService
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.commit
import androidx.lifecycle.ViewModelProvider
@@ -40,6 +33,7 @@ import com.github.michaelbull.result.onFailure
import com.github.michaelbull.result.runCatching
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputEditText
+import dagger.hilt.android.AndroidEntryPoint
import dev.msfjarvis.aps.R
import dev.msfjarvis.aps.data.password.PasswordItem
import dev.msfjarvis.aps.data.repo.PasswordRepository
@@ -67,9 +61,11 @@ import dev.msfjarvis.aps.util.extensions.sharedPrefs
import dev.msfjarvis.aps.util.settings.AuthMode
import dev.msfjarvis.aps.util.settings.GitSettings
import dev.msfjarvis.aps.util.settings.PreferenceKeys
+import dev.msfjarvis.aps.util.shortcuts.ShortcutHandler
import dev.msfjarvis.aps.util.viewmodel.SearchableRepositoryViewModel
import java.io.File
import java.lang.Character.UnicodeBlock
+import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -77,8 +73,10 @@ import org.eclipse.jgit.api.Git
const val PASSWORD_FRAGMENT_TAG = "PasswordsList"
+@AndroidEntryPoint
class PasswordStore : BaseGitActivity() {
+ @Inject lateinit var shortcutHandler: ShortcutHandler
private lateinit var searchItem: MenuItem
private val settings by lazy { sharedPrefs }
@@ -440,50 +438,7 @@ class PasswordStore : BaseGitActivity() {
startActivity(decryptIntent)
// Adds shortcut
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
- addShortcut(item, authDecryptIntent)
- }
- }
-
- @RequiresApi(Build.VERSION_CODES.N_MR1)
- private fun addShortcut(item: PasswordItem, intent: Intent) {
- val shortcutManager: ShortcutManager = getSystemService() ?: return
- val shortcut =
- Builder(this, item.fullPathToParent)
- .setShortLabel(item.toString())
- .setLongLabel(item.fullPathToParent + item.toString())
- .setIcon(Icon.createWithResource(this, R.drawable.ic_lock_open_24px))
- .setIntent(intent)
- .build()
- val shortcuts = shortcutManager.dynamicShortcuts
- // If we're above or equal to the maximum shortcuts allowed, drop the last item.
- if (shortcuts.size >= MAX_SHORTCUT_COUNT) {
- shortcuts.removeLast()
- }
- // Reverse the list so we can append our new shortcut at the 'end'.
- shortcuts.reverse()
- shortcuts.add(shortcut)
- // Reverse it again, so the previous items are now in the correct order and our new item
- // is at the front like it's supposed to.
- shortcuts.reverse()
- // Write back the new shortcuts.
- shortcutManager.dynamicShortcuts = shortcuts.map(::rebuildShortcut)
- }
-
- /**
- * Takes an existing [ShortcutInfo] and builds a fresh instance of [ShortcutInfo] with the same
- * data, which ensures that the get/set dance in [addShortcut] does not cause invalidation of icon
- * assets, resulting in invisible icons in all but the newest launcher shortcut.
- */
- @RequiresApi(Build.VERSION_CODES.N_MR1)
- private fun rebuildShortcut(shortcut: ShortcutInfo): ShortcutInfo {
- // Non-null assertions are fine since we know these values aren't null.
- return Builder(this@PasswordStore, shortcut.id)
- .setShortLabel(shortcut.shortLabel!!)
- .setLongLabel(shortcut.longLabel!!)
- .setIcon(Icon.createWithResource(this@PasswordStore, R.drawable.ic_lock_open_24px))
- .setIntent(shortcut.intent!!)
- .build()
+ shortcutHandler.addDynamicShortcut(item, authDecryptIntent)
}
private fun validateState(): Boolean {
@@ -693,10 +648,6 @@ class PasswordStore : BaseGitActivity() {
companion object {
- // The max shortcut count from the system is set to 15 for some godforsaken reason, which
- // makes zero sense and is why our update logic just never worked. Capping it at 4 which is
- // what most launchers seem to have agreed upon is the only reasonable solution.
- private const val MAX_SHORTCUT_COUNT = 4
const val REQUEST_ARG_PATH = "PATH"
private fun isPrintable(c: Char): Boolean {
val block = UnicodeBlock.of(c)
diff --git a/app/src/main/java/dev/msfjarvis/aps/util/shortcuts/ShortcutHandler.kt b/app/src/main/java/dev/msfjarvis/aps/util/shortcuts/ShortcutHandler.kt
new file mode 100644
index 00000000..8e36c9af
--- /dev/null
+++ b/app/src/main/java/dev/msfjarvis/aps/util/shortcuts/ShortcutHandler.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package dev.msfjarvis.aps.util.shortcuts
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ShortcutInfo
+import android.content.pm.ShortcutManager
+import android.graphics.drawable.Icon
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.core.content.getSystemService
+import com.github.ajalt.timberkt.d
+import dagger.Reusable
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dev.msfjarvis.aps.R
+import dev.msfjarvis.aps.data.password.PasswordItem
+import javax.inject.Inject
+
+@Reusable
+class ShortcutHandler
+@Inject
+constructor(
+ @ApplicationContext val context: Context,
+) {
+
+ private companion object {
+
+ // The max shortcut count from the system is set to 15 for some godforsaken reason, which
+ // makes zero sense and is why our update logic just never worked. Capping it at 4 which is
+ // what most launchers seem to have agreed upon is the only reasonable solution.
+ private const val MAX_SHORTCUT_COUNT = 4
+ }
+
+ /**
+ * Creates a
+ * [dynamic shortcut](https://developer.android.com/guide/topics/ui/shortcuts/creating-shortcuts#dynamic)
+ * that shows up with the app icon on long press. The list of items is capped to
+ * [MAX_SHORTCUT_COUNT] and older items are removed by a simple LRU sweep.
+ */
+ fun addDynamicShortcut(item: PasswordItem, intent: Intent) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) return
+ val shortcutManager: ShortcutManager = context.getSystemService() ?: return
+ val shortcut = buildShortcut(item, intent)
+ val shortcuts = shortcutManager.dynamicShortcuts
+ // If we're above or equal to the maximum shortcuts allowed, drop the last item.
+ if (shortcuts.size >= MAX_SHORTCUT_COUNT) {
+ shortcuts.removeLast()
+ }
+ // Reverse the list so we can append our new shortcut at the 'end'.
+ shortcuts.reverse()
+ shortcuts.add(shortcut)
+ // Reverse it again, so the previous items are now in the correct order and our new item
+ // is at the front like it's supposed to.
+ shortcuts.reverse()
+ // Write back the new shortcuts.
+ shortcutManager.dynamicShortcuts = shortcuts.map(::rebuildShortcut)
+ }
+
+ /**
+ * Creates a
+ * [pinned shortcut](https://developer.android.com/guide/topics/ui/shortcuts/creating-shortcuts#pinned)
+ * which presents a UI to users, allowing manual placement on the launcher screen. This method is
+ * a no-op if the user's default launcher does not support pinned shortcuts.
+ */
+ fun addPinnedShortcut(item: PasswordItem, intent: Intent) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
+ val shortcutManager: ShortcutManager = context.getSystemService() ?: return
+ if (!shortcutManager.isRequestPinShortcutSupported) {
+ d { "addPinnedShortcut: pin shortcuts unsupported" }
+ return
+ }
+ val shortcut = buildShortcut(item, intent)
+ shortcutManager.requestPinShortcut(shortcut, null)
+ }
+
+ /** Creates a [ShortcutInfo] from [item] and assigns [intent] to it. */
+ @RequiresApi(Build.VERSION_CODES.N_MR1)
+ private fun buildShortcut(item: PasswordItem, intent: Intent): ShortcutInfo {
+ return ShortcutInfo.Builder(context, item.fullPathToParent)
+ .setShortLabel(item.toString())
+ .setLongLabel(item.fullPathToParent + item.toString())
+ .setIcon(Icon.createWithResource(context, R.drawable.ic_lock_open_24px))
+ .setIntent(intent)
+ .build()
+ }
+
+ /**
+ * Takes an existing [ShortcutInfo] and builds a fresh instance of [ShortcutInfo] with the same
+ * data, which ensures that the get/set dance in [addDynamicShortcut] does not cause invalidation
+ * of icon assets, resulting in invisible icons in all but the newest launcher shortcut.
+ */
+ @RequiresApi(Build.VERSION_CODES.N_MR1)
+ private fun rebuildShortcut(shortcut: ShortcutInfo): ShortcutInfo {
+ // Non-null assertions are fine since we know these values aren't null.
+ return ShortcutInfo.Builder(context, shortcut.id)
+ .setShortLabel(shortcut.shortLabel!!)
+ .setLongLabel(shortcut.longLabel!!)
+ .setIcon(Icon.createWithResource(context, R.drawable.ic_lock_open_24px))
+ .setIntent(shortcut.intent!!)
+ .build()
+ }
+}