-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Edit remotes in a new activity instead of a dialog
This has a much nicer user experience since we can now have proper switches for toggleable options and include descriptions for all the actions. Most of the annoying "success" confirmation snackbar messages have also been removed now that the UI can more intuitively show the effects of the successful operations. Signed-off-by: Andrew Gunnerson <[email protected]>
- Loading branch information
1 parent
d73d2f9
commit efb3b77
Showing
17 changed files
with
1,079 additions
and
727 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
90 changes: 90 additions & 0 deletions
90
app/src/main/java/com/chiller3/rsaf/PreferenceBaseActivity.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
/* | ||
* SPDX-FileCopyrightText: 2023-2024 Andrew Gunnerson | ||
* SPDX-License-Identifier: GPL-3.0-only | ||
*/ | ||
|
||
package com.chiller3.rsaf | ||
|
||
import android.content.Intent | ||
import android.os.Bundle | ||
import android.view.MenuItem | ||
import android.view.ViewGroup | ||
import androidx.activity.enableEdgeToEdge | ||
import androidx.appcompat.app.AppCompatActivity | ||
import androidx.core.view.ViewCompat | ||
import androidx.core.view.WindowInsetsCompat | ||
import androidx.core.view.updateLayoutParams | ||
import com.chiller3.rsaf.databinding.SettingsActivityBinding | ||
|
||
abstract class PreferenceBaseActivity : AppCompatActivity() { | ||
protected abstract val actionBarTitle: CharSequence? | ||
|
||
protected abstract val showUpButton: Boolean | ||
|
||
protected abstract fun createFragment(): PreferenceBaseFragment | ||
|
||
override fun onCreate(savedInstanceState: Bundle?) { | ||
enableEdgeToEdge() | ||
super.onCreate(savedInstanceState) | ||
|
||
val binding = SettingsActivityBinding.inflate(layoutInflater) | ||
setContentView(binding.root) | ||
|
||
val transaction = supportFragmentManager.beginTransaction() | ||
|
||
// https://issuetracker.google.com/issues/181805603 | ||
val bioFragment = supportFragmentManager | ||
.findFragmentByTag("androidx.biometric.BiometricFragment") | ||
if (bioFragment != null) { | ||
transaction.remove(bioFragment) | ||
} | ||
|
||
val fragment: PreferenceBaseFragment | ||
|
||
if (savedInstanceState == null) { | ||
fragment = createFragment() | ||
transaction.replace(R.id.settings, fragment) | ||
} else { | ||
fragment = supportFragmentManager.findFragmentById(R.id.settings) | ||
as PreferenceBaseFragment | ||
} | ||
|
||
transaction.commit() | ||
|
||
supportFragmentManager.setFragmentResultListener(fragment.requestTag, this) { _, result -> | ||
setResult(RESULT_OK, Intent().apply { putExtras(result) }) | ||
} | ||
|
||
ViewCompat.setOnApplyWindowInsetsListener(binding.toolbar) { v, windowInsets -> | ||
val insets = windowInsets.getInsets( | ||
WindowInsetsCompat.Type.systemBars() | ||
or WindowInsetsCompat.Type.displayCutout() | ||
) | ||
|
||
v.updateLayoutParams<ViewGroup.MarginLayoutParams> { | ||
leftMargin = insets.left | ||
topMargin = insets.top | ||
rightMargin = insets.right | ||
} | ||
|
||
WindowInsetsCompat.CONSUMED | ||
} | ||
|
||
setSupportActionBar(binding.toolbar) | ||
supportActionBar!!.setDisplayHomeAsUpEnabled(showUpButton) | ||
|
||
actionBarTitle?.let { | ||
setTitle(it) | ||
} | ||
} | ||
|
||
override fun onOptionsItemSelected(item: MenuItem): Boolean { | ||
return when (item.itemId) { | ||
android.R.id.home -> { | ||
onBackPressedDispatcher.onBackPressed() | ||
true | ||
} | ||
else -> super.onOptionsItemSelected(item) | ||
} | ||
} | ||
} |
145 changes: 145 additions & 0 deletions
145
app/src/main/java/com/chiller3/rsaf/PreferenceBaseFragment.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
/* | ||
* SPDX-FileCopyrightText: 2023-2024 Andrew Gunnerson | ||
* SPDX-License-Identifier: GPL-3.0-only | ||
*/ | ||
|
||
package com.chiller3.rsaf | ||
|
||
import android.os.Bundle | ||
import android.view.LayoutInflater | ||
import android.view.View | ||
import android.view.ViewGroup | ||
import android.widget.Toast | ||
import androidx.biometric.BiometricManager.Authenticators | ||
import androidx.biometric.BiometricPrompt | ||
import androidx.core.view.ViewCompat | ||
import androidx.core.view.WindowInsetsCompat | ||
import androidx.core.view.updatePadding | ||
import androidx.preference.PreferenceFragmentCompat | ||
import androidx.recyclerview.widget.RecyclerView | ||
|
||
abstract class PreferenceBaseFragment : PreferenceFragmentCompat() { | ||
companion object { | ||
private const val INACTIVE_TIMEOUT_NS = 60_000_000_000L | ||
|
||
// These are intentionally global to ensure that the prompt does not appear when navigating | ||
// within the app. | ||
private var bioAuthenticated = false | ||
private var lastPause = 0L | ||
} | ||
|
||
abstract val requestTag: String | ||
|
||
protected lateinit var prefs: Preferences | ||
private lateinit var bioPrompt: BiometricPrompt | ||
|
||
override fun onCreate(savedInstanceState: Bundle?) { | ||
val activity = requireActivity() | ||
|
||
prefs = Preferences(activity) | ||
|
||
bioPrompt = BiometricPrompt( | ||
this, | ||
activity.mainExecutor, | ||
object : BiometricPrompt.AuthenticationCallback() { | ||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { | ||
Toast.makeText( | ||
activity, | ||
getString(R.string.biometric_error, errString), | ||
Toast.LENGTH_LONG, | ||
).show() | ||
activity.finish() | ||
} | ||
|
||
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { | ||
bioAuthenticated = true | ||
refreshGlobalVisibility() | ||
} | ||
|
||
override fun onAuthenticationFailed() { | ||
Toast.makeText( | ||
activity, | ||
R.string.biometric_failure, | ||
Toast.LENGTH_LONG, | ||
).show() | ||
activity.finish() | ||
} | ||
}, | ||
) | ||
|
||
super.onCreate(savedInstanceState) | ||
} | ||
|
||
override fun onCreateRecyclerView( | ||
inflater: LayoutInflater, | ||
parent: ViewGroup, | ||
savedInstanceState: Bundle? | ||
): RecyclerView { | ||
val view = super.onCreateRecyclerView(inflater, parent, savedInstanceState) | ||
|
||
view.clipToPadding = false | ||
|
||
ViewCompat.setOnApplyWindowInsetsListener(view) { v, windowInsets -> | ||
val insets = windowInsets.getInsets( | ||
WindowInsetsCompat.Type.systemBars() | ||
or WindowInsetsCompat.Type.displayCutout() | ||
) | ||
|
||
// This is a little bit ugly in landscape mode because the divider lines for categories | ||
// extend into the inset area. However, it's worth applying the left/right padding here | ||
// anyway because it allows the inset area to be used for scrolling instead of just | ||
// being a useless dead zone. | ||
v.updatePadding( | ||
bottom = insets.bottom, | ||
left = insets.left, | ||
right = insets.right, | ||
) | ||
|
||
WindowInsetsCompat.CONSUMED | ||
} | ||
|
||
return view | ||
} | ||
|
||
override fun onResume() { | ||
super.onResume() | ||
|
||
if (bioAuthenticated && (System.nanoTime() - lastPause) >= INACTIVE_TIMEOUT_NS) { | ||
bioAuthenticated = false | ||
} | ||
|
||
if (!bioAuthenticated) { | ||
if (!prefs.requireAuth) { | ||
bioAuthenticated = true | ||
} else { | ||
startBiometricAuth() | ||
} | ||
} | ||
|
||
refreshGlobalVisibility() | ||
} | ||
|
||
override fun onPause() { | ||
super.onPause() | ||
|
||
lastPause = System.nanoTime() | ||
} | ||
|
||
private fun startBiometricAuth() { | ||
val promptInfo = BiometricPrompt.PromptInfo.Builder() | ||
.setAllowedAuthenticators(Authenticators.BIOMETRIC_STRONG or Authenticators.DEVICE_CREDENTIAL) | ||
.setTitle(getString(R.string.biometric_title)) | ||
.build() | ||
|
||
bioPrompt.authenticate(promptInfo) | ||
} | ||
|
||
private fun refreshGlobalVisibility() { | ||
view?.visibility = if (bioAuthenticated) { | ||
View.VISIBLE | ||
} else { | ||
// Using View.GONE causes noticeable scrolling jank due to relayout. | ||
View.INVISIBLE | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
111 changes: 0 additions & 111 deletions
111
app/src/main/java/com/chiller3/rsaf/dialog/EditRemoteDialogFragment.kt
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.