mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-23 03:05:26 +00:00
Add polish to usernames UX.
This commit is contained in:
committed by
Nicholas Tinsley
parent
ec96b4e3aa
commit
38d5d3ad1b
@@ -42,6 +42,7 @@ import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
import org.thoughtcrime.securesms.util.views.Stub
|
||||
import org.thoughtcrime.securesms.util.visible
|
||||
|
||||
class AppSettingsFragment : DSLSettingsFragment(
|
||||
titleId = R.string.text_secure_normal__menu_settings,
|
||||
@@ -340,6 +341,7 @@ class AppSettingsFragment : DSLSettingsFragment(
|
||||
private val aboutView: EmojiTextView = itemView.findViewById(R.id.about)
|
||||
private val badgeView: BadgeImageView = itemView.findViewById(R.id.badge)
|
||||
private val qrButton: View = itemView.findViewById(R.id.qr_button)
|
||||
private val usernameView: TextView = itemView.findViewById(R.id.username)
|
||||
|
||||
init {
|
||||
aboutView.setOverflowText(" ")
|
||||
@@ -352,6 +354,8 @@ class AppSettingsFragment : DSLSettingsFragment(
|
||||
|
||||
titleView.text = model.recipient.profileName.toString()
|
||||
summaryView.text = PhoneNumberFormatter.prettyPrint(model.recipient.requireE164())
|
||||
usernameView.text = model.recipient.username.orElse("")
|
||||
usernameView.visible = model.recipient.username.isPresent
|
||||
avatarView.setRecipient(Recipient.self())
|
||||
badgeView.setBadgeFromRecipient(Recipient.self())
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import androidx.compose.ui.graphics.Color
|
||||
enum class UsernameQrCodeColorScheme(
|
||||
val borderColor: Color,
|
||||
val foregroundColor: Color,
|
||||
val backgroundColor: Color,
|
||||
val textColor: Color = Color.White,
|
||||
val outlineColor: Color = Color.Transparent,
|
||||
private val key: String
|
||||
@@ -15,11 +16,13 @@ enum class UsernameQrCodeColorScheme(
|
||||
Blue(
|
||||
borderColor = Color(0xFF506ECD),
|
||||
foregroundColor = Color(0xFF2449C0),
|
||||
backgroundColor = Color(0xFFEDF0FA),
|
||||
key = "blue"
|
||||
),
|
||||
White(
|
||||
borderColor = Color(0xFFFFFFFF),
|
||||
foregroundColor = Color(0xFF000000),
|
||||
backgroundColor = Color(0xFFF5F5F5),
|
||||
textColor = Color.Black,
|
||||
outlineColor = Color(0xFFE9E9E9),
|
||||
key = "white"
|
||||
@@ -27,31 +30,37 @@ enum class UsernameQrCodeColorScheme(
|
||||
Grey(
|
||||
borderColor = Color(0xFF6A6C74),
|
||||
foregroundColor = Color(0xFF464852),
|
||||
backgroundColor = Color(0xFFF0F0F1),
|
||||
key = "grey"
|
||||
),
|
||||
Tan(
|
||||
borderColor = Color(0xFFBBB29A),
|
||||
foregroundColor = Color(0xFF73694F),
|
||||
backgroundColor = Color(0xFFF6F5F2),
|
||||
key = "tan"
|
||||
),
|
||||
Green(
|
||||
borderColor = Color(0xFF97AA89),
|
||||
foregroundColor = Color(0xFF55733F),
|
||||
backgroundColor = Color(0xFFF2F5F0),
|
||||
key = "green"
|
||||
),
|
||||
Orange(
|
||||
borderColor = Color(0xFFDE7134),
|
||||
foregroundColor = Color(0xFFDA6C2E),
|
||||
backgroundColor = Color(0xFFFCF1EB),
|
||||
key = "orange"
|
||||
),
|
||||
Pink(
|
||||
borderColor = Color(0xFFEA7B9D),
|
||||
foregroundColor = Color(0xFFBB617B),
|
||||
backgroundColor = Color(0xFFFCF1F5),
|
||||
key = "pink"
|
||||
),
|
||||
Purple(
|
||||
borderColor = Color(0xFF9E7BE9),
|
||||
foregroundColor = Color(0xFF7651C5),
|
||||
backgroundColor = Color(0xFFF5F3FA),
|
||||
key = "purple"
|
||||
);
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
@@ -56,6 +55,7 @@ import com.google.accompanist.permissions.rememberPermissionState
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.signal.core.ui.Buttons
|
||||
import org.signal.core.ui.Dialogs
|
||||
import org.signal.core.ui.Snackbars
|
||||
import org.signal.core.ui.theme.SignalTheme
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable
|
||||
import org.thoughtcrime.securesms.R
|
||||
@@ -89,7 +89,9 @@ class UsernameLinkSettingsFragment : ComposeFragment() {
|
||||
val navController: NavController by remember { mutableStateOf(findNavController()) }
|
||||
var showResetDialog: Boolean by remember { mutableStateOf(false) }
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
||||
val cameraPermissionState: PermissionState = rememberPermissionState(permission = android.Manifest.permission.CAMERA)
|
||||
val cameraPermissionState: PermissionState = rememberPermissionState(permission = android.Manifest.permission.CAMERA) {
|
||||
viewModel.onTabSelected(ActiveTab.Scan)
|
||||
}
|
||||
val linkCopiedEvent: UUID? by viewModel.linkCopiedEvent
|
||||
|
||||
val linkCopiedString = stringResource(R.string.UsernameLinkSettings_link_copied_toast)
|
||||
@@ -101,7 +103,7 @@ class UsernameLinkSettingsFragment : ComposeFragment() {
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||
snackbarHost = { Snackbars.Host(snackbarHostState) },
|
||||
topBar = {
|
||||
TopAppBarContent(
|
||||
activeTab = state.activeTab,
|
||||
@@ -123,6 +125,7 @@ class UsernameLinkSettingsFragment : ComposeFragment() {
|
||||
enter = slideInHorizontally(initialOffsetX = { fullWidth -> -fullWidth }),
|
||||
exit = slideOutHorizontally(targetOffsetX = { fullWidth -> -fullWidth })
|
||||
) {
|
||||
val helpText = stringResource(id = R.string.UsernameLinkSettings_scan_this_qr_code)
|
||||
UsernameLinkShareScreen(
|
||||
state = state,
|
||||
snackbarHostState = snackbarHostState,
|
||||
@@ -130,7 +133,7 @@ class UsernameLinkSettingsFragment : ComposeFragment() {
|
||||
modifier = Modifier.padding(contentPadding),
|
||||
navController = navController,
|
||||
onShareBadge = {
|
||||
shareQrBadge(viewModel.generateQrCodeImage())
|
||||
shareQrBadge(viewModel.generateQrCodeImage(helpText))
|
||||
},
|
||||
onResetClicked = { showResetDialog = true },
|
||||
onLinkResultHandled = { viewModel.onUsernameLinkResetResultHandled() }
|
||||
|
||||
@@ -10,6 +10,9 @@ import android.graphics.Rect
|
||||
import android.graphics.RectF
|
||||
import android.graphics.Typeface
|
||||
import android.os.Build
|
||||
import android.text.Layout
|
||||
import android.text.StaticLayout
|
||||
import android.text.TextPaint
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -17,8 +20,10 @@ import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Canvas
|
||||
import androidx.compose.ui.graphics.drawscope.CanvasDrawScope
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.graphics.withSave
|
||||
import androidx.compose.ui.unit.Density
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.core.graphics.withTranslation
|
||||
import androidx.lifecycle.ViewModel
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
@@ -122,7 +127,9 @@ class UsernameLinkSettingsViewModel : ViewModel() {
|
||||
val components: Optional<UsernameLinkComponents> = when (result) {
|
||||
is UsernameLinkResetResult.Success -> Optional.of(result.components)
|
||||
is UsernameLinkResetResult.NetworkError -> Optional.empty()
|
||||
else -> { usernameLink.value ?: Optional.empty() }
|
||||
else -> {
|
||||
usernameLink.value ?: Optional.empty()
|
||||
}
|
||||
}
|
||||
|
||||
_state.value = _state.value.copy(
|
||||
@@ -200,7 +207,7 @@ class UsernameLinkSettingsViewModel : ViewModel() {
|
||||
*
|
||||
* I hate this as much as you do.
|
||||
*/
|
||||
fun generateQrCodeImage(): Bitmap? {
|
||||
fun generateQrCodeImage(helpText: String): Bitmap? {
|
||||
val state: UsernameLinkSettingsState = _state.value
|
||||
|
||||
if (state.qrCodeState !is QrCodeState.Present) {
|
||||
@@ -210,12 +217,16 @@ class UsernameLinkSettingsViewModel : ViewModel() {
|
||||
|
||||
val qrCodeData: QrCodeData = state.qrCodeState.data
|
||||
|
||||
val width = 480
|
||||
val height = 525
|
||||
val qrSize = 300f
|
||||
val qrPadding = 25f
|
||||
val borderSizeX = 64f
|
||||
val borderSizeY = 52f
|
||||
val width = 424
|
||||
val height = 576
|
||||
val backgroundPadHorizontal = 64f
|
||||
val backgroundPadVertical = 80f
|
||||
val qrBorderWidth = width - (backgroundPadHorizontal * 2)
|
||||
val qrBorderHeight = 324f
|
||||
val qrSize = 184f
|
||||
val qrPadding = 16f
|
||||
val borderSizeX = 40f
|
||||
val borderSizeY = 32f
|
||||
|
||||
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).apply {
|
||||
eraseColor(Color.TRANSPARENT)
|
||||
@@ -225,56 +236,70 @@ class UsernameLinkSettingsViewModel : ViewModel() {
|
||||
val composeCanvas = Canvas(androidCanvas)
|
||||
val canvasDrawScope = CanvasDrawScope()
|
||||
|
||||
// Draw the background
|
||||
androidCanvas.drawRoundRect(0f, 0f, width.toFloat(), height.toFloat(), 30f, 30f, Paint().apply { color = state.qrCodeColorScheme.borderColor.toArgb() })
|
||||
androidCanvas.drawRoundRect(borderSizeX, borderSizeY, borderSizeX + qrSize + qrPadding * 2, borderSizeY + qrSize + qrPadding * 2, 15f, 15f, Paint().apply { color = Color.WHITE })
|
||||
androidCanvas.drawRoundRect(
|
||||
borderSizeX,
|
||||
borderSizeY,
|
||||
borderSizeX + qrSize + qrPadding * 2,
|
||||
borderSizeY + qrSize + qrPadding * 2,
|
||||
15f,
|
||||
15f,
|
||||
Paint().apply {
|
||||
color = state.qrCodeColorScheme.outlineColor.toArgb()
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = 4f
|
||||
}
|
||||
)
|
||||
// Background
|
||||
androidCanvas.drawColor(state.qrCodeColorScheme.backgroundColor.toArgb())
|
||||
|
||||
// Draw the QR code
|
||||
composeCanvas.translate((width / 2) - (qrSize / 2), 80f)
|
||||
canvasDrawScope.draw(
|
||||
density = object : Density {
|
||||
override val density: Float = 1f
|
||||
override val fontScale: Float = 1f
|
||||
},
|
||||
layoutDirection = LayoutDirection.Ltr,
|
||||
canvas = composeCanvas,
|
||||
size = Size(qrSize, qrSize)
|
||||
) {
|
||||
drawQr(
|
||||
data = qrCodeData,
|
||||
foregroundColor = state.qrCodeColorScheme.foregroundColor,
|
||||
backgroundColor = state.qrCodeColorScheme.borderColor,
|
||||
deadzonePercent = 0.35f,
|
||||
logo = null
|
||||
// QR Border
|
||||
androidCanvas.withTranslation(x = backgroundPadHorizontal, y = backgroundPadVertical) {
|
||||
drawRoundRect(0f, 0f, qrBorderWidth, qrBorderHeight, 30f, 30f, Paint().apply { color = state.qrCodeColorScheme.borderColor.toArgb() })
|
||||
|
||||
drawRoundRect(borderSizeX, borderSizeY, borderSizeX + qrSize + qrPadding * 2, borderSizeY + qrSize + qrPadding * 2, 15f, 15f, Paint().apply { color = Color.WHITE })
|
||||
drawRoundRect(
|
||||
borderSizeX,
|
||||
borderSizeY,
|
||||
borderSizeX + qrSize + qrPadding * 2,
|
||||
borderSizeY + qrSize + qrPadding * 2,
|
||||
15f,
|
||||
15f,
|
||||
Paint().apply {
|
||||
color = state.qrCodeColorScheme.outlineColor.toArgb()
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = 4f
|
||||
}
|
||||
)
|
||||
}
|
||||
composeCanvas.translate(-90f, -80f)
|
||||
|
||||
// Draw the signal logo -- unfortunately can't have the normal QR code drawing handle it because it requires a composable ImageBitmap
|
||||
BitmapFactory.decodeResource(ApplicationDependencies.getApplication().resources, R.drawable.qrcode_logo).also { logoBitmap ->
|
||||
val tintedPaint = Paint().apply {
|
||||
colorFilter = PorterDuffColorFilter(state.qrCodeColorScheme.foregroundColor.toArgb(), PorterDuff.Mode.SRC_IN)
|
||||
// Draw the QR code
|
||||
composeCanvas.translate((qrBorderWidth / 2) - (qrSize / 2), borderSizeY + qrPadding)
|
||||
|
||||
composeCanvas.withSave {
|
||||
composeCanvas.scale(qrSize / 300f, qrSize / 300f)
|
||||
canvasDrawScope.draw(
|
||||
density = object : Density {
|
||||
override val density: Float = 1f
|
||||
override val fontScale: Float = 1f
|
||||
},
|
||||
layoutDirection = LayoutDirection.Ltr,
|
||||
canvas = composeCanvas,
|
||||
size = Size(300f, 300f)
|
||||
) {
|
||||
drawQr(
|
||||
data = qrCodeData,
|
||||
foregroundColor = state.qrCodeColorScheme.foregroundColor,
|
||||
backgroundColor = state.qrCodeColorScheme.borderColor,
|
||||
deadzonePercent = 0.35f,
|
||||
logo = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
composeCanvas.translate(-90f, -80f)
|
||||
|
||||
// Draw the signal logo -- unfortunately can't have the normal QR code drawing handle it because it requires a composable ImageBitmap
|
||||
BitmapFactory.decodeResource(ApplicationDependencies.getApplication().resources, R.drawable.qrcode_logo).also { logoBitmap ->
|
||||
val tintedPaint = Paint().apply {
|
||||
colorFilter = PorterDuffColorFilter(state.qrCodeColorScheme.foregroundColor.toArgb(), PorterDuff.Mode.SRC_IN)
|
||||
}
|
||||
val sourceRect = Rect(0, 0, logoBitmap.width, logoBitmap.height)
|
||||
|
||||
val destLeft = qrBorderWidth / 2f + qrPadding
|
||||
val destTop = destLeft - 10f
|
||||
val destRect = RectF(destLeft, destTop, destLeft + 36f, destTop + 36f)
|
||||
drawBitmap(logoBitmap, sourceRect, destRect, tintedPaint)
|
||||
}
|
||||
val sourceRect = Rect(0, 0, logoBitmap.width, logoBitmap.height)
|
||||
val destRect = RectF(210f, 200f, 270f, 260f)
|
||||
androidCanvas.drawBitmap(logoBitmap, sourceRect, destRect, tintedPaint)
|
||||
}
|
||||
|
||||
// Draw the text
|
||||
val textPaint = Paint().apply {
|
||||
// Draw the username
|
||||
val usernamePaint = Paint().apply {
|
||||
color = state.qrCodeColorScheme.textColor.toArgb()
|
||||
textSize = 34f
|
||||
typeface = if (Build.VERSION.SDK_INT < 26) {
|
||||
@@ -286,10 +311,33 @@ class UsernameLinkSettingsViewModel : ViewModel() {
|
||||
.build()
|
||||
}
|
||||
}
|
||||
val textBounds = Rect()
|
||||
textPaint.getTextBounds(state.username, 0, state.username.length, textBounds)
|
||||
val usernameBounds = Rect()
|
||||
usernamePaint.getTextBounds(state.username, 0, state.username.length, usernameBounds)
|
||||
|
||||
androidCanvas.drawText(state.username, (width / 2f) - (textBounds.width() / 2f), 465f, textPaint)
|
||||
androidCanvas.drawText(state.username, (width / 2f) - (usernameBounds.width() / 2f), 348f + usernameBounds.height(), usernamePaint)
|
||||
|
||||
// Draw the help text
|
||||
val helpTextPaint = TextPaint().apply {
|
||||
isAntiAlias = true
|
||||
color = 0xFF3C3C43.toInt()
|
||||
textSize = 14f
|
||||
typeface = if (Build.VERSION.SDK_INT < 26) {
|
||||
Typeface.DEFAULT
|
||||
} else {
|
||||
Typeface.Builder("")
|
||||
.setFallback("sans-serif")
|
||||
.setWeight(400)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
val helpTextHorizontalPad = 72
|
||||
val maxWidth = width - helpTextHorizontalPad * 2
|
||||
val helpTextLayout = StaticLayout(helpText, helpTextPaint, maxWidth, Layout.Alignment.ALIGN_CENTER, 1f, 0f, true)
|
||||
|
||||
androidCanvas.withTranslation(x = helpTextHorizontalPad.toFloat(), y = 444f) {
|
||||
helpTextLayout.draw(androidCanvas)
|
||||
}
|
||||
|
||||
return bitmap
|
||||
}
|
||||
|
||||
@@ -93,21 +93,18 @@ fun UsernameLinkShareScreen(
|
||||
|
||||
ButtonBar(
|
||||
onShareClicked = onShareBadge,
|
||||
onColorClicked = { navController.safeNavigate(UsernameLinkSettingsFragmentDirections.actionUsernameLinkSettingsFragmentToUsernameLinkQrColorPickerFragment()) }
|
||||
)
|
||||
|
||||
LinkRow(
|
||||
linkState = state.usernameLinkState,
|
||||
onClick = {
|
||||
onColorClicked = { navController.safeNavigate(UsernameLinkSettingsFragmentDirections.actionUsernameLinkSettingsFragmentToUsernameLinkQrColorPickerFragment()) },
|
||||
onLinkClicked = {
|
||||
navController.safeNavigate(UsernameLinkSettingsFragmentDirections.actionUsernameLinkSettingsFragmentToUsernameLinkShareBottomSheet())
|
||||
}
|
||||
},
|
||||
linkState = state.usernameLinkState
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.UsernameLinkSettings_qr_description),
|
||||
textAlign = TextAlign.Center,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(bottom = 19.dp, start = 43.dp, end = 43.dp),
|
||||
modifier = Modifier.padding(top = 42.dp, bottom = 19.dp, start = 43.dp, end = 43.dp),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
|
||||
@@ -127,11 +124,22 @@ fun UsernameLinkShareScreen(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ButtonBar(onShareClicked: () -> Unit, onColorClicked: () -> Unit) {
|
||||
private fun ButtonBar(
|
||||
linkState: UsernameLinkState,
|
||||
onLinkClicked: () -> Unit,
|
||||
onShareClicked: () -> Unit,
|
||||
onColorClicked: () -> Unit
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 32.dp, alignment = Alignment.CenterHorizontally),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Buttons.ActionButton(
|
||||
enabled = linkState is UsernameLinkState.Present,
|
||||
onClick = onLinkClicked,
|
||||
iconResId = R.drawable.symbol_link_24,
|
||||
labelResId = R.string.UsernameLinkSettings_link_button_label
|
||||
)
|
||||
Buttons.ActionButton(
|
||||
onClick = onShareClicked,
|
||||
iconResId = R.drawable.symbol_share_android_24,
|
||||
|
||||
@@ -194,6 +194,8 @@ public class UsernameEditFragment extends LoggingFragment {
|
||||
case DISCRIMINATOR_HAS_INVALID_CHARACTERS, DISCRIMINATOR_NOT_AVAILABLE -> getString(R.string.UsernameEditFragment__this_username_is_not_available_try_another_number);
|
||||
case DISCRIMINATOR_TOO_LONG -> getString(R.string.UsernameEditFragment__invalid_username_enter_a_maximum_of_d_digits, UsernameUtil.MAX_DISCRIMINATOR_LENGTH);
|
||||
case DISCRIMINATOR_TOO_SHORT -> getString(R.string.UsernameEditFragment__invalid_username_enter_a_minimum_of_d_digits, UsernameUtil.MIN_DISCRIMINATOR_LENGTH);
|
||||
case DISCRIMINATOR_CANNOT_BE_00 -> getString(R.string.UsernameEditFragment__this_number_cant_be_00);
|
||||
case DISCRIMINATOR_CANNOT_START_WITH_00 -> getString(R.string.UsernameEditFragment__this_number_cant_start_with_00);
|
||||
};
|
||||
|
||||
int colorRes = error != null ? R.color.signal_colorError : R.color.signal_colorPrimary;
|
||||
|
||||
@@ -355,7 +355,9 @@ internal class UsernameEditViewModel private constructor(private val mode: Usern
|
||||
DISCRIMINATOR_NOT_AVAILABLE,
|
||||
DISCRIMINATOR_TOO_SHORT,
|
||||
DISCRIMINATOR_TOO_LONG,
|
||||
DISCRIMINATOR_HAS_INVALID_CHARACTERS
|
||||
DISCRIMINATOR_HAS_INVALID_CHARACTERS,
|
||||
DISCRIMINATOR_CANNOT_BE_00,
|
||||
DISCRIMINATOR_CANNOT_START_WITH_00
|
||||
}
|
||||
|
||||
enum class ButtonState {
|
||||
@@ -383,6 +385,7 @@ internal class UsernameEditViewModel private constructor(private val mode: Usern
|
||||
InvalidReason.TOO_LONG -> UsernameStatus.TOO_LONG
|
||||
InvalidReason.STARTS_WITH_NUMBER -> UsernameStatus.CANNOT_START_WITH_NUMBER
|
||||
InvalidReason.INVALID_CHARACTERS -> UsernameStatus.INVALID_CHARACTERS
|
||||
InvalidReason.INVALID_NUMBER, InvalidReason.INVALID_NUMBER_PREFIX -> error("Unexpected reason $invalidReason")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,6 +394,8 @@ internal class UsernameEditViewModel private constructor(private val mode: Usern
|
||||
InvalidReason.TOO_SHORT -> UsernameStatus.DISCRIMINATOR_TOO_SHORT
|
||||
InvalidReason.TOO_LONG -> UsernameStatus.DISCRIMINATOR_TOO_LONG
|
||||
InvalidReason.INVALID_CHARACTERS -> UsernameStatus.DISCRIMINATOR_HAS_INVALID_CHARACTERS
|
||||
InvalidReason.INVALID_NUMBER -> UsernameStatus.DISCRIMINATOR_CANNOT_BE_00
|
||||
InvalidReason.INVALID_NUMBER_PREFIX -> UsernameStatus.DISCRIMINATOR_CANNOT_START_WITH_00
|
||||
else -> UsernameStatus.INVALID_GENERIC
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ public final class SpanUtil {
|
||||
|
||||
public static void appendBottomImageSpan(@NonNull SpannableStringBuilder builder, @NonNull Drawable drawable, int width, int height) {
|
||||
drawable.setBounds(0, 0, ViewUtil.dpToPx(width), ViewUtil.dpToPx(height));
|
||||
builder.append(" ").append(SpanUtil.buildImageSpan(drawable, DynamicDrawableSpan.ALIGN_BOTTOM));
|
||||
builder.append(SpanUtil.buildImageSpan(drawable, DynamicDrawableSpan.ALIGN_BOTTOM));
|
||||
}
|
||||
|
||||
public static CharSequence learnMore(@NonNull Context context,
|
||||
|
||||
@@ -64,6 +64,12 @@ object UsernameUtil {
|
||||
value == null -> {
|
||||
null
|
||||
}
|
||||
value == "00" -> {
|
||||
InvalidReason.INVALID_NUMBER
|
||||
}
|
||||
value.startsWith("00") -> {
|
||||
InvalidReason.INVALID_NUMBER_PREFIX
|
||||
}
|
||||
value.length < MIN_DISCRIMINATOR_LENGTH -> {
|
||||
InvalidReason.TOO_SHORT
|
||||
}
|
||||
@@ -83,6 +89,8 @@ object UsernameUtil {
|
||||
TOO_SHORT,
|
||||
TOO_LONG,
|
||||
INVALID_CHARACTERS,
|
||||
STARTS_WITH_NUMBER
|
||||
STARTS_WITH_NUMBER,
|
||||
INVALID_NUMBER,
|
||||
INVALID_NUMBER_PREFIX
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="40"
|
||||
android:viewportHeight="40">
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M13.5 2.5c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6Zm-4 6c0-2.2 1.8-4 4-4s4 1.8 4 4-1.8 4-4 4-4-1.8-4-4Z"/>
|
||||
android:pathData="M7.95 1.25C5.88 1.25 4.2 2.93 4.2 5c0 2.07 1.68 3.75 3.75 3.75 2.07 0 3.75-1.68 3.75-3.75 0-2.07-1.68-3.75-3.75-3.75ZM5.95 5c0-1.1 0.9-2 2-2s2 0.9 2 2-0.9 2-2 2-2-0.9-2-2Z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M13.5 25.5c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6Zm-4 6c0-2.2 1.8-4 4-4s4 1.8 4 4-1.8 4-4 4-4-1.8-4-4Z"/>
|
||||
android:pathData="M7.95 15.25c-2.07 0-3.75 1.68-3.75 3.75 0 2.07 1.68 3.75 3.75 3.75 2.07 0 3.75-1.68 3.75-3.75 0-2.07-1.68-3.75-3.75-3.75Zm-2 3.75c0-1.1 0.9-2 2-2s2 0.9 2 2-0.9 2-2 2-2-0.9-2-2Z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M20.5 8.5c0-3.31 2.69-6 6-6s6 2.69 6 6-2.69 6-6 6-6-2.69-6-6Zm6-4c-2.2 0-4 1.8-4 4s1.8 4 4 4 4-1.8 4-4-1.8-4-4-4Z"/>
|
||||
android:pathData="M12.3 5c0-2.07 1.68-3.75 3.75-3.75 2.07 0 3.75 1.68 3.75 3.75 0 2.07-1.68 3.75-3.75 3.75-2.07 0-3.75-1.68-3.75-3.75Zm3.75-2c-1.1 0-2 0.9-2 2s0.9 2 2 2 2-0.9 2-2-0.9-2-2-2Z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M26.5 25.5c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6Zm-4 6c0-2.2 1.8-4 4-4s4 1.8 4 4-1.8 4-4 4-4-1.8-4-4Z"/>
|
||||
android:pathData="M16.05 15.25c-2.07 0-3.75 1.68-3.75 3.75 0 2.07 1.68 3.75 3.75 3.75 2.07 0 3.75-1.68 3.75-3.75 0-2.07-1.68-3.75-3.75-3.75Zm-2 3.75c0-1.1 0.9-2 2-2s2 0.9 2 2-0.9 2-2 2-2-0.9-2-2Z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M27 20c0-3.31 2.69-6 6-6s6 2.69 6 6-2.69 6-6 6-6-2.69-6-6Zm6-4c-2.2 0-4 1.8-4 4s1.8 4 4 4 4-1.8 4-4-1.8-4-4-4Z"/>
|
||||
android:pathData="M16.38 12c0-2.07 1.67-3.75 3.75-3.75 2.07 0 3.75 1.68 3.75 3.75 0 2.07-1.68 3.75-3.75 3.75-2.08 0-3.75-1.68-3.75-3.75Zm3.75-2c-1.11 0-2 0.9-2 2s0.89 2 2 2c1.1 0 2-0.9 2-2s-0.9-2-2-2Z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M7 14c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6Zm-4 6c0-2.2 1.8-4 4-4s4 1.8 4 4-1.8 4-4 4-4-1.8-4-4Z"/>
|
||||
android:pathData="M3.88 8.25C1.8 8.25 0.13 9.93 0.13 12c0 2.07 1.67 3.75 3.75 3.75 2.07 0 3.75-1.68 3.75-3.75 0-2.07-1.68-3.75-3.75-3.75Zm-2 3.75c0-1.1 0.89-2 2-2 1.1 0 2 0.9 2 2s-0.9 2-2 2c-1.11 0-2-0.9-2-2Z"/>
|
||||
</vector>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
tools:viewBindingIgnore="true"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/dsl_preference_item_background"
|
||||
android:minHeight="56dp">
|
||||
android:minHeight="56dp"
|
||||
tools:viewBindingIgnore="true">
|
||||
|
||||
<org.thoughtcrime.securesms.components.AvatarImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:importantForAccessibility="no"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
@@ -24,8 +24,8 @@
|
||||
android:id="@+id/badge"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginStart="40dp"
|
||||
android:layout_marginTop="40dp"
|
||||
android:layout_marginStart="56dp"
|
||||
android:layout_marginTop="56dp"
|
||||
android:contentDescription="@string/ImageView__badge"
|
||||
app:badge_size="medium"
|
||||
app:layout_constraintStart_toStartOf="@id/icon"
|
||||
@@ -35,7 +35,9 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginBottom="14dp"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/qr_button"
|
||||
@@ -51,17 +53,6 @@
|
||||
android:textAppearance="@style/Signal.Text.TitleLarge"
|
||||
tools:text="Peter Parker" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
android:id="@+id/about"
|
||||
style="@style/Signal.Text.BodyMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="@color/signal_colorOnSurfaceVariant"
|
||||
tools:text="Crusin' the web" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
android:id="@+id/summary"
|
||||
style="@style/Signal.Text.BodyMedium"
|
||||
@@ -71,21 +62,43 @@
|
||||
android:textColor="@color/signal_colorOnSurfaceVariant"
|
||||
tools:text="+1 (999) 555-1234" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
android:id="@+id/username"
|
||||
style="@style/Signal.Text.BodyMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="@color/signal_colorOnSurfaceVariant"
|
||||
android:visibility="gone"
|
||||
tools:text="miles.07" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
android:id="@+id/about"
|
||||
style="@style/Signal.Text.BodySmall"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="@color/signal_colorOnSurfaceVariant"
|
||||
tools:text="Crusin' the web" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/qr_button"
|
||||
style="@style/Widget.Signal.Button.Icon"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:backgroundTint="@color/signal_colorSurface4"
|
||||
app:icon="@drawable/symbol_qrcode_24"
|
||||
app:iconSize="20dp"
|
||||
app:iconTint="@color/core_black"
|
||||
app:backgroundTint="@color/signal_light_colorSurface3"
|
||||
style="@style/Widget.Signal.Button.Icon"/>
|
||||
app:iconTint="@color/signal_colorOnSurface"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -1,16 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:viewBindingIgnore="true"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
tools:parentTag="org.thoughtcrime.securesms.util.views.CircularProgressMaterialButton">
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:parentTag="org.thoughtcrime.securesms.util.views.CircularProgressMaterialButton"
|
||||
tools:viewBindingIgnore="true">
|
||||
|
||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
android:id="@+id/progress_indicator"
|
||||
style="?circularProgressIndicatorStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal" />
|
||||
android:layout_gravity="center_horizontal"
|
||||
app:indicatorInset="2dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/button"
|
||||
|
||||
@@ -93,9 +93,11 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/dsl_settings_gutter"
|
||||
android:layout_marginTop="14dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="@dimen/dsl_settings_gutter"
|
||||
android:text="@string/ManageProfileFragment__edit_photo"
|
||||
android:textColor="@color/signal_colorOnSurface"
|
||||
app:backgroundTint="@color/signal_colorSurface4"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/manage_profile_avatar_background" />
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
|
||||
<EditText
|
||||
android:id="@+id/username_text"
|
||||
style="@style/Signal.Text.Body"
|
||||
style="@style/Signal.Text.BodyLarge"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/UsernameEditFragment_username"
|
||||
@@ -83,7 +83,7 @@
|
||||
|
||||
<EditText
|
||||
android:id="@+id/discriminator_text"
|
||||
style="@style/Signal.Text.Body"
|
||||
style="@style/Signal.Text.BodyLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/dsl_settings_gutter"
|
||||
@@ -105,8 +105,8 @@
|
||||
android:id="@+id/suffix_progress"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
app:indicatorColor="@color/signal_colorOnSurfaceVariant"
|
||||
app:indicatorSize="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/username_text_wrapper"
|
||||
@@ -134,11 +134,11 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/username_error"
|
||||
style="@style/Signal.Text.BodyMedium"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:textAppearance="@style/Signal.Text.BodyMedium"
|
||||
android:textColor="@color/signal_colorError"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="@id/username_description"
|
||||
@@ -164,6 +164,7 @@
|
||||
|
||||
<org.thoughtcrime.securesms.util.views.LearnMoreTextView
|
||||
android:id="@+id/username_description"
|
||||
style="@style/Signal.Text.BodyMedium"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/dsl_settings_gutter"
|
||||
@@ -171,7 +172,6 @@
|
||||
android:layout_marginEnd="@dimen/dsl_settings_gutter"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:text="@string/UsernameEditFragment__usernames_let_others_message"
|
||||
android:textAppearance="@style/Signal.Text.BodyMedium"
|
||||
android:textColor="@color/signal_colorOnSurfaceVariant"
|
||||
app:layout_constraintBottom_toTopOf="@id/username_button_barrier"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
||||
@@ -2243,6 +2243,10 @@
|
||||
<string name="UsernameEditFragment__invalid_username_enter_a_minimum_of_d_digits">Invalid username, enter a minimum of %1$d digits.</string>
|
||||
<!-- Displayed when the chosen discriminator is too long -->
|
||||
<string name="UsernameEditFragment__invalid_username_enter_a_maximum_of_d_digits">Invalid username, enter a maximum of %1$d digits.</string>
|
||||
<!-- Displayed when the chosen discriminator is 00 -->
|
||||
<string name="UsernameEditFragment__this_number_cant_be_00">This number can\'t be 00. Enter a digit between 1–9</string>
|
||||
<!-- Displayed when the chosen discriminator starts with 00 -->
|
||||
<string name="UsernameEditFragment__this_number_cant_start_with_00">This number can\'t start with 00. Enter a digit between 1–9</string>
|
||||
<!-- The body of an alert dialog asking the user to confirm that they want to recover their username -->
|
||||
<string name="UsernameEditFragment_recovery_dialog_confirmation">Recovering your username will reset your existing QR code and link. Are you sure?</string>
|
||||
|
||||
@@ -6350,6 +6354,8 @@
|
||||
<!-- Displayed as body in dialog when user attempts to delete the link -->
|
||||
<string name="CallLinkDetailsFragment__this_link_will_no_longer_work">This link will no longer work for anyone who as it.</string>
|
||||
|
||||
<!-- Button label for the link button in the username link settings -->
|
||||
<string name="UsernameLinkSettings_link_button_label">Link</string>
|
||||
<!-- Button label for the share button in the username link settings -->
|
||||
<string name="UsernameLinkSettings_share_button_label">Share</string>
|
||||
<!-- Button label for the color selector button in the username link settings -->
|
||||
@@ -6394,6 +6400,8 @@
|
||||
<string name="UsernameLinkSettings_reset_link_result_network_unavailable">You do not have network access. Your link was not reset. Try again later.</string>
|
||||
<!-- Body of a dialog that is displayed when we failed to reset your username link because of a transient network issue. -->
|
||||
<string name="UsernameLinkSettings_reset_link_result_network_error">A network error occurred while trying to reset your link. Try again later.</string>
|
||||
<!-- Shown on the generated username qr code image to explain how to use it. -->
|
||||
<string name="UsernameLinkSettings_scan_this_qr_code">Scan this QR code with your phone to chat with me on Signal.</string>
|
||||
|
||||
<!-- Explanatory text at the top of a bottom sheet describing how username links work -->
|
||||
<string name="UsernameLinkShareBottomSheet_title">Anyone with this link can view your username and start a chat with you. Only share it with people you trust.</string>
|
||||
|
||||
@@ -177,9 +177,11 @@ object Buttons {
|
||||
onClick: () -> Unit,
|
||||
@DrawableRes iconResId: Int,
|
||||
@StringRes labelResId: Int,
|
||||
modifier: Modifier = Modifier
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true
|
||||
) {
|
||||
ActionButton(
|
||||
enabled = enabled,
|
||||
onClick = onClick,
|
||||
label = stringResource(labelResId),
|
||||
modifier = modifier
|
||||
|
||||
74
core-ui/src/main/java/org/signal/core/ui/Snackbars.kt
Normal file
74
core-ui/src/main/java/org/signal/core/ui/Snackbars.kt
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.core.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.material3.Snackbar
|
||||
import androidx.compose.material3.SnackbarData
|
||||
import androidx.compose.material3.SnackbarDuration
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.SnackbarVisuals
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import org.signal.core.ui.theme.LocalSnackbarColors
|
||||
import org.signal.core.ui.theme.SignalTheme
|
||||
|
||||
/**
|
||||
* Properly themed Snackbars. Since these use internal color state, we need to
|
||||
* use a local provider to pass the properly themed colors around. These composables
|
||||
* allow for quick and easy access to the proper theming for snackbars.
|
||||
*/
|
||||
object Snackbars {
|
||||
@Composable
|
||||
fun Host(snackbarHostState: SnackbarHostState) {
|
||||
SnackbarHost(hostState = snackbarHostState) {
|
||||
Default(snackbarData = it)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun Default(snackbarData: SnackbarData) {
|
||||
val colors = LocalSnackbarColors.current
|
||||
Snackbar(
|
||||
snackbarData = snackbarData,
|
||||
containerColor = colors.color,
|
||||
contentColor = colors.contentColor,
|
||||
actionColor = colors.actionColor,
|
||||
actionContentColor = colors.actionContentColor,
|
||||
dismissActionContentColor = colors.dismissActionContentColor
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun SnackbarLightPreview() {
|
||||
SignalTheme {
|
||||
Snackbars.Default(snackbarData = SampleSnackbarData)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun SnackbarDarkPreview() {
|
||||
SignalTheme {
|
||||
Snackbars.Default(snackbarData = SampleSnackbarData)
|
||||
}
|
||||
}
|
||||
|
||||
private object SampleSnackbarData : SnackbarData {
|
||||
override val visuals = object : SnackbarVisuals {
|
||||
override val actionLabel: String = "Action Label"
|
||||
override val duration: SnackbarDuration = SnackbarDuration.Short
|
||||
override val message: String = "Message"
|
||||
override val withDismissAction: Boolean = true
|
||||
}
|
||||
|
||||
override fun dismiss() = Unit
|
||||
|
||||
override fun performAction() = Unit
|
||||
}
|
||||
@@ -168,14 +168,31 @@ private val darkColorScheme = darkColorScheme(
|
||||
outline = Color(0xFF5C5E65)
|
||||
)
|
||||
|
||||
private val lightSnackbarColors = SnackbarColors(
|
||||
color = darkColorScheme.surface,
|
||||
contentColor = darkColorScheme.onSurface,
|
||||
actionColor = darkColorScheme.primary,
|
||||
actionContentColor = darkColorScheme.primary,
|
||||
dismissActionContentColor = darkColorScheme.onSurface
|
||||
)
|
||||
|
||||
private val darkSnackbarColors = SnackbarColors(
|
||||
color = darkColorScheme.surfaceVariant,
|
||||
contentColor = darkColorScheme.onSurfaceVariant,
|
||||
actionColor = darkColorScheme.primary,
|
||||
actionContentColor = darkColorScheme.primary,
|
||||
dismissActionContentColor = darkColorScheme.onSurfaceVariant
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun SignalTheme(
|
||||
isDarkMode: Boolean = LocalContext.current.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val extendedColors = if (isDarkMode) darkExtendedColors else lightExtendedColors
|
||||
val snackbarColors = if (isDarkMode) darkSnackbarColors else lightSnackbarColors
|
||||
|
||||
CompositionLocalProvider(LocalExtendedColors provides extendedColors) {
|
||||
CompositionLocalProvider(LocalExtendedColors provides extendedColors, LocalSnackbarColors provides snackbarColors) {
|
||||
MaterialTheme(
|
||||
colorScheme = if (isDarkMode) darkColorScheme else lightColorScheme,
|
||||
typography = typography,
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.core.ui.theme
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
/**
|
||||
* Borrowed from [androidx.compose.material3.Snackbar]
|
||||
*
|
||||
* Works in conjunction with [org.signal.core.ui.Snackbars] for properly
|
||||
* themed snackbars in light and dark modes.
|
||||
*/
|
||||
@Immutable
|
||||
data class SnackbarColors(
|
||||
val color: Color,
|
||||
val contentColor: Color,
|
||||
val actionColor: Color,
|
||||
val actionContentColor: Color,
|
||||
val dismissActionContentColor: Color
|
||||
)
|
||||
|
||||
val LocalSnackbarColors = staticCompositionLocalOf {
|
||||
SnackbarColors(
|
||||
color = Color.Unspecified,
|
||||
contentColor = Color.Unspecified,
|
||||
actionColor = Color.Unspecified,
|
||||
actionContentColor = Color.Unspecified,
|
||||
dismissActionContentColor = Color.Unspecified
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user