Misc cleanups to build and extension functions (#1108)
This commit is contained in:
parent
9d63b11391
commit
bad8e2b404
11 changed files with 221 additions and 172 deletions
|
@ -141,9 +141,6 @@ dependencies {
|
||||||
androidTestImplementation(Dependencies.Testing.kotlin_test_junit)
|
androidTestImplementation(Dependencies.Testing.kotlin_test_junit)
|
||||||
androidTestImplementation(Dependencies.Testing.AndroidX.runner)
|
androidTestImplementation(Dependencies.Testing.AndroidX.runner)
|
||||||
androidTestImplementation(Dependencies.Testing.AndroidX.rules)
|
androidTestImplementation(Dependencies.Testing.AndroidX.rules)
|
||||||
androidTestImplementation(Dependencies.Testing.AndroidX.junit)
|
|
||||||
androidTestImplementation(Dependencies.Testing.AndroidX.espresso_core)
|
|
||||||
androidTestImplementation(Dependencies.Testing.AndroidX.espresso_intents)
|
|
||||||
|
|
||||||
testImplementation(Dependencies.Testing.junit)
|
testImplementation(Dependencies.Testing.junit)
|
||||||
testImplementation(Dependencies.Testing.kotlin_test_junit)
|
testImplementation(Dependencies.Testing.kotlin_test_junit)
|
||||||
|
|
|
@ -54,7 +54,7 @@ import com.zeapo.pwdstore.utils.BiometricAuthenticator
|
||||||
import com.zeapo.pwdstore.utils.PasswordRepository
|
import com.zeapo.pwdstore.utils.PasswordRepository
|
||||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||||
import com.zeapo.pwdstore.utils.autofillManager
|
import com.zeapo.pwdstore.utils.autofillManager
|
||||||
import com.zeapo.pwdstore.utils.getEncryptedPrefs
|
import com.zeapo.pwdstore.utils.getEncryptedGitPrefs
|
||||||
import com.zeapo.pwdstore.utils.getString
|
import com.zeapo.pwdstore.utils.getString
|
||||||
import com.zeapo.pwdstore.utils.sharedPrefs
|
import com.zeapo.pwdstore.utils.sharedPrefs
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
@ -81,7 +81,7 @@ class UserPreference : AppCompatActivity() {
|
||||||
prefsActivity = requireActivity() as UserPreference
|
prefsActivity = requireActivity() as UserPreference
|
||||||
val context = requireContext()
|
val context = requireContext()
|
||||||
sharedPreferences = preferenceManager.sharedPreferences
|
sharedPreferences = preferenceManager.sharedPreferences
|
||||||
encryptedPreferences = requireActivity().getEncryptedPrefs("git_operation")
|
encryptedPreferences = requireActivity().getEncryptedGitPrefs()
|
||||||
|
|
||||||
addPreferencesFromResource(R.xml.preference)
|
addPreferencesFromResource(R.xml.preference)
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ import com.zeapo.pwdstore.git.operation.PushOperation
|
||||||
import com.zeapo.pwdstore.git.operation.ResetToRemoteOperation
|
import com.zeapo.pwdstore.git.operation.ResetToRemoteOperation
|
||||||
import com.zeapo.pwdstore.git.operation.SyncOperation
|
import com.zeapo.pwdstore.git.operation.SyncOperation
|
||||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||||
import com.zeapo.pwdstore.utils.getEncryptedPrefs
|
import com.zeapo.pwdstore.utils.getEncryptedGitPrefs
|
||||||
import com.zeapo.pwdstore.utils.sharedPrefs
|
import com.zeapo.pwdstore.utils.sharedPrefs
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
@ -86,7 +86,7 @@ abstract class BaseGitActivity : AppCompatActivity() {
|
||||||
suspend fun promptOnErrorHandler(err: Throwable, onPromptDone: () -> Unit = {}) {
|
suspend fun promptOnErrorHandler(err: Throwable, onPromptDone: () -> Unit = {}) {
|
||||||
val error = rootCauseException(err)
|
val error = rootCauseException(err)
|
||||||
if (!isExplicitlyUserInitiatedError(error)) {
|
if (!isExplicitlyUserInitiatedError(error)) {
|
||||||
getEncryptedPrefs("git_operation").edit {
|
getEncryptedGitPrefs().edit {
|
||||||
remove(PreferenceKeys.HTTPS_PASSWORD)
|
remove(PreferenceKeys.HTTPS_PASSWORD)
|
||||||
}
|
}
|
||||||
sharedPrefs.edit { remove(PreferenceKeys.SSH_OPENKEYSTORE_KEYID) }
|
sharedPrefs.edit { remove(PreferenceKeys.SSH_OPENKEYSTORE_KEYID) }
|
||||||
|
|
|
@ -10,7 +10,7 @@ import com.github.michaelbull.result.runCatching
|
||||||
import com.zeapo.pwdstore.Application
|
import com.zeapo.pwdstore.Application
|
||||||
import com.zeapo.pwdstore.utils.PasswordRepository
|
import com.zeapo.pwdstore.utils.PasswordRepository
|
||||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||||
import com.zeapo.pwdstore.utils.getEncryptedPrefs
|
import com.zeapo.pwdstore.utils.getEncryptedGitPrefs
|
||||||
import com.zeapo.pwdstore.utils.getString
|
import com.zeapo.pwdstore.utils.getString
|
||||||
import com.zeapo.pwdstore.utils.sharedPrefs
|
import com.zeapo.pwdstore.utils.sharedPrefs
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
@ -53,7 +53,7 @@ object GitSettings {
|
||||||
private const val DEFAULT_BRANCH = "master"
|
private const val DEFAULT_BRANCH = "master"
|
||||||
|
|
||||||
private val settings by lazy { Application.instance.sharedPrefs }
|
private val settings by lazy { Application.instance.sharedPrefs }
|
||||||
private val encryptedSettings by lazy { Application.instance.getEncryptedPrefs("git_operation") }
|
private val encryptedSettings by lazy { Application.instance.getEncryptedGitPrefs() }
|
||||||
|
|
||||||
var authMode
|
var authMode
|
||||||
get() = AuthMode.fromString(settings.getString(PreferenceKeys.GIT_REMOTE_AUTH))
|
get() = AuthMode.fromString(settings.getString(PreferenceKeys.GIT_REMOTE_AUTH))
|
||||||
|
|
|
@ -14,7 +14,7 @@ import com.zeapo.pwdstore.R
|
||||||
import com.zeapo.pwdstore.git.config.AuthMode
|
import com.zeapo.pwdstore.git.config.AuthMode
|
||||||
import com.zeapo.pwdstore.git.sshj.InteractivePasswordFinder
|
import com.zeapo.pwdstore.git.sshj.InteractivePasswordFinder
|
||||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||||
import com.zeapo.pwdstore.utils.getEncryptedPrefs
|
import com.zeapo.pwdstore.utils.getEncryptedGitPrefs
|
||||||
import com.zeapo.pwdstore.utils.requestInputFocusOnView
|
import com.zeapo.pwdstore.utils.requestInputFocusOnView
|
||||||
import kotlin.coroutines.Continuation
|
import kotlin.coroutines.Continuation
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
|
@ -25,7 +25,7 @@ class CredentialFinder(
|
||||||
) : InteractivePasswordFinder() {
|
) : InteractivePasswordFinder() {
|
||||||
|
|
||||||
override fun askForPassword(cont: Continuation<String?>, isRetry: Boolean) {
|
override fun askForPassword(cont: Continuation<String?>, isRetry: Boolean) {
|
||||||
val gitOperationPrefs = callingActivity.getEncryptedPrefs("git_operation")
|
val gitOperationPrefs = callingActivity.getEncryptedGitPrefs()
|
||||||
val credentialPref: String
|
val credentialPref: String
|
||||||
@StringRes val messageRes: Int
|
@StringRes val messageRes: Int
|
||||||
@StringRes val hintRes: Int
|
@StringRes val hintRes: Int
|
||||||
|
|
|
@ -23,7 +23,7 @@ import com.github.michaelbull.result.runCatching
|
||||||
import com.zeapo.pwdstore.Application
|
import com.zeapo.pwdstore.Application
|
||||||
import com.zeapo.pwdstore.R
|
import com.zeapo.pwdstore.R
|
||||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||||
import com.zeapo.pwdstore.utils.getEncryptedPrefs
|
import com.zeapo.pwdstore.utils.getEncryptedGitPrefs
|
||||||
import com.zeapo.pwdstore.utils.getString
|
import com.zeapo.pwdstore.utils.getString
|
||||||
import com.zeapo.pwdstore.utils.sharedPrefs
|
import com.zeapo.pwdstore.utils.sharedPrefs
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
@ -169,7 +169,7 @@ object SshKey {
|
||||||
if (publicKeyFile.isFile) {
|
if (publicKeyFile.isFile) {
|
||||||
publicKeyFile.delete()
|
publicKeyFile.delete()
|
||||||
}
|
}
|
||||||
context.getEncryptedPrefs("git_operation").edit {
|
context.getEncryptedGitPrefs().edit {
|
||||||
remove(PreferenceKeys.SSH_KEY_LOCAL_PASSPHRASE)
|
remove(PreferenceKeys.SSH_KEY_LOCAL_PASSPHRASE)
|
||||||
}
|
}
|
||||||
type = null
|
type = null
|
||||||
|
|
|
@ -20,7 +20,7 @@ import com.zeapo.pwdstore.R
|
||||||
import com.zeapo.pwdstore.databinding.ActivitySshKeygenBinding
|
import com.zeapo.pwdstore.databinding.ActivitySshKeygenBinding
|
||||||
import com.zeapo.pwdstore.git.sshj.SshKey
|
import com.zeapo.pwdstore.git.sshj.SshKey
|
||||||
import com.zeapo.pwdstore.utils.BiometricAuthenticator
|
import com.zeapo.pwdstore.utils.BiometricAuthenticator
|
||||||
import com.zeapo.pwdstore.utils.getEncryptedPrefs
|
import com.zeapo.pwdstore.utils.getEncryptedGitPrefs
|
||||||
import com.zeapo.pwdstore.utils.keyguardManager
|
import com.zeapo.pwdstore.utils.keyguardManager
|
||||||
import com.zeapo.pwdstore.utils.viewBinding
|
import com.zeapo.pwdstore.utils.viewBinding
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
|
@ -128,7 +128,7 @@ class SshKeyGenActivity : AppCompatActivity() {
|
||||||
keyGenType.generateKey(requireAuthentication)
|
keyGenType.generateKey(requireAuthentication)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getEncryptedPrefs("git_operation").edit {
|
getEncryptedGitPrefs().edit {
|
||||||
remove("ssh_key_local_passphrase")
|
remove("ssh_key_local_passphrase")
|
||||||
}
|
}
|
||||||
binding.generate.apply {
|
binding.generate.apply {
|
||||||
|
|
172
app/src/main/java/com/zeapo/pwdstore/utils/AndroidExtensions.kt
Normal file
172
app/src/main/java/com/zeapo/pwdstore/utils/AndroidExtensions.kt
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.zeapo.pwdstore.utils
|
||||||
|
|
||||||
|
import android.app.KeyguardManager
|
||||||
|
import android.content.ClipboardManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
|
import android.util.Base64
|
||||||
|
import android.util.TypedValue
|
||||||
|
import android.view.View
|
||||||
|
import android.view.autofill.AutofillManager
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import androidx.annotation.IdRes
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.security.crypto.EncryptedSharedPreferences
|
||||||
|
import androidx.security.crypto.MasterKey
|
||||||
|
import com.github.ajalt.timberkt.d
|
||||||
|
import com.github.michaelbull.result.Ok
|
||||||
|
import com.github.michaelbull.result.Result
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import com.zeapo.pwdstore.R
|
||||||
|
import com.zeapo.pwdstore.git.operation.GitOperation
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension function for [AlertDialog] that requests focus for the
|
||||||
|
* view whose id is [id]. Solution based on a StackOverflow
|
||||||
|
* answer: https://stackoverflow.com/a/13056259/297261
|
||||||
|
*/
|
||||||
|
fun <T : View> AlertDialog.requestInputFocusOnView(@IdRes id: Int) {
|
||||||
|
setOnShowListener {
|
||||||
|
findViewById<T>(id)?.apply {
|
||||||
|
setOnFocusChangeListener { v, _ ->
|
||||||
|
v.post {
|
||||||
|
context.getSystemService<InputMethodManager>()
|
||||||
|
?.showSoftInput(v, InputMethodManager.SHOW_IMPLICIT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
requestFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an instance of [AutofillManager]. Only
|
||||||
|
* available on Android Oreo and above
|
||||||
|
*/
|
||||||
|
val Context.autofillManager: AutofillManager?
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
|
get() = getSystemService()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an instance of [ClipboardManager]
|
||||||
|
*/
|
||||||
|
val Context.clipboard
|
||||||
|
get() = getSystemService<ClipboardManager>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for [getEncryptedPrefs] to avoid open-coding the file name at
|
||||||
|
* each call site
|
||||||
|
*/
|
||||||
|
fun Context.getEncryptedGitPrefs() = getEncryptedPrefs("git_operation")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an instance of [EncryptedSharedPreferences] with the given [fileName]
|
||||||
|
*/
|
||||||
|
private fun Context.getEncryptedPrefs(fileName: String): SharedPreferences {
|
||||||
|
val masterKeyAlias = MasterKey.Builder(applicationContext)
|
||||||
|
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
||||||
|
.build()
|
||||||
|
return EncryptedSharedPreferences.create(
|
||||||
|
applicationContext,
|
||||||
|
fileName,
|
||||||
|
masterKeyAlias,
|
||||||
|
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||||
|
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an instance of [KeyguardManager]
|
||||||
|
*/
|
||||||
|
val Context.keyguardManager: KeyguardManager
|
||||||
|
get() = getSystemService()!!
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default [SharedPreferences] instance
|
||||||
|
*/
|
||||||
|
val Context.sharedPrefs: SharedPreferences
|
||||||
|
get() = PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve [attr] from the [Context]'s theme
|
||||||
|
*/
|
||||||
|
fun Context.resolveAttribute(attr: Int): Int {
|
||||||
|
val typedValue = TypedValue()
|
||||||
|
this.theme.resolveAttribute(attr, typedValue, true)
|
||||||
|
return typedValue.data
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Commit changes to the store from a [FragmentActivity] using
|
||||||
|
* a custom implementation of [GitOperation]
|
||||||
|
*/
|
||||||
|
suspend fun FragmentActivity.commitChange(
|
||||||
|
message: String,
|
||||||
|
): Result<Unit, Throwable> {
|
||||||
|
if (!PasswordRepository.isGitRepo()) {
|
||||||
|
return Ok(Unit)
|
||||||
|
}
|
||||||
|
return object : GitOperation(this@commitChange) {
|
||||||
|
override val commands = arrayOf(
|
||||||
|
// Stage all files
|
||||||
|
git.add().addFilepattern("."),
|
||||||
|
// Populate the changed files count
|
||||||
|
git.status(),
|
||||||
|
// Commit everything! If anything changed, that is.
|
||||||
|
git.commit().setAll(true).setMessage(message),
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun preExecute(): Boolean {
|
||||||
|
d { "Committing with message: '$message'" }
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}.execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if [permission] has been granted to the app.
|
||||||
|
*/
|
||||||
|
fun FragmentActivity.isPermissionGranted(permission: String): Boolean {
|
||||||
|
return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a [Snackbar] in a [FragmentActivity] and correctly
|
||||||
|
* anchor it to a [com.google.android.material.floatingactionbutton.FloatingActionButton]
|
||||||
|
* if one exists in the [view]
|
||||||
|
*/
|
||||||
|
fun FragmentActivity.snackbar(
|
||||||
|
view: View = findViewById(android.R.id.content),
|
||||||
|
message: String,
|
||||||
|
length: Int = Snackbar.LENGTH_SHORT,
|
||||||
|
): Snackbar {
|
||||||
|
val snackbar = Snackbar.make(view, message, length)
|
||||||
|
snackbar.anchorView = findViewById(R.id.fab)
|
||||||
|
snackbar.show()
|
||||||
|
return snackbar
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simplifies the common `getString(key, null) ?: defaultValue` case slightly
|
||||||
|
*/
|
||||||
|
fun SharedPreferences.getString(key: String): String? = getString(key, null)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert this [String] to its [Base64] representation
|
||||||
|
*/
|
||||||
|
fun String.base64(): String {
|
||||||
|
return Base64.encodeToString(encodeToByteArray(), Base64.NO_WRAP)
|
||||||
|
}
|
|
@ -4,74 +4,33 @@
|
||||||
*/
|
*/
|
||||||
package com.zeapo.pwdstore.utils
|
package com.zeapo.pwdstore.utils
|
||||||
|
|
||||||
import android.app.KeyguardManager
|
|
||||||
import android.content.ClipboardManager
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.os.Build
|
|
||||||
import android.util.Base64
|
|
||||||
import android.util.TypedValue
|
|
||||||
import android.view.View
|
|
||||||
import android.view.autofill.AutofillManager
|
|
||||||
import android.view.inputmethod.InputMethodManager
|
|
||||||
import androidx.annotation.IdRes
|
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.core.content.getSystemService
|
|
||||||
import androidx.fragment.app.FragmentActivity
|
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import androidx.security.crypto.EncryptedSharedPreferences
|
|
||||||
import androidx.security.crypto.MasterKey
|
|
||||||
import com.github.ajalt.timberkt.d
|
|
||||||
import com.github.michaelbull.result.Ok
|
|
||||||
import com.github.michaelbull.result.Result
|
|
||||||
import com.github.michaelbull.result.getOrElse
|
import com.github.michaelbull.result.getOrElse
|
||||||
import com.github.michaelbull.result.runCatching
|
import com.github.michaelbull.result.runCatching
|
||||||
import com.google.android.material.snackbar.Snackbar
|
|
||||||
import com.zeapo.pwdstore.R
|
|
||||||
import com.zeapo.pwdstore.git.operation.GitOperation
|
|
||||||
import com.zeapo.pwdstore.utils.PasswordRepository.Companion.getRepositoryDirectory
|
import com.zeapo.pwdstore.utils.PasswordRepository.Companion.getRepositoryDirectory
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import org.eclipse.jgit.lib.ObjectId
|
import org.eclipse.jgit.lib.ObjectId
|
||||||
import org.eclipse.jgit.revwalk.RevCommit
|
import org.eclipse.jgit.revwalk.RevCommit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default OpenPGP provider for the app
|
||||||
|
*/
|
||||||
const val OPENPGP_PROVIDER = "org.sufficientlysecure.keychain"
|
const val OPENPGP_PROVIDER = "org.sufficientlysecure.keychain"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the given [flag] from the value of this [Int]
|
||||||
|
*/
|
||||||
fun Int.clearFlag(flag: Int): Int {
|
fun Int.clearFlag(flag: Int): Int {
|
||||||
return this and flag.inv()
|
return this and flag.inv()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this [Int] contains the given [flag]
|
||||||
|
*/
|
||||||
infix fun Int.hasFlag(flag: Int): Boolean {
|
infix fun Int.hasFlag(flag: Int): Boolean {
|
||||||
return this and flag == flag
|
return this and flag == flag
|
||||||
}
|
}
|
||||||
|
|
||||||
fun String.splitLines(): Array<String> {
|
|
||||||
return split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun String.base64(): String {
|
|
||||||
return Base64.encodeToString(encodeToByteArray(), Base64.NO_WRAP)
|
|
||||||
}
|
|
||||||
|
|
||||||
val Context.clipboard
|
|
||||||
get() = getSystemService<ClipboardManager>()
|
|
||||||
|
|
||||||
fun FragmentActivity.snackbar(
|
|
||||||
view: View = findViewById(android.R.id.content),
|
|
||||||
message: String,
|
|
||||||
length: Int = Snackbar.LENGTH_SHORT,
|
|
||||||
): Snackbar {
|
|
||||||
val snackbar = Snackbar.make(view, message, length)
|
|
||||||
snackbar.anchorView = findViewById(R.id.fab)
|
|
||||||
snackbar.show()
|
|
||||||
return snackbar
|
|
||||||
}
|
|
||||||
|
|
||||||
fun File.listFilesRecursively() = walkTopDown().filter { !it.isDirectory }.toList()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether this [File] is a directory that contains [other].
|
* Checks whether this [File] is a directory that contains [other].
|
||||||
*/
|
*/
|
||||||
|
@ -89,91 +48,23 @@ fun File.contains(other: File): Boolean {
|
||||||
return relativePath.path == other.name
|
return relativePath.path == other.name
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.resolveAttribute(attr: Int): Int {
|
|
||||||
val typedValue = TypedValue()
|
|
||||||
this.theme.resolveAttribute(attr, typedValue, true)
|
|
||||||
return typedValue.data
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Context.getEncryptedPrefs(fileName: String): SharedPreferences {
|
|
||||||
val masterKeyAlias = MasterKey.Builder(applicationContext)
|
|
||||||
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
|
||||||
.build()
|
|
||||||
return EncryptedSharedPreferences.create(
|
|
||||||
applicationContext,
|
|
||||||
fileName,
|
|
||||||
masterKeyAlias,
|
|
||||||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
|
||||||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val Context.sharedPrefs: SharedPreferences
|
|
||||||
get() = PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
|
||||||
|
|
||||||
fun SharedPreferences.getString(key: String): String? = getString(key, null)
|
|
||||||
|
|
||||||
suspend fun FragmentActivity.commitChange(
|
|
||||||
message: String,
|
|
||||||
): Result<Unit, Throwable> {
|
|
||||||
if (!PasswordRepository.isGitRepo()) {
|
|
||||||
return Ok(Unit)
|
|
||||||
}
|
|
||||||
return object : GitOperation(this@commitChange) {
|
|
||||||
override val commands = arrayOf(
|
|
||||||
// Stage all files
|
|
||||||
git.add().addFilepattern("."),
|
|
||||||
// Populate the changed files count
|
|
||||||
git.status(),
|
|
||||||
// Commit everything! If anything changed, that is.
|
|
||||||
git.commit().setAll(true).setMessage(message),
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun preExecute(): Boolean {
|
|
||||||
d { "Committing with message: '$message'" }
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}.execute()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun FragmentActivity.isPermissionGranted(permission: String): Boolean {
|
|
||||||
return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extension function for [AlertDialog] that requests focus for the
|
* Checks if this [File] is in the password repository directory as given
|
||||||
* view whose id is [id]. Solution based on a StackOverflow
|
* by [getRepositoryDirectory]
|
||||||
* answer: https://stackoverflow.com/a/13056259/297261
|
|
||||||
*/
|
*/
|
||||||
fun <T : View> AlertDialog.requestInputFocusOnView(@IdRes id: Int) {
|
|
||||||
setOnShowListener {
|
|
||||||
findViewById<T>(id)?.apply {
|
|
||||||
setOnFocusChangeListener { v, _ ->
|
|
||||||
v.post {
|
|
||||||
context.getSystemService<InputMethodManager>()
|
|
||||||
?.showSoftInput(v, InputMethodManager.SHOW_IMPLICIT)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
requestFocus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val Context.autofillManager: AutofillManager?
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
get() = getSystemService()
|
|
||||||
|
|
||||||
val Context.keyguardManager: KeyguardManager
|
|
||||||
get() = getSystemService()!!
|
|
||||||
|
|
||||||
fun File.isInsideRepository(): Boolean {
|
fun File.isInsideRepository(): Boolean {
|
||||||
return canonicalPath.contains(getRepositoryDirectory().canonicalPath)
|
return canonicalPath.contains(getRepositoryDirectory().canonicalPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively lists the files in this [File], skipping any directories it encounters.
|
||||||
|
*/
|
||||||
|
fun File.listFilesRecursively() = walkTopDown().filter { !it.isDirectory }.toList()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unique SHA-1 hash of this commit as hexadecimal string.
|
* Unique SHA-1 hash of this commit as hexadecimal string.
|
||||||
*
|
*
|
||||||
* @see RevCommit.id
|
* @see RevCommit.getId
|
||||||
*/
|
*/
|
||||||
val RevCommit.hash: String
|
val RevCommit.hash: String
|
||||||
get() = ObjectId.toString(id)
|
get() = ObjectId.toString(id)
|
||||||
|
@ -189,3 +80,11 @@ val RevCommit.time: Date
|
||||||
val epochMilliseconds = epochSeconds * 1000
|
val epochMilliseconds = epochSeconds * 1000
|
||||||
return Date(epochMilliseconds)
|
return Date(epochMilliseconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits this [String] into an [Array] of [String]s, split on the UNIX LF line ending
|
||||||
|
* and stripped of any empty lines.
|
||||||
|
*/
|
||||||
|
fun String.splitLines(): Array<String> {
|
||||||
|
return split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||||
|
}
|
||||||
|
|
|
@ -1,34 +1,29 @@
|
||||||
package com.zeapo.pwdstore.utils
|
package com.zeapo.pwdstore.utils
|
||||||
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.view.View
|
|
||||||
import androidx.annotation.IdRes
|
import androidx.annotation.IdRes
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import androidx.fragment.app.commit
|
import androidx.fragment.app.commit
|
||||||
import com.zeapo.pwdstore.R
|
import com.zeapo.pwdstore.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if [permission] is granted to the app. Aliases to [isPermissionGranted] internally.
|
||||||
|
*/
|
||||||
fun Fragment.isPermissionGranted(permission: String): Boolean {
|
fun Fragment.isPermissionGranted(permission: String): Boolean {
|
||||||
return ContextCompat.checkSelfPermission(requireActivity(), permission) == PackageManager.PERMISSION_GRANTED
|
return requireActivity().isPermissionGranted(permission)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls `finish()` on the enclosing [androidx.fragment.app.FragmentActivity]
|
||||||
|
*/
|
||||||
fun Fragment.finish() = requireActivity().finish()
|
fun Fragment.finish() = requireActivity().finish()
|
||||||
|
|
||||||
fun FragmentManager.performTransaction(destinationFragment: Fragment, @IdRes containerViewId: Int = android.R.id.content) {
|
/**
|
||||||
this.commit {
|
* Perform a [commit] on this [FragmentManager] with custom animations and adding the [destinationFragment]
|
||||||
beginTransaction()
|
* to the fragment backstack
|
||||||
setCustomAnimations(
|
*/
|
||||||
R.animator.slide_in_left,
|
|
||||||
R.animator.slide_out_left,
|
|
||||||
R.animator.slide_in_right,
|
|
||||||
R.animator.slide_out_right)
|
|
||||||
replace(containerViewId, destinationFragment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun FragmentManager.performTransactionWithBackStack(destinationFragment: Fragment, @IdRes containerViewId: Int = android.R.id.content) {
|
fun FragmentManager.performTransactionWithBackStack(destinationFragment: Fragment, @IdRes containerViewId: Int = android.R.id.content) {
|
||||||
this.commit {
|
commit {
|
||||||
beginTransaction()
|
beginTransaction()
|
||||||
addToBackStack(destinationFragment.tag)
|
addToBackStack(destinationFragment.tag)
|
||||||
setCustomAnimations(
|
setCustomAnimations(
|
||||||
|
@ -39,14 +34,3 @@ fun FragmentManager.performTransactionWithBackStack(destinationFragment: Fragmen
|
||||||
replace(containerViewId, destinationFragment)
|
replace(containerViewId, destinationFragment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun FragmentManager.performSharedElementTransaction(destinationFragment: Fragment, views: List<View>, @IdRes containerViewId: Int = android.R.id.content) {
|
|
||||||
this.commit {
|
|
||||||
beginTransaction()
|
|
||||||
for (view in views) {
|
|
||||||
addSharedElement(view, view.transitionName)
|
|
||||||
}
|
|
||||||
addToBackStack(destinationFragment.tag)
|
|
||||||
replace(containerViewId, destinationFragment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -83,9 +83,6 @@ object Dependencies {
|
||||||
|
|
||||||
const val runner = "androidx.test:runner:1.3.0"
|
const val runner = "androidx.test:runner:1.3.0"
|
||||||
const val rules = "androidx.test:rules:1.3.0"
|
const val rules = "androidx.test:rules:1.3.0"
|
||||||
const val junit = "androidx.test.ext:junit:1.1.2"
|
|
||||||
const val espresso_core = "androidx.test.espresso:espresso-core:3.3.0"
|
|
||||||
const val espresso_intents = "androidx.test.espresso:espresso-intents:3.3.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue