diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsFragment.kt index d9d42ad02a..efbb6a3336 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsFragment.kt @@ -1,21 +1,16 @@ package org.thoughtcrime.securesms.components.settings.app.privacy import android.content.ActivityNotFoundException -import android.content.Context -import android.content.DialogInterface import android.content.Intent import android.os.Build import android.os.Bundle import android.provider.Settings import android.text.SpannableStringBuilder -import android.text.Spanned -import android.text.style.TextAppearanceSpan import android.view.View import android.view.WindowManager import android.widget.TextView import android.widget.Toast import androidx.activity.result.ActivityResultLauncher -import androidx.annotation.StringRes import androidx.biometric.BiometricManager import androidx.biometric.BiometricPrompt 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.configure 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.util.CommunicationActions import org.thoughtcrime.securesms.util.ConversationUtil @@ -126,6 +119,19 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac private fun getConfiguration(state: PrivacySettingsState): DSLConfiguration { 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( title = DSLSettingsText.from(R.string.PrivacySettingsFragment__blocked), summary = DSLSettingsText.from(getString(R.string.PrivacySettingsFragment__d_contacts, state.blockedCount)), @@ -137,28 +143,6 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac 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) 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 = 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 { - val map: MutableMap = 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( val value: DSLSettingsText, val clickPreference: ClickPreference diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsState.kt index f2c0ecf3b5..06c314db15 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsState.kt @@ -1,11 +1,7 @@ package org.thoughtcrime.securesms.components.settings.app.privacy -import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues - data class PrivacySettingsState( val blockedCount: Int, - val seeMyPhoneNumber: PhoneNumberPrivacyValues.PhoneNumberSharingMode, - val findMeByPhoneNumber: PhoneNumberPrivacyValues.PhoneNumberListingMode, val readReceipts: Boolean, val typingIndicators: Boolean, val screenLock: Boolean, diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsViewModel.kt index cfbf7f05e7..dcaa3019e0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsViewModel.kt @@ -4,14 +4,8 @@ import android.content.SharedPreferences import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider -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 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.livedata.Store @@ -58,20 +52,6 @@ class PrivacySettingsViewModel( 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) { sharedPreferences.edit().putBoolean(TextSecurePreferences.INCOGNITO_KEYBORAD_PREF, enabled).apply() refresh() @@ -106,8 +86,6 @@ class PrivacySettingsViewModel( screenSecurity = TextSecurePreferences.isScreenSecurityEnabled(ApplicationDependencies.getApplication()), incognitoKeyboard = TextSecurePreferences.isIncognitoKeyboardEnabled(ApplicationDependencies.getApplication()), paymentLock = SignalStore.paymentsValues().paymentLock, - seeMyPhoneNumber = SignalStore.phoneNumberPrivacy().phoneNumberSharingMode, - findMeByPhoneNumber = SignalStore.phoneNumberPrivacy().phoneNumberListingMode, isObsoletePasswordEnabled = !TextSecurePreferences.isPasswordDisabled(ApplicationDependencies.getApplication()), isObsoletePasswordTimeoutEnabled = TextSecurePreferences.isPassphraseTimeoutEnabled(ApplicationDependencies.getApplication()), obsoletePasswordTimeout = TextSecurePreferences.getPassphraseTimeoutInterval(ApplicationDependencies.getApplication()), diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/pnp/PhoneNumberPrivacySettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/pnp/PhoneNumberPrivacySettingsFragment.kt new file mode 100644 index 0000000000..13071f0522 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/pnp/PhoneNumberPrivacySettingsFragment.kt @@ -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) + ) + } + } + } + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/pnp/PhoneNumberPrivacySettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/pnp/PhoneNumberPrivacySettingsState.kt new file mode 100644 index 0000000000..184726abb5 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/pnp/PhoneNumberPrivacySettingsState.kt @@ -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 +) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/pnp/PhoneNumberPrivacySettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/pnp/PhoneNumberPrivacySettingsViewModel.kt new file mode 100644 index 0000000000..761b1b7bef --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/pnp/PhoneNumberPrivacySettingsViewModel.kt @@ -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 = _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 + ) + } +} diff --git a/app/src/main/res/navigation/privacy_settings.xml b/app/src/main/res/navigation/privacy_settings.xml index 85c8c83410..b0448cd860 100644 --- a/app/src/main/res/navigation/privacy_settings.xml +++ b/app/src/main/res/navigation/privacy_settings.xml @@ -35,6 +35,13 @@ app:exitAnim="@anim/fragment_open_exit" app:popEnterAnim="@anim/fragment_close_enter" app:popExitAnim="@anim/fragment_close_exit" /> + @@ -48,6 +55,11 @@ android:name="org.thoughtcrime.securesms.components.settings.app.privacy.advanced.AdvancedPrivacySettingsFragment" android:label="advanced_privacy_settings_fragment" /> + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9f47c38730..d9cf4556c1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -3458,6 +3458,18 @@ Unknown See my phone number Find me by phone number + + Phone number + + Choose who can see your phone number and who can contact you on Signal with it. + + Who can see my number + + Nobody will see your phone number on Signal + + Who can find me by 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. Everyone My contacts Nobody diff --git a/core-ui/src/main/java/org/signal/core/ui/Dividers.kt b/core-ui/src/main/java/org/signal/core/ui/Dividers.kt new file mode 100644 index 0000000000..a64df564d9 --- /dev/null +++ b/core-ui/src/main/java/org/signal/core/ui/Dividers.kt @@ -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() + } +} diff --git a/core-ui/src/main/java/org/signal/core/ui/Rows.kt b/core-ui/src/main/java/org/signal/core/ui/Rows.kt new file mode 100644 index 0000000000..2d4173ff0d --- /dev/null +++ b/core-ui/src/main/java/org/signal/core/ui/Rows.kt @@ -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") + } +} diff --git a/core-ui/src/main/java/org/signal/core/ui/Scaffolds.kt b/core-ui/src/main/java/org/signal/core/ui/Scaffolds.kt new file mode 100644 index 0000000000..0f1458a62a --- /dev/null +++ b/core-ui/src/main/java/org/signal/core/ui/Scaffolds.kt @@ -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 + ) + } +} diff --git a/core-ui/src/main/java/org/signal/core/ui/Texts.kt b/core-ui/src/main/java/org/signal/core/ui/Texts.kt new file mode 100644 index 0000000000..662abea6f2 --- /dev/null +++ b/core-ui/src/main/java/org/signal/core/ui/Texts.kt @@ -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") + } +} diff --git a/core-ui/src/main/java/org/signal/core/ui/theme/SignalTheme.kt b/core-ui/src/main/java/org/signal/core/ui/theme/SignalTheme.kt index d1fe0df9d1..0c0539cd18 100644 --- a/core-ui/src/main/java/org/signal/core/ui/theme/SignalTheme.kt +++ b/core-ui/src/main/java/org/signal/core/ui/theme/SignalTheme.kt @@ -11,7 +11,7 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.unit.sp -private val typography = Typography().apply { +private val typography = Typography().run { copy( headlineLarge = headlineLarge.copy( lineHeight = 40.sp, diff --git a/core-ui/src/main/res/values-sw360dp/dimens.xml b/core-ui/src/main/res/values-sw360dp/dimens.xml new file mode 100644 index 0000000000..88ea03c837 --- /dev/null +++ b/core-ui/src/main/res/values-sw360dp/dimens.xml @@ -0,0 +1,4 @@ + + + 24dp + \ No newline at end of file diff --git a/core-ui/src/main/res/values/dimens.xml b/core-ui/src/main/res/values/dimens.xml new file mode 100644 index 0000000000..0411eef7bb --- /dev/null +++ b/core-ui/src/main/res/values/dimens.xml @@ -0,0 +1,4 @@ + + + 16dp + \ No newline at end of file