mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-22 01:40:07 +01:00
Update screen lock.
This commit is contained in:
committed by
mtang-signal
parent
c880db0f4a
commit
3bdbd69a7d
@@ -109,8 +109,13 @@ class LearnMoreTextPreferenceViewHolder(itemView: View) : PreferenceViewHolder<L
|
||||
class ClickPreferenceViewHolder(itemView: View) : PreferenceViewHolder<ClickPreference>(itemView) {
|
||||
override fun bind(model: ClickPreference) {
|
||||
super.bind(model)
|
||||
itemView.setOnClickListener { model.onClick() }
|
||||
itemView.setOnLongClickListener { model.onLongClick?.invoke() ?: false }
|
||||
if (!itemView.isEnabled && model.onDisabledClicked != null) {
|
||||
itemView.isEnabled = true
|
||||
itemView.setOnClickListener { model.onDisabledClicked() }
|
||||
} else {
|
||||
itemView.setOnClickListener { model.onClick() }
|
||||
itemView.setOnLongClickListener { model.onLongClick?.invoke() ?: false }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.BiometricDeviceAuthentication
|
||||
import org.thoughtcrime.securesms.BiometricDeviceLockContract
|
||||
@@ -36,9 +37,9 @@ import org.thoughtcrime.securesms.components.settings.PreferenceModel
|
||||
import org.thoughtcrime.securesms.components.settings.PreferenceViewHolder
|
||||
import org.thoughtcrime.securesms.components.settings.configure
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||
import org.thoughtcrime.securesms.util.ConversationUtil
|
||||
import org.thoughtcrime.securesms.util.ExpirationUtil
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil
|
||||
import org.thoughtcrime.securesms.util.SpanUtil
|
||||
@@ -197,7 +198,7 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac
|
||||
KeyCachingService.getMasterSecret(context),
|
||||
MasterSecretUtil.UNENCRYPTED_PASSPHRASE
|
||||
)
|
||||
TextSecurePreferences.setPasswordDisabled(activity, true)
|
||||
SignalStore.settings.passphraseDisabled = true
|
||||
val intent = Intent(activity, KeyCachingService::class.java)
|
||||
intent.action = KeyCachingService.DISABLE_ACTION
|
||||
requireActivity().startService(intent)
|
||||
@@ -249,33 +250,21 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac
|
||||
} else {
|
||||
val isKeyguardSecure = ServiceUtil.getKeyguardManager(requireContext()).isKeyguardSecure
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences_app_protection__screen_lock),
|
||||
summary = DSLSettingsText.from(R.string.preferences_app_protection__lock_signal_access_with_android_screen_lock_or_fingerprint),
|
||||
isChecked = state.screenLock && isKeyguardSecure,
|
||||
isEnabled = isKeyguardSecure,
|
||||
onClick = {
|
||||
viewModel.setScreenLockEnabled(!state.screenLock)
|
||||
|
||||
val intent = Intent(requireContext(), KeyCachingService::class.java)
|
||||
intent.action = KeyCachingService.LOCK_TOGGLED_EVENT
|
||||
requireContext().startService(intent)
|
||||
|
||||
ConversationUtil.refreshRecipientShortcuts()
|
||||
}
|
||||
)
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.preferences_app_protection__screen_lock_inactivity_timeout),
|
||||
summary = DSLSettingsText.from(getScreenLockInactivityTimeoutSummary(state.screenLockActivityTimeout)),
|
||||
isEnabled = isKeyguardSecure && state.screenLock,
|
||||
title = DSLSettingsText.from(R.string.preferences_app_protection__screen_lock),
|
||||
summary = DSLSettingsText.from(getScreenLockInactivityTimeoutSummary(isKeyguardSecure && state.screenLock, state.screenLockActivityTimeout)),
|
||||
onClick = {
|
||||
childFragmentManager.clearFragmentResult(TimeDurationPickerDialog.RESULT_DURATION)
|
||||
childFragmentManager.clearFragmentResultListener(TimeDurationPickerDialog.RESULT_DURATION)
|
||||
childFragmentManager.setFragmentResultListener(TimeDurationPickerDialog.RESULT_DURATION, this@PrivacySettingsFragment) { _, bundle ->
|
||||
viewModel.setScreenLockTimeout(bundle.getLong(TimeDurationPickerDialog.RESULT_KEY_DURATION_MILLISECONDS).milliseconds.inWholeSeconds)
|
||||
}
|
||||
TimeDurationPickerDialog.create(state.screenLockActivityTimeout.seconds).show(childFragmentManager, null)
|
||||
Navigation.findNavController(requireView()).safeNavigate(R.id.action_privacySettingsFragment_to_screenLockSettingsFragment)
|
||||
},
|
||||
isEnabled = isKeyguardSecure,
|
||||
onDisabledClicked = {
|
||||
Snackbar
|
||||
.make(
|
||||
requireView(),
|
||||
resources.getString(R.string.preferences_app_protection__to_use_screen_lock),
|
||||
Snackbar.LENGTH_LONG
|
||||
)
|
||||
.show()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -362,9 +351,11 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac
|
||||
}
|
||||
}
|
||||
|
||||
private fun getScreenLockInactivityTimeoutSummary(timeoutSeconds: Long): String {
|
||||
return if (timeoutSeconds <= 0) {
|
||||
getString(R.string.AppProtectionPreferenceFragment_none)
|
||||
private fun getScreenLockInactivityTimeoutSummary(enabledScreenLock: Boolean, timeoutSeconds: Long): String {
|
||||
return if (!enabledScreenLock) {
|
||||
getString(R.string.ScreenLockSettingsFragment__off)
|
||||
} else if (timeoutSeconds == 0L) {
|
||||
getString(R.string.ScreenLockSettingsFragment__immediately)
|
||||
} else {
|
||||
ExpirationUtil.getExpirationDisplayValue(requireContext(), timeoutSeconds.toInt())
|
||||
}
|
||||
|
||||
@@ -37,16 +37,6 @@ class PrivacySettingsViewModel(
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setScreenLockEnabled(enabled: Boolean) {
|
||||
sharedPreferences.edit().putBoolean(TextSecurePreferences.SCREEN_LOCK, enabled).apply()
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setScreenLockTimeout(seconds: Long) {
|
||||
TextSecurePreferences.setScreenLockTimeout(AppDependencies.application, seconds)
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setScreenSecurityEnabled(enabled: Boolean) {
|
||||
sharedPreferences.edit().putBoolean(TextSecurePreferences.SCREEN_SECURITY_PREF, enabled).apply()
|
||||
refresh()
|
||||
@@ -63,12 +53,12 @@ class PrivacySettingsViewModel(
|
||||
}
|
||||
|
||||
fun setObsoletePasswordTimeoutEnabled(enabled: Boolean) {
|
||||
sharedPreferences.edit().putBoolean(TextSecurePreferences.PASSPHRASE_TIMEOUT_PREF, enabled).apply()
|
||||
SignalStore.settings.passphraseTimeoutEnabled = enabled
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setObsoletePasswordTimeout(minutes: Int) {
|
||||
TextSecurePreferences.setPassphraseTimeoutInterval(AppDependencies.application, minutes)
|
||||
SignalStore.settings.passphraseTimeout = minutes
|
||||
refresh()
|
||||
}
|
||||
|
||||
@@ -81,14 +71,14 @@ class PrivacySettingsViewModel(
|
||||
blockedCount = 0,
|
||||
readReceipts = TextSecurePreferences.isReadReceiptsEnabled(AppDependencies.application),
|
||||
typingIndicators = TextSecurePreferences.isTypingIndicatorsEnabled(AppDependencies.application),
|
||||
screenLock = TextSecurePreferences.isScreenLockEnabled(AppDependencies.application),
|
||||
screenLockActivityTimeout = TextSecurePreferences.getScreenLockTimeout(AppDependencies.application),
|
||||
screenLock = SignalStore.settings.screenLockEnabled,
|
||||
screenLockActivityTimeout = SignalStore.settings.screenLockTimeout,
|
||||
screenSecurity = TextSecurePreferences.isScreenSecurityEnabled(AppDependencies.application),
|
||||
incognitoKeyboard = TextSecurePreferences.isIncognitoKeyboardEnabled(AppDependencies.application),
|
||||
paymentLock = SignalStore.payments.paymentLock,
|
||||
isObsoletePasswordEnabled = !TextSecurePreferences.isPasswordDisabled(AppDependencies.application),
|
||||
isObsoletePasswordTimeoutEnabled = TextSecurePreferences.isPassphraseTimeoutEnabled(AppDependencies.application),
|
||||
obsoletePasswordTimeout = TextSecurePreferences.getPassphraseTimeoutInterval(AppDependencies.application),
|
||||
isObsoletePasswordEnabled = !SignalStore.settings.passphraseDisabled,
|
||||
isObsoletePasswordTimeoutEnabled = SignalStore.settings.passphraseTimeoutEnabled,
|
||||
obsoletePasswordTimeout = SignalStore.settings.passphraseTimeout,
|
||||
universalExpireTimer = SignalStore.settings.universalExpireTimer
|
||||
)
|
||||
}
|
||||
|
||||
@@ -65,6 +65,12 @@ class CustomExpireTimerSelectorView @JvmOverloads constructor(
|
||||
valuePicker.maxValue = timerUnit.maxValue
|
||||
}
|
||||
|
||||
fun setUnits(minValue: Int, maxValue: Int, timeUnitRes: Int) {
|
||||
unitPicker.minValue = minValue
|
||||
unitPicker.maxValue = maxValue
|
||||
unitPicker.displayedValues = context.resources.getStringArray(timeUnitRes)
|
||||
}
|
||||
|
||||
private enum class TimerUnit(val minValue: Int, val maxValue: Int, val valueMultiplier: Long) {
|
||||
SECONDS(1, 59, TimeUnit.SECONDS.toSeconds(1)),
|
||||
MINUTES(1, 59, TimeUnit.MINUTES.toSeconds(1)),
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.privacy.screenlock
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.app.privacy.expire.CustomExpireTimerSelectorView
|
||||
|
||||
/**
|
||||
* Dialog for selecting a custom timer value when setting the screen lock timeout.
|
||||
*/
|
||||
class CustomScreenLockTimerSelectDialog : DialogFragment() {
|
||||
|
||||
private val viewModel: ScreenLockSettingsViewModel by activityViewModels()
|
||||
private lateinit var selector: CustomExpireTimerSelectorView
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val dialogView: View = LayoutInflater.from(context).inflate(R.layout.custom_expire_timer_select_dialog, null, false)
|
||||
|
||||
selector = dialogView.findViewById(R.id.custom_expire_timer_select_dialog_selector)
|
||||
selector.setUnits(1, 3, R.array.CustomScreenLockTimerSelectorView__unit_labels)
|
||||
selector.setTimer(viewModel.state.value.screenLockActivityTimeout.toInt())
|
||||
|
||||
return MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.ExpireTimerSettingsFragment__custom_time)
|
||||
.setView(dialogView)
|
||||
.setPositiveButton(R.string.ExpireTimerSettingsFragment__set) { _, _ ->
|
||||
viewModel.setScreenLockTimeout(selector.getTimer().toLong())
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.privacy.screenlock
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.selection.selectable
|
||||
import androidx.compose.foundation.selection.selectableGroup
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import org.signal.core.ui.Previews
|
||||
import org.signal.core.ui.Scaffolds
|
||||
import org.signal.core.ui.SignalPreview
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.BiometricDeviceAuthentication
|
||||
import org.thoughtcrime.securesms.BiometricDeviceLockContract
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.compose.ComposeFragment
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService
|
||||
import org.thoughtcrime.securesms.util.ConversationUtil
|
||||
import org.thoughtcrime.securesms.util.ExpirationUtil
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
|
||||
/**
|
||||
* Fragment that allows user to turn on screen lock and set a timer to lock
|
||||
*/
|
||||
class ScreenLockSettingsFragment : ComposeFragment() {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(ScreenLockSettingsFragment::class)
|
||||
}
|
||||
|
||||
private val viewModel: ScreenLockSettingsViewModel by activityViewModels()
|
||||
|
||||
private lateinit var biometricAuth: BiometricDeviceAuthentication
|
||||
private lateinit var biometricDeviceLockLauncher: ActivityResultLauncher<String>
|
||||
private lateinit var disableLockPromptInfo: BiometricPrompt.PromptInfo
|
||||
private lateinit var enableLockPromptInfo: BiometricPrompt.PromptInfo
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
biometricDeviceLockLauncher = registerForActivityResult(BiometricDeviceLockContract()) { result: Int ->
|
||||
if (result == BiometricDeviceAuthentication.AUTHENTICATED) {
|
||||
toggleScreenLock()
|
||||
}
|
||||
}
|
||||
|
||||
enableLockPromptInfo = BiometricPrompt.PromptInfo.Builder()
|
||||
.setAllowedAuthenticators(BiometricDeviceAuthentication.ALLOWED_AUTHENTICATORS)
|
||||
.setTitle(requireContext().getString(R.string.ScreenLockSettingsFragment__use_signal_screen_lock))
|
||||
.setConfirmationRequired(true)
|
||||
.build()
|
||||
|
||||
disableLockPromptInfo = BiometricPrompt.PromptInfo.Builder()
|
||||
.setAllowedAuthenticators(BiometricDeviceAuthentication.ALLOWED_AUTHENTICATORS)
|
||||
.setTitle(requireContext().getString(R.string.ScreenLockSettingsFragment__turn_off_signal_lock))
|
||||
.setConfirmationRequired(true)
|
||||
.build()
|
||||
|
||||
biometricAuth = BiometricDeviceAuthentication(
|
||||
BiometricManager.from(requireActivity()),
|
||||
BiometricPrompt(requireActivity(), BiometricAuthenticationListener()),
|
||||
enableLockPromptInfo
|
||||
)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
biometricAuth.cancelAuthentication()
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun FragmentContent() {
|
||||
val state by viewModel.state.collectAsState()
|
||||
val navController: NavController by remember { mutableStateOf(findNavController()) }
|
||||
|
||||
Scaffolds.Settings(
|
||||
title = stringResource(id = R.string.preferences_app_protection__screen_lock),
|
||||
onNavigationClick = { navController.popBackStack() },
|
||||
navigationIconPainter = painterResource(id = R.drawable.ic_arrow_left_24),
|
||||
navigationContentDescription = stringResource(id = R.string.Material3SearchToolbar__close)
|
||||
) { contentPadding: PaddingValues ->
|
||||
ScreenLockScreen(
|
||||
state = state,
|
||||
onChecked = { checked ->
|
||||
if (biometricAuth.canAuthenticate() && !checked) {
|
||||
biometricAuth.updatePromptInfo(disableLockPromptInfo)
|
||||
biometricAuth.authenticate(requireContext(), true) {
|
||||
biometricDeviceLockLauncher.launch(getString(R.string.ScreenLockSettingsFragment__turn_off_signal_lock))
|
||||
}
|
||||
} else if (biometricAuth.canAuthenticate() && checked) {
|
||||
biometricAuth.updatePromptInfo(enableLockPromptInfo)
|
||||
biometricAuth.authenticate(requireContext(), true) {
|
||||
biometricDeviceLockLauncher.launch(getString(R.string.ScreenLockSettingsFragment__use_screen_lock))
|
||||
}
|
||||
}
|
||||
},
|
||||
onTimeClicked = viewModel::setScreenLockTimeout,
|
||||
onCustomTimeClicked = { navController.safeNavigate(R.id.action_screenLockSettingsFragment_to_customScreenLockTimerSelectDialog) },
|
||||
modifier = Modifier.padding(contentPadding)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleScreenLock() {
|
||||
viewModel.toggleScreenLock()
|
||||
|
||||
val intent = Intent(requireContext(), KeyCachingService::class.java)
|
||||
intent.action = KeyCachingService.LOCK_TOGGLED_EVENT
|
||||
requireContext().startService(intent)
|
||||
|
||||
ConversationUtil.refreshRecipientShortcuts()
|
||||
}
|
||||
|
||||
private inner class BiometricAuthenticationListener : BiometricPrompt.AuthenticationCallback() {
|
||||
override fun onAuthenticationError(errorCode: Int, errorString: CharSequence) {
|
||||
Log.w(TAG, "Authentication error: $errorCode")
|
||||
onAuthenticationFailed()
|
||||
}
|
||||
|
||||
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||
Log.i(TAG, "Authentication succeeded")
|
||||
toggleScreenLock()
|
||||
}
|
||||
|
||||
override fun onAuthenticationFailed() {
|
||||
Log.w(TAG, "Unable to authenticate")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ScreenLockScreen(
|
||||
state: ScreenLockSettingsState,
|
||||
onChecked: (Boolean) -> Unit,
|
||||
onTimeClicked: (Long) -> Unit,
|
||||
onCustomTimeClicked: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Column(modifier = modifier.verticalScroll(rememberScrollState())) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_screen_lock),
|
||||
contentDescription = null,
|
||||
tint = Color.Unspecified,
|
||||
modifier = Modifier.padding(top = 24.dp, bottom = 24.dp)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = R.string.ScreenLockSettingsFragment__your_android_device),
|
||||
textAlign = TextAlign.Center,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
modifier = Modifier.padding(start = 40.dp, end = 40.dp, bottom = 24.dp)
|
||||
)
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 24.dp)
|
||||
.background(color = MaterialTheme.colorScheme.surfaceVariant, shape = RoundedCornerShape(24.dp))
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp, vertical = 12.dp)
|
||||
) {
|
||||
Text(stringResource(id = R.string.ScreenLockSettingsFragment__use_screen_lock))
|
||||
Spacer(Modifier.weight(1f))
|
||||
Switch(checked = state.screenLock, onCheckedChange = onChecked)
|
||||
}
|
||||
}
|
||||
|
||||
if (state.screenLock) {
|
||||
val labels: List<String> = LocalContext.current.resources.getStringArray(R.array.ScreenLockSettingsFragment__labels).toList()
|
||||
val values: List<Long> = LocalContext.current.resources.getIntArray(R.array.ScreenLockSettingsFragment__values).map { it.toLong() }
|
||||
|
||||
Text(
|
||||
stringResource(id = R.string.ScreenLockSettingsFragment__start_screen_lock),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
modifier = Modifier.padding(top = 24.dp, bottom = 16.dp, start = 24.dp)
|
||||
)
|
||||
Column(Modifier.selectableGroup()) {
|
||||
var isCustomTime = true
|
||||
labels.zip(values).forEach { (label, seconds) ->
|
||||
val isSelected = seconds == state.screenLockActivityTimeout
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.defaultMinSize(minHeight = 56.dp)
|
||||
.selectable(
|
||||
selected = isSelected,
|
||||
onClick = { onTimeClicked(seconds) },
|
||||
role = Role.RadioButton
|
||||
)
|
||||
.padding(horizontal = 24.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
RadioButton(selected = isSelected, onClick = null)
|
||||
Text(
|
||||
text = label,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.padding(start = 16.dp)
|
||||
)
|
||||
isCustomTime = isCustomTime && !isSelected
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.height(56.dp)
|
||||
.selectable(
|
||||
selected = isCustomTime,
|
||||
onClick = onCustomTimeClicked,
|
||||
role = Role.RadioButton
|
||||
)
|
||||
.padding(horizontal = 24.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
RadioButton(selected = isCustomTime, onClick = null)
|
||||
Column(modifier = Modifier.padding(start = 16.dp)) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.ScreenLockSettingsFragment__custom_time),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
if (isCustomTime && state.screenLockActivityTimeout > 0) {
|
||||
Text(
|
||||
text = ExpirationUtil.getExpirationDisplayValue(LocalContext.current, state.screenLockActivityTimeout.toInt()),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SignalPreview
|
||||
@Composable
|
||||
fun ScreenLockScreenPreview() {
|
||||
Previews.Preview {
|
||||
ScreenLockScreen(
|
||||
state = ScreenLockSettingsState(true, 60),
|
||||
onChecked = {},
|
||||
onTimeClicked = {},
|
||||
onCustomTimeClicked = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.privacy.screenlock
|
||||
|
||||
/**
|
||||
* Information about the screen lock state. Used in [ScreenLockSettingsViewModel].
|
||||
*/
|
||||
data class ScreenLockSettingsState(
|
||||
val screenLock: Boolean = false,
|
||||
val screenLockActivityTimeout: Long = 0L
|
||||
)
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.privacy.screenlock
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
|
||||
/**
|
||||
* Maintains the state of the [ScreenLockSettingsFragment]
|
||||
*/
|
||||
class ScreenLockSettingsViewModel : ViewModel() {
|
||||
|
||||
private val _state = MutableStateFlow(getState())
|
||||
val state = _state.asStateFlow()
|
||||
|
||||
fun toggleScreenLock() {
|
||||
val enabled = !_state.value.screenLock
|
||||
SignalStore.settings.screenLockEnabled = enabled
|
||||
_state.update {
|
||||
it.copy(
|
||||
screenLock = enabled
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun setScreenLockTimeout(seconds: Long) {
|
||||
SignalStore.settings.screenLockTimeout = seconds
|
||||
_state.update {
|
||||
it.copy(
|
||||
screenLockActivityTimeout = seconds
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getState(): ScreenLockSettingsState {
|
||||
return ScreenLockSettingsState(
|
||||
screenLock = SignalStore.settings.screenLockEnabled,
|
||||
screenLockActivityTimeout = SignalStore.settings.screenLockTimeout
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -105,9 +105,10 @@ class DSLConfiguration {
|
||||
iconEnd: DSLSettingsIcon? = null,
|
||||
isEnabled: Boolean = true,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: (() -> Boolean)? = null
|
||||
onLongClick: (() -> Boolean)? = null,
|
||||
onDisabledClicked: () -> Unit = {}
|
||||
) {
|
||||
val preference = ClickPreference(title, summary, icon, iconEnd, isEnabled, onClick, onLongClick)
|
||||
val preference = ClickPreference(title, summary, icon, iconEnd, isEnabled, onClick, onLongClick, onDisabledClicked)
|
||||
children.add(preference)
|
||||
}
|
||||
|
||||
@@ -344,7 +345,8 @@ class ClickPreference(
|
||||
override val iconEnd: DSLSettingsIcon? = null,
|
||||
override val isEnabled: Boolean = true,
|
||||
val onClick: () -> Unit,
|
||||
val onLongClick: (() -> Boolean)? = null
|
||||
val onLongClick: (() -> Boolean)? = null,
|
||||
val onDisabledClicked: () -> Unit = {}
|
||||
) : PreferenceModel<ClickPreference>()
|
||||
|
||||
class LongClickPreference(
|
||||
|
||||
Reference in New Issue
Block a user