Add PNP settings.
This commit is contained in:
parent
f3693c966a
commit
45a04423b0
15 changed files with 425 additions and 167 deletions
|
@ -1,21 +1,16 @@
|
||||||
package org.thoughtcrime.securesms.components.settings.app.privacy
|
package org.thoughtcrime.securesms.components.settings.app.privacy
|
||||||
|
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.Context
|
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
import android.text.Spanned
|
|
||||||
import android.text.style.TextAppearanceSpan
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.biometric.BiometricManager
|
import androidx.biometric.BiometricManager
|
||||||
import androidx.biometric.BiometricPrompt
|
import androidx.biometric.BiometricPrompt
|
||||||
import androidx.biometric.BiometricPrompt.PromptInfo
|
import androidx.biometric.BiometricPrompt.PromptInfo
|
||||||
|
@ -41,8 +36,6 @@ import org.thoughtcrime.securesms.components.settings.PreferenceModel
|
||||||
import org.thoughtcrime.securesms.components.settings.PreferenceViewHolder
|
import org.thoughtcrime.securesms.components.settings.PreferenceViewHolder
|
||||||
import org.thoughtcrime.securesms.components.settings.configure
|
import org.thoughtcrime.securesms.components.settings.configure
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil
|
import org.thoughtcrime.securesms.crypto.MasterSecretUtil
|
||||||
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues
|
|
||||||
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues.PhoneNumberListingMode
|
|
||||||
import org.thoughtcrime.securesms.service.KeyCachingService
|
import org.thoughtcrime.securesms.service.KeyCachingService
|
||||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||||
import org.thoughtcrime.securesms.util.ConversationUtil
|
import org.thoughtcrime.securesms.util.ConversationUtil
|
||||||
|
@ -126,6 +119,19 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac
|
||||||
|
|
||||||
private fun getConfiguration(state: PrivacySettingsState): DSLConfiguration {
|
private fun getConfiguration(state: PrivacySettingsState): DSLConfiguration {
|
||||||
return configure {
|
return configure {
|
||||||
|
if (FeatureFlags.phoneNumberPrivacy()) {
|
||||||
|
clickPref(
|
||||||
|
title = DSLSettingsText.from(R.string.preferences_app_protection__phone_number),
|
||||||
|
summary = DSLSettingsText.from(R.string.preferences_app_protection__choose_who_can_see),
|
||||||
|
onClick = {
|
||||||
|
Navigation.findNavController(requireView())
|
||||||
|
.safeNavigate(R.id.action_privacySettingsFragment_to_phoneNumberPrivacySettingsFragment)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
dividerPref()
|
||||||
|
}
|
||||||
|
|
||||||
clickPref(
|
clickPref(
|
||||||
title = DSLSettingsText.from(R.string.PrivacySettingsFragment__blocked),
|
title = DSLSettingsText.from(R.string.PrivacySettingsFragment__blocked),
|
||||||
summary = DSLSettingsText.from(getString(R.string.PrivacySettingsFragment__d_contacts, state.blockedCount)),
|
summary = DSLSettingsText.from(getString(R.string.PrivacySettingsFragment__d_contacts, state.blockedCount)),
|
||||||
|
@ -137,28 +143,6 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac
|
||||||
|
|
||||||
dividerPref()
|
dividerPref()
|
||||||
|
|
||||||
if (FeatureFlags.phoneNumberPrivacy()) {
|
|
||||||
sectionHeaderPref(R.string.preferences_app_protection__who_can)
|
|
||||||
|
|
||||||
clickPref(
|
|
||||||
title = DSLSettingsText.from(R.string.preferences_app_protection__see_my_phone_number),
|
|
||||||
summary = DSLSettingsText.from(getWhoCanSeeMyPhoneNumberSummary(state.seeMyPhoneNumber)),
|
|
||||||
onClick = {
|
|
||||||
onSeeMyPhoneNumberClicked(state.seeMyPhoneNumber)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
clickPref(
|
|
||||||
title = DSLSettingsText.from(R.string.preferences_app_protection__find_me_by_phone_number),
|
|
||||||
summary = DSLSettingsText.from(getWhoCanFindMeByPhoneNumberSummary(state.findMeByPhoneNumber)),
|
|
||||||
onClick = {
|
|
||||||
onFindMyPhoneNumberClicked(state.findMeByPhoneNumber)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
dividerPref()
|
|
||||||
}
|
|
||||||
|
|
||||||
sectionHeaderPref(R.string.PrivacySettingsFragment__messaging)
|
sectionHeaderPref(R.string.PrivacySettingsFragment__messaging)
|
||||||
|
|
||||||
switchPref(
|
switchPref(
|
||||||
|
@ -389,117 +373,6 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@StringRes
|
|
||||||
private fun getWhoCanSeeMyPhoneNumberSummary(phoneNumberSharingMode: PhoneNumberPrivacyValues.PhoneNumberSharingMode): Int {
|
|
||||||
return when (phoneNumberSharingMode) {
|
|
||||||
PhoneNumberPrivacyValues.PhoneNumberSharingMode.EVERYONE -> R.string.PhoneNumberPrivacy_everyone
|
|
||||||
PhoneNumberPrivacyValues.PhoneNumberSharingMode.CONTACTS -> R.string.PhoneNumberPrivacy_my_contacts
|
|
||||||
PhoneNumberPrivacyValues.PhoneNumberSharingMode.NOBODY -> R.string.PhoneNumberPrivacy_nobody
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@StringRes
|
|
||||||
private fun getWhoCanFindMeByPhoneNumberSummary(phoneNumberListingMode: PhoneNumberListingMode): Int {
|
|
||||||
return when (phoneNumberListingMode) {
|
|
||||||
PhoneNumberListingMode.LISTED -> R.string.PhoneNumberPrivacy_everyone
|
|
||||||
PhoneNumberListingMode.UNLISTED -> R.string.PhoneNumberPrivacy_nobody
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onSeeMyPhoneNumberClicked(phoneNumberSharingMode: PhoneNumberPrivacyValues.PhoneNumberSharingMode) {
|
|
||||||
val value = arrayOf(phoneNumberSharingMode)
|
|
||||||
val items = items(requireContext())
|
|
||||||
val modes: List<PhoneNumberPrivacyValues.PhoneNumberSharingMode> = ArrayList(items.keys)
|
|
||||||
val modeStrings = items.values.toTypedArray()
|
|
||||||
val selectedMode = modes.indexOf(value[0])
|
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(requireActivity()).apply {
|
|
||||||
setTitle(R.string.preferences_app_protection__see_my_phone_number)
|
|
||||||
setCancelable(true)
|
|
||||||
setSingleChoiceItems(
|
|
||||||
modeStrings,
|
|
||||||
selectedMode
|
|
||||||
) { _: DialogInterface?, which: Int -> value[0] = modes[which] }
|
|
||||||
setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
|
||||||
val newSharingMode = value[0]
|
|
||||||
Log.i(
|
|
||||||
TAG,
|
|
||||||
String.format(
|
|
||||||
"PhoneNumberSharingMode changed to %s. Scheduling storage value sync",
|
|
||||||
newSharingMode
|
|
||||||
)
|
|
||||||
)
|
|
||||||
viewModel.setPhoneNumberSharingMode(value[0])
|
|
||||||
}
|
|
||||||
setNegativeButton(android.R.string.cancel, null)
|
|
||||||
show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun items(context: Context): Map<PhoneNumberPrivacyValues.PhoneNumberSharingMode, CharSequence> {
|
|
||||||
val map: MutableMap<PhoneNumberPrivacyValues.PhoneNumberSharingMode, CharSequence> = LinkedHashMap()
|
|
||||||
map[PhoneNumberPrivacyValues.PhoneNumberSharingMode.EVERYONE] = titleAndDescription(
|
|
||||||
context,
|
|
||||||
context.getString(R.string.PhoneNumberPrivacy_everyone),
|
|
||||||
context.getString(R.string.PhoneNumberPrivacy_everyone_see_description)
|
|
||||||
)
|
|
||||||
map[PhoneNumberPrivacyValues.PhoneNumberSharingMode.NOBODY] =
|
|
||||||
context.getString(R.string.PhoneNumberPrivacy_nobody)
|
|
||||||
return map
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun titleAndDescription(
|
|
||||||
context: Context,
|
|
||||||
header: String,
|
|
||||||
description: String
|
|
||||||
): CharSequence {
|
|
||||||
return SpannableStringBuilder().apply {
|
|
||||||
append("\n")
|
|
||||||
append(header)
|
|
||||||
append("\n")
|
|
||||||
setSpan(
|
|
||||||
TextAppearanceSpan(context, android.R.style.TextAppearance_Small),
|
|
||||||
length,
|
|
||||||
length,
|
|
||||||
Spanned.SPAN_INCLUSIVE_INCLUSIVE
|
|
||||||
)
|
|
||||||
append(description)
|
|
||||||
append("\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onFindMyPhoneNumberClicked(phoneNumberListingMode: PhoneNumberListingMode) {
|
|
||||||
val context = requireContext()
|
|
||||||
val value = arrayOf(phoneNumberListingMode)
|
|
||||||
MaterialAlertDialogBuilder(requireActivity()).apply {
|
|
||||||
setTitle(R.string.preferences_app_protection__find_me_by_phone_number)
|
|
||||||
setCancelable(true)
|
|
||||||
setSingleChoiceItems(
|
|
||||||
arrayOf(
|
|
||||||
titleAndDescription(
|
|
||||||
context,
|
|
||||||
context.getString(R.string.PhoneNumberPrivacy_everyone),
|
|
||||||
context.getString(R.string.PhoneNumberPrivacy_everyone_find_description)
|
|
||||||
),
|
|
||||||
context.getString(R.string.PhoneNumberPrivacy_nobody)
|
|
||||||
),
|
|
||||||
value[0].ordinal
|
|
||||||
) { _: DialogInterface?, which: Int -> value[0] = PhoneNumberListingMode.values()[which] }
|
|
||||||
setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
|
||||||
Log.i(
|
|
||||||
TAG,
|
|
||||||
String.format(
|
|
||||||
"PhoneNumberListingMode changed to %s. Scheduling storage value sync",
|
|
||||||
value[0]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
viewModel.setPhoneNumberListingMode(value[0])
|
|
||||||
}
|
|
||||||
setNegativeButton(android.R.string.cancel, null)
|
|
||||||
show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ValueClickPreference(
|
private class ValueClickPreference(
|
||||||
val value: DSLSettingsText,
|
val value: DSLSettingsText,
|
||||||
val clickPreference: ClickPreference
|
val clickPreference: ClickPreference
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
package org.thoughtcrime.securesms.components.settings.app.privacy
|
package org.thoughtcrime.securesms.components.settings.app.privacy
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues
|
|
||||||
|
|
||||||
data class PrivacySettingsState(
|
data class PrivacySettingsState(
|
||||||
val blockedCount: Int,
|
val blockedCount: Int,
|
||||||
val seeMyPhoneNumber: PhoneNumberPrivacyValues.PhoneNumberSharingMode,
|
|
||||||
val findMeByPhoneNumber: PhoneNumberPrivacyValues.PhoneNumberListingMode,
|
|
||||||
val readReceipts: Boolean,
|
val readReceipts: Boolean,
|
||||||
val typingIndicators: Boolean,
|
val typingIndicators: Boolean,
|
||||||
val screenLock: Boolean,
|
val screenLock: Boolean,
|
||||||
|
|
|
@ -4,14 +4,8 @@ import android.content.SharedPreferences
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||||
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob
|
|
||||||
import org.thoughtcrime.securesms.jobs.RefreshOwnProfileJob
|
|
||||||
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues
|
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
|
||||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper
|
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.thoughtcrime.securesms.util.livedata.Store
|
import org.thoughtcrime.securesms.util.livedata.Store
|
||||||
|
|
||||||
|
@ -58,20 +52,6 @@ class PrivacySettingsViewModel(
|
||||||
refresh()
|
refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setPhoneNumberSharingMode(phoneNumberSharingMode: PhoneNumberPrivacyValues.PhoneNumberSharingMode) {
|
|
||||||
SignalStore.phoneNumberPrivacy().phoneNumberSharingMode = phoneNumberSharingMode
|
|
||||||
SignalDatabase.recipients.markNeedsSync(Recipient.self().id)
|
|
||||||
StorageSyncHelper.scheduleSyncForDataChange()
|
|
||||||
refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setPhoneNumberListingMode(phoneNumberListingMode: PhoneNumberPrivacyValues.PhoneNumberListingMode) {
|
|
||||||
SignalStore.phoneNumberPrivacy().phoneNumberListingMode = phoneNumberListingMode
|
|
||||||
StorageSyncHelper.scheduleSyncForDataChange()
|
|
||||||
ApplicationDependencies.getJobManager().startChain(RefreshAttributesJob()).then(RefreshOwnProfileJob()).enqueue()
|
|
||||||
refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setIncognitoKeyboard(enabled: Boolean) {
|
fun setIncognitoKeyboard(enabled: Boolean) {
|
||||||
sharedPreferences.edit().putBoolean(TextSecurePreferences.INCOGNITO_KEYBORAD_PREF, enabled).apply()
|
sharedPreferences.edit().putBoolean(TextSecurePreferences.INCOGNITO_KEYBORAD_PREF, enabled).apply()
|
||||||
refresh()
|
refresh()
|
||||||
|
@ -106,8 +86,6 @@ class PrivacySettingsViewModel(
|
||||||
screenSecurity = TextSecurePreferences.isScreenSecurityEnabled(ApplicationDependencies.getApplication()),
|
screenSecurity = TextSecurePreferences.isScreenSecurityEnabled(ApplicationDependencies.getApplication()),
|
||||||
incognitoKeyboard = TextSecurePreferences.isIncognitoKeyboardEnabled(ApplicationDependencies.getApplication()),
|
incognitoKeyboard = TextSecurePreferences.isIncognitoKeyboardEnabled(ApplicationDependencies.getApplication()),
|
||||||
paymentLock = SignalStore.paymentsValues().paymentLock,
|
paymentLock = SignalStore.paymentsValues().paymentLock,
|
||||||
seeMyPhoneNumber = SignalStore.phoneNumberPrivacy().phoneNumberSharingMode,
|
|
||||||
findMeByPhoneNumber = SignalStore.phoneNumberPrivacy().phoneNumberListingMode,
|
|
||||||
isObsoletePasswordEnabled = !TextSecurePreferences.isPasswordDisabled(ApplicationDependencies.getApplication()),
|
isObsoletePasswordEnabled = !TextSecurePreferences.isPasswordDisabled(ApplicationDependencies.getApplication()),
|
||||||
isObsoletePasswordTimeoutEnabled = TextSecurePreferences.isPassphraseTimeoutEnabled(ApplicationDependencies.getApplication()),
|
isObsoletePasswordTimeoutEnabled = TextSecurePreferences.isPassphraseTimeoutEnabled(ApplicationDependencies.getApplication()),
|
||||||
obsoletePasswordTimeout = TextSecurePreferences.getPassphraseTimeoutInterval(ApplicationDependencies.getApplication()),
|
obsoletePasswordTimeout = TextSecurePreferences.getPassphraseTimeoutInterval(ApplicationDependencies.getApplication()),
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
package org.thoughtcrime.securesms.components.settings.app.privacy.pnp
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.dimensionResource
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import org.signal.core.ui.Dividers
|
||||||
|
import org.signal.core.ui.Rows
|
||||||
|
import org.signal.core.ui.Scaffolds
|
||||||
|
import org.signal.core.ui.Texts
|
||||||
|
import org.thoughtcrime.securesms.R
|
||||||
|
import org.thoughtcrime.securesms.compose.ComposeFragment
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues.PhoneNumberListingMode
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues.PhoneNumberSharingMode
|
||||||
|
|
||||||
|
class PhoneNumberPrivacySettingsFragment : ComposeFragment() {
|
||||||
|
|
||||||
|
private val viewModel: PhoneNumberPrivacySettingsViewModel by viewModels()
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun SheetContent() {
|
||||||
|
val state: PhoneNumberPrivacySettingsState by viewModel.state
|
||||||
|
val onNavigationClick: () -> Unit = remember {
|
||||||
|
{ findNavController().popBackStack() }
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffolds.Settings(
|
||||||
|
title = stringResource(id = R.string.preferences_app_protection__phone_number),
|
||||||
|
onNavigationClick = onNavigationClick,
|
||||||
|
painter = painterResource(id = R.drawable.ic_arrow_left_24),
|
||||||
|
navigationContentDescription = stringResource(id = R.string.Material3SearchToolbar__close)
|
||||||
|
) { contentPadding ->
|
||||||
|
Box(modifier = Modifier.padding(contentPadding)) {
|
||||||
|
LazyColumn {
|
||||||
|
item {
|
||||||
|
Texts.SectionHeader(
|
||||||
|
text = stringResource(id = R.string.PhoneNumberPrivacySettingsFragment__who_can_see_my_number)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Rows.RadioRow(
|
||||||
|
selected = state.seeMyPhoneNumber == PhoneNumberSharingMode.EVERYONE,
|
||||||
|
text = stringResource(id = R.string.PhoneNumberPrivacy_everyone),
|
||||||
|
modifier = Modifier.clickable(onClick = viewModel::setEveryoneCanSeeMyNumber)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Rows.RadioRow(
|
||||||
|
selected = state.seeMyPhoneNumber == PhoneNumberSharingMode.NOBODY,
|
||||||
|
text = stringResource(id = R.string.PhoneNumberPrivacy_nobody),
|
||||||
|
modifier = Modifier.clickable(onClick = viewModel::setNobodyCanSeeMyNumber)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
text = stringResource(
|
||||||
|
id = when (state.seeMyPhoneNumber) {
|
||||||
|
PhoneNumberSharingMode.EVERYONE -> R.string.PhoneNumberPrivacySettingsFragment__your_phone_number
|
||||||
|
PhoneNumberSharingMode.NOBODY -> R.string.PhoneNumberPrivacySettingsFragment__nobody_will_see
|
||||||
|
else -> error("Unexpected state $state")
|
||||||
|
}
|
||||||
|
),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter), vertical = 16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Dividers.Default()
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Texts.SectionHeader(text = stringResource(id = R.string.PhoneNumberPrivacySettingsFragment__who_can_find_me_by_number))
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Rows.RadioRow(
|
||||||
|
selected = state.findMeByPhoneNumber == PhoneNumberListingMode.LISTED,
|
||||||
|
text = stringResource(id = R.string.PhoneNumberPrivacy_everyone),
|
||||||
|
modifier = Modifier.clickable(onClick = viewModel::setEveryoneCanFindMeByMyNumber)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.seeMyPhoneNumber == PhoneNumberSharingMode.NOBODY) {
|
||||||
|
item {
|
||||||
|
Rows.RadioRow(
|
||||||
|
selected = state.findMeByPhoneNumber == PhoneNumberListingMode.UNLISTED,
|
||||||
|
text = stringResource(id = R.string.PhoneNumberPrivacy_nobody),
|
||||||
|
modifier = Modifier.clickable(onClick = viewModel::setNobodyCanFindMeByMyNumber)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
text = stringResource(
|
||||||
|
id = when (state.findMeByPhoneNumber) {
|
||||||
|
PhoneNumberListingMode.UNLISTED -> R.string.WhoCanSeeMyPhoneNumberFragment__nobody_on_signal
|
||||||
|
PhoneNumberListingMode.LISTED -> R.string.WhoCanSeeMyPhoneNumberFragment__anyone_who_has
|
||||||
|
}
|
||||||
|
),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter), vertical = 16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package org.thoughtcrime.securesms.components.settings.app.privacy.pnp
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues
|
||||||
|
|
||||||
|
data class PhoneNumberPrivacySettingsState(
|
||||||
|
val seeMyPhoneNumber: PhoneNumberPrivacyValues.PhoneNumberSharingMode,
|
||||||
|
val findMeByPhoneNumber: PhoneNumberPrivacyValues.PhoneNumberListingMode
|
||||||
|
)
|
|
@ -0,0 +1,63 @@
|
||||||
|
package org.thoughtcrime.securesms.components.settings.app.privacy.pnp
|
||||||
|
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||||
|
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob
|
||||||
|
import org.thoughtcrime.securesms.jobs.RefreshOwnProfileJob
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues.PhoneNumberListingMode
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues.PhoneNumberSharingMode
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
import org.thoughtcrime.securesms.storage.StorageSyncHelper
|
||||||
|
|
||||||
|
class PhoneNumberPrivacySettingsViewModel : ViewModel() {
|
||||||
|
|
||||||
|
private val _state = mutableStateOf(
|
||||||
|
PhoneNumberPrivacySettingsState(
|
||||||
|
seeMyPhoneNumber = SignalStore.phoneNumberPrivacy().phoneNumberSharingMode,
|
||||||
|
findMeByPhoneNumber = SignalStore.phoneNumberPrivacy().phoneNumberListingMode
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val state: State<PhoneNumberPrivacySettingsState> = _state
|
||||||
|
|
||||||
|
fun setNobodyCanSeeMyNumber() {
|
||||||
|
setPhoneNumberSharingMode(PhoneNumberSharingMode.NOBODY)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setEveryoneCanSeeMyNumber() {
|
||||||
|
setPhoneNumberSharingMode(PhoneNumberSharingMode.EVERYONE)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setNobodyCanFindMeByMyNumber() {
|
||||||
|
setPhoneNumberListingMode(PhoneNumberListingMode.UNLISTED)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setEveryoneCanFindMeByMyNumber() {
|
||||||
|
setPhoneNumberListingMode(PhoneNumberListingMode.LISTED)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setPhoneNumberSharingMode(phoneNumberSharingMode: PhoneNumberSharingMode) {
|
||||||
|
SignalStore.phoneNumberPrivacy().phoneNumberSharingMode = phoneNumberSharingMode
|
||||||
|
SignalDatabase.recipients.markNeedsSync(Recipient.self().id)
|
||||||
|
StorageSyncHelper.scheduleSyncForDataChange()
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setPhoneNumberListingMode(phoneNumberListingMode: PhoneNumberListingMode) {
|
||||||
|
SignalStore.phoneNumberPrivacy().phoneNumberListingMode = phoneNumberListingMode
|
||||||
|
StorageSyncHelper.scheduleSyncForDataChange()
|
||||||
|
ApplicationDependencies.getJobManager().startChain(RefreshAttributesJob()).then(RefreshOwnProfileJob()).enqueue()
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refresh() {
|
||||||
|
_state.value = PhoneNumberPrivacySettingsState(
|
||||||
|
seeMyPhoneNumber = SignalStore.phoneNumberPrivacy().phoneNumberSharingMode,
|
||||||
|
findMeByPhoneNumber = SignalStore.phoneNumberPrivacy().phoneNumberListingMode
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,6 +35,13 @@
|
||||||
app:exitAnim="@anim/fragment_open_exit"
|
app:exitAnim="@anim/fragment_open_exit"
|
||||||
app:popEnterAnim="@anim/fragment_close_enter"
|
app:popEnterAnim="@anim/fragment_close_enter"
|
||||||
app:popExitAnim="@anim/fragment_close_exit" />
|
app:popExitAnim="@anim/fragment_close_exit" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_privacySettingsFragment_to_phoneNumberPrivacySettingsFragment"
|
||||||
|
app:destination="@id/phoneNumberPrivacySettingsFragment"
|
||||||
|
app:enterAnim="@anim/fragment_open_enter"
|
||||||
|
app:exitAnim="@anim/fragment_open_exit"
|
||||||
|
app:popEnterAnim="@anim/fragment_close_enter"
|
||||||
|
app:popExitAnim="@anim/fragment_close_exit" />
|
||||||
|
|
||||||
</fragment>
|
</fragment>
|
||||||
|
|
||||||
|
@ -48,6 +55,11 @@
|
||||||
android:name="org.thoughtcrime.securesms.components.settings.app.privacy.advanced.AdvancedPrivacySettingsFragment"
|
android:name="org.thoughtcrime.securesms.components.settings.app.privacy.advanced.AdvancedPrivacySettingsFragment"
|
||||||
android:label="advanced_privacy_settings_fragment" />
|
android:label="advanced_privacy_settings_fragment" />
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/phoneNumberPrivacySettingsFragment"
|
||||||
|
android:name="org.thoughtcrime.securesms.components.settings.app.privacy.pnp.PhoneNumberPrivacySettingsFragment"
|
||||||
|
android:label="phone_number_privacy_settings_fragment" />
|
||||||
|
|
||||||
<include app:graph="@navigation/app_settings_expire_timer" />
|
<include app:graph="@navigation/app_settings_expire_timer" />
|
||||||
|
|
||||||
</navigation>
|
</navigation>
|
||||||
|
|
|
@ -3458,6 +3458,18 @@
|
||||||
<string name="BackupUtil_unknown">Unknown</string>
|
<string name="BackupUtil_unknown">Unknown</string>
|
||||||
<string name="preferences_app_protection__see_my_phone_number">See my phone number</string>
|
<string name="preferences_app_protection__see_my_phone_number">See my phone number</string>
|
||||||
<string name="preferences_app_protection__find_me_by_phone_number">Find me by phone number</string>
|
<string name="preferences_app_protection__find_me_by_phone_number">Find me by phone number</string>
|
||||||
|
<!-- Phone number heading displayed as a screen title -->
|
||||||
|
<string name="preferences_app_protection__phone_number">Phone number</string>
|
||||||
|
<!-- Subtext below option to launch into phone number privacy settings screen -->
|
||||||
|
<string name="preferences_app_protection__choose_who_can_see">Choose who can see your phone number and who can contact you on Signal with it.</string>
|
||||||
|
<!-- Section title above two radio buttons for enabling and disabling phone number display -->
|
||||||
|
<string name="PhoneNumberPrivacySettingsFragment__who_can_see_my_number">Who can see my number</string>
|
||||||
|
<!-- Subtext below radio buttons when who can see my number is set to nobody -->
|
||||||
|
<string name="PhoneNumberPrivacySettingsFragment__nobody_will_see">Nobody will see your phone number on Signal</string>
|
||||||
|
<!-- Section title above two radio buttons for enabling and disabling whether users can find me by my phone number -->
|
||||||
|
<string name="PhoneNumberPrivacySettingsFragment__who_can_find_me_by_number">Who can find me by number</string>
|
||||||
|
<!-- Subtext below radio buttons when who can see my number is set to everyone -->
|
||||||
|
<string name="PhoneNumberPrivacySettingsFragment__your_phone_number">Your phone number will be visible to people and groups you message. People who have your number in their phone contacts will also see it on Signal.</string>
|
||||||
<string name="PhoneNumberPrivacy_everyone">Everyone</string>
|
<string name="PhoneNumberPrivacy_everyone">Everyone</string>
|
||||||
<string name="PhoneNumberPrivacy_my_contacts">My contacts</string>
|
<string name="PhoneNumberPrivacy_my_contacts">My contacts</string>
|
||||||
<string name="PhoneNumberPrivacy_nobody">Nobody</string>
|
<string name="PhoneNumberPrivacy_nobody">Nobody</string>
|
||||||
|
|
32
core-ui/src/main/java/org/signal/core/ui/Dividers.kt
Normal file
32
core-ui/src/main/java/org/signal/core/ui/Dividers.kt
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package org.signal.core.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import org.signal.core.ui.theme.SignalTheme
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thin divider lines for separating content.
|
||||||
|
*/
|
||||||
|
object Dividers {
|
||||||
|
@Composable
|
||||||
|
fun Default(modifier: Modifier = Modifier) {
|
||||||
|
Divider(
|
||||||
|
thickness = 1.5.dp,
|
||||||
|
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
|
modifier = modifier.padding(vertical = 16.25.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun DefaultPreview() {
|
||||||
|
SignalTheme(isDarkMode = false) {
|
||||||
|
Dividers.Default()
|
||||||
|
}
|
||||||
|
}
|
55
core-ui/src/main/java/org/signal/core/ui/Rows.kt
Normal file
55
core-ui/src/main/java/org/signal/core/ui/Rows.kt
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
package org.signal.core.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.dimensionResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import org.signal.core.ui.theme.SignalTheme
|
||||||
|
|
||||||
|
object Rows {
|
||||||
|
/**
|
||||||
|
* A row consisting of a radio button and text, which takes up the full
|
||||||
|
* width of the screen.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun RadioRow(
|
||||||
|
selected: Boolean,
|
||||||
|
text: String,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(
|
||||||
|
horizontal = dimensionResource(id = R.dimen.core_ui__gutter),
|
||||||
|
vertical = 16.dp
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
RadioButton(
|
||||||
|
selected = selected,
|
||||||
|
onClick = null,
|
||||||
|
modifier = Modifier.padding(end = 24.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
style = MaterialTheme.typography.bodyLarge
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun RadioRowPreview() {
|
||||||
|
SignalTheme(isDarkMode = false) {
|
||||||
|
Rows.RadioRow(true, "RadioRow")
|
||||||
|
}
|
||||||
|
}
|
54
core-ui/src/main/java/org/signal/core/ui/Scaffolds.kt
Normal file
54
core-ui/src/main/java/org/signal/core/ui/Scaffolds.kt
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package org.signal.core.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
object Scaffolds {
|
||||||
|
@Composable
|
||||||
|
fun Settings(
|
||||||
|
title: String,
|
||||||
|
onNavigationClick: () -> Unit,
|
||||||
|
painter: Painter,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
navigationContentDescription: String? = null,
|
||||||
|
content: @Composable (PaddingValues) -> Unit
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
},
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(
|
||||||
|
onClick = onNavigationClick,
|
||||||
|
Modifier.padding(end = 16.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painter,
|
||||||
|
contentDescription = navigationContentDescription
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = modifier,
|
||||||
|
content = content
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
40
core-ui/src/main/java/org/signal/core/ui/Texts.kt
Normal file
40
core-ui/src/main/java/org/signal/core/ui/Texts.kt
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package org.signal.core.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.dimensionResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import org.signal.core.ui.theme.SignalTheme
|
||||||
|
|
||||||
|
object Texts {
|
||||||
|
/**
|
||||||
|
* Header row for settings pages.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun SectionHeader(
|
||||||
|
text: String,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
modifier = modifier
|
||||||
|
.padding(
|
||||||
|
horizontal = dimensionResource(id = R.dimen.core_ui__gutter)
|
||||||
|
)
|
||||||
|
.padding(top = 16.dp, bottom = 12.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun SectionHeaderPreview() {
|
||||||
|
SignalTheme(isDarkMode = false) {
|
||||||
|
Texts.SectionHeader("Header")
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.text.font.FontStyle
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
private val typography = Typography().apply {
|
private val typography = Typography().run {
|
||||||
copy(
|
copy(
|
||||||
headlineLarge = headlineLarge.copy(
|
headlineLarge = headlineLarge.copy(
|
||||||
lineHeight = 40.sp,
|
lineHeight = 40.sp,
|
||||||
|
|
4
core-ui/src/main/res/values-sw360dp/dimens.xml
Normal file
4
core-ui/src/main/res/values-sw360dp/dimens.xml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<dimen name="core_ui__gutter">24dp</dimen>
|
||||||
|
</resources>
|
4
core-ui/src/main/res/values/dimens.xml
Normal file
4
core-ui/src/main/res/values/dimens.xml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<dimen name="core_ui__gutter">16dp</dimen>
|
||||||
|
</resources>
|
Loading…
Add table
Reference in a new issue