Add restore local backupv2 infra.

This commit is contained in:
Cody Henthorne
2024-09-03 16:49:33 -04:00
parent 00d20a1917
commit a8bf03af89
41 changed files with 615 additions and 324 deletions

View File

@@ -5,6 +5,7 @@
package org.thoughtcrime.securesms.banner
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
@@ -44,5 +45,5 @@ abstract class Banner {
* @see [org.thoughtcrime.securesms.banner.ui.compose.DefaultBanner]
*/
@Composable
abstract fun DisplayBanner()
abstract fun DisplayBanner(contentPadding: PaddingValues)
}

View File

@@ -6,11 +6,14 @@
package org.thoughtcrime.securesms.banner
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import org.signal.core.ui.theme.SignalTheme
import org.signal.core.util.logging.Log
/**
@@ -47,8 +50,10 @@ class BannerManager @JvmOverloads constructor(
val bannerToDisplay = state.value.firstOrNull()
if (bannerToDisplay != null) {
Box {
bannerToDisplay.DisplayBanner()
SignalTheme {
Box {
bannerToDisplay.DisplayBanner(PaddingValues(horizontal = 12.dp, vertical = 8.dp))
}
}
onNewBannerShownListener()

View File

@@ -6,6 +6,7 @@
package org.thoughtcrime.securesms.banner.banners
import android.os.Build
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import kotlinx.coroutines.flow.Flow
@@ -21,7 +22,7 @@ class BubbleOptOutBanner(inBubble: Boolean, private val actionListener: (Boolean
override val enabled: Boolean = inBubble && !SignalStore.tooltips.hasSeenBubbleOptOutTooltip() && Build.VERSION.SDK_INT > 29
@Composable
override fun DisplayBanner() {
override fun DisplayBanner(contentPadding: PaddingValues) {
DefaultBanner(
title = null,
body = stringResource(id = R.string.BubbleOptOutTooltip__description),
@@ -32,7 +33,8 @@ class BubbleOptOutBanner(inBubble: Boolean, private val actionListener: (Boolean
Action(R.string.BubbleOptOutTooltip__not_now) {
actionListener(false)
}
)
),
paddingValues = contentPadding
)
}

View File

@@ -5,6 +5,7 @@
package org.thoughtcrime.securesms.banner.banners
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.fragment.app.FragmentManager
@@ -24,7 +25,7 @@ class CdsPermanentErrorBanner(private val fragmentManager: FragmentManager) : Ba
override val enabled: Boolean = SignalStore.misc.isCdsBlocked && timeUntilUnblock >= PERMANENT_TIME_CUTOFF
@Composable
override fun DisplayBanner() {
override fun DisplayBanner(contentPadding: PaddingValues) {
DefaultBanner(
title = null,
body = stringResource(id = R.string.reminder_cds_permanent_error_body),
@@ -33,7 +34,8 @@ class CdsPermanentErrorBanner(private val fragmentManager: FragmentManager) : Ba
Action(R.string.reminder_cds_permanent_error_learn_more) {
CdsPermanentErrorBottomSheet.show(fragmentManager)
}
)
),
paddingValues = contentPadding
)
}

View File

@@ -5,6 +5,7 @@
package org.thoughtcrime.securesms.banner.banners
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.fragment.app.FragmentManager
@@ -23,7 +24,7 @@ class CdsTemporaryErrorBanner(private val fragmentManager: FragmentManager) : Ba
override val enabled: Boolean = SignalStore.misc.isCdsBlocked && timeUntilUnblock < CdsPermanentErrorBanner.PERMANENT_TIME_CUTOFF
@Composable
override fun DisplayBanner() {
override fun DisplayBanner(contentPadding: PaddingValues) {
DefaultBanner(
title = null,
body = stringResource(id = R.string.reminder_cds_warning_body),
@@ -32,7 +33,8 @@ class CdsTemporaryErrorBanner(private val fragmentManager: FragmentManager) : Ba
Action(R.string.reminder_cds_warning_learn_more) {
CdsTemporaryErrorBottomSheet.show(fragmentManager)
}
)
),
paddingValues = contentPadding
)
}

View File

@@ -7,6 +7,7 @@ package org.thoughtcrime.securesms.banner.banners
import android.content.Context
import android.os.Build
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import kotlinx.coroutines.flow.Flow
@@ -25,23 +26,24 @@ class DozeBanner(private val context: Context, val dismissed: Boolean, private v
Build.VERSION.SDK_INT >= 23 && !SignalStore.account.fcmEnabled && !TextSecurePreferences.hasPromptedOptimizeDoze(context) && !ServiceUtil.getPowerManager(context).isIgnoringBatteryOptimizations(context.packageName)
@Composable
override fun DisplayBanner() {
override fun DisplayBanner(contentPadding: PaddingValues) {
if (Build.VERSION.SDK_INT < 23) {
throw IllegalStateException("Showing a Doze banner for an OS prior to Android 6.0")
}
DefaultBanner(
title = stringResource(id = R.string.DozeReminder_optimize_for_missing_play_services),
body = stringResource(id = R.string.DozeReminder_this_device_does_not_support_play_services_tap_to_disable_system_battery),
onDismissListener = {
TextSecurePreferences.setPromptedOptimizeDoze(context, true)
onDismiss()
},
actions = listOf(
Action(android.R.string.ok) {
TextSecurePreferences.setPromptedOptimizeDoze(context, true)
PowerManagerCompat.requestIgnoreBatteryOptimizations(context)
}
),
onDismissListener = {
TextSecurePreferences.setPromptedOptimizeDoze(context, true)
onDismiss()
}
paddingValues = contentPadding
)
}

View File

@@ -6,6 +6,7 @@
package org.thoughtcrime.securesms.banner.banners
import android.content.Context
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import kotlinx.coroutines.flow.Flow
@@ -21,7 +22,7 @@ class EnclaveFailureBanner(enclaveFailed: Boolean, private val context: Context)
override val enabled: Boolean = enclaveFailed
@Composable
override fun DisplayBanner() {
override fun DisplayBanner(contentPadding: PaddingValues) {
DefaultBanner(
title = null,
body = stringResource(id = R.string.EnclaveFailureReminder_update_signal),
@@ -30,7 +31,8 @@ class EnclaveFailureBanner(enclaveFailed: Boolean, private val context: Context)
Action(R.string.ExpiredBuildReminder_update_now) {
PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(context)
}
)
),
paddingValues = contentPadding
)
}

View File

@@ -5,6 +5,7 @@
package org.thoughtcrime.securesms.banner.banners
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.pluralStringResource
import kotlinx.coroutines.flow.Flow
@@ -18,7 +19,7 @@ class GroupsV1MigrationSuggestionsBanner(private val suggestionsSize: Int, priva
override val enabled: Boolean = suggestionsSize > 0
@Composable
override fun DisplayBanner() {
override fun DisplayBanner(contentPadding: PaddingValues) {
DefaultBanner(
title = null,
body = pluralStringResource(
@@ -29,7 +30,8 @@ class GroupsV1MigrationSuggestionsBanner(private val suggestionsSize: Int, priva
actions = listOf(
Action(R.plurals.GroupsV1MigrationSuggestionsReminder_add_members, isPluralizedLabel = true, pluralQuantity = suggestionsSize, onAddMembers),
Action(R.string.GroupsV1MigrationSuggestionsReminder_no_thanks, onClick = onNoThanks)
)
),
paddingValues = contentPadding
)
}

View File

@@ -5,18 +5,22 @@
package org.thoughtcrime.securesms.banner.banners
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.flowWithLifecycle
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.withContext
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.backup.v2.ui.status.BackupStatus
import org.thoughtcrime.securesms.backup.v2.ui.status.BackupStatusData
import org.thoughtcrime.securesms.banner.Banner
@@ -24,68 +28,71 @@ import org.thoughtcrime.securesms.database.DatabaseObserver
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import kotlin.time.Duration.Companion.seconds
class MediaRestoreProgressBanner(private val data: MediaRestoreEvent) : Banner() {
companion object {
private val TAG = Log.tag(MediaRestoreProgressBanner::class)
/**
* Create a Lifecycle-aware [Flow] of [MediaRestoreProgressBanner] that observes the database for changes in attachments and emits banners when attachments are updated.
*/
@JvmStatic
fun createLifecycleAwareFlow(lifecycleOwner: LifecycleOwner): Flow<MediaRestoreProgressBanner> {
if (SignalStore.backup.isRestoreInProgress) {
val observer = LifecycleObserver()
lifecycleOwner.lifecycle.addObserver(observer)
return observer.flow
return if (SignalStore.backup.isRestoreInProgress) {
restoreFlow(lifecycleOwner)
} else {
return flow {
flow {
emit(MediaRestoreProgressBanner(MediaRestoreEvent(0L, 0L)))
}
}
}
/**
* Create a flow that listens for all attachment changes in the db and emits a new banner at most
* once every 1 second.
*/
private fun restoreFlow(lifecycleOwner: LifecycleOwner): Flow<MediaRestoreProgressBanner> {
val flow = callbackFlow {
val queryObserver = DatabaseObserver.Observer {
trySend(Unit)
}
queryObserver.onChanged()
AppDependencies.databaseObserver.registerAttachmentUpdatedObserver(queryObserver)
awaitClose {
AppDependencies.databaseObserver.unregisterObserver(queryObserver)
}
}
return flow
.flowWithLifecycle(lifecycleOwner.lifecycle)
.buffer(1, BufferOverflow.DROP_OLDEST)
.onEach { delay(1.seconds) }
.map { MediaRestoreProgressBanner(loadData()) }
.flowOn(Dispatchers.IO)
}
private suspend fun loadData() = withContext(Dispatchers.IO) {
// TODO [backups]: define and query data for interrupted/paused restores
val totalRestoreSize = SignalStore.backup.totalRestorableAttachmentSize
val remainingAttachmentSize = SignalDatabase.attachments.getRemainingRestorableAttachmentSize()
val completedBytes = totalRestoreSize - remainingAttachmentSize
if (remainingAttachmentSize == 0L) {
SignalStore.backup.totalRestorableAttachmentSize = 0
}
MediaRestoreEvent(completedBytes, totalRestoreSize)
}
}
override var enabled: Boolean = data.totalBytes > 0L && data.totalBytes != data.completedBytes
@Composable
override fun DisplayBanner() {
override fun DisplayBanner(contentPadding: PaddingValues) {
BackupStatus(data = BackupStatusData.RestoringMedia(data.completedBytes, data.totalBytes))
}
data class MediaRestoreEvent(val completedBytes: Long, val totalBytes: Long)
private class LifecycleObserver : DefaultLifecycleObserver {
private var attachmentObserver: DatabaseObserver.Observer? = null
private val _mutableSharedFlow = MutableSharedFlow<MediaRestoreEvent>(replay = 1)
val flow = _mutableSharedFlow.map { MediaRestoreProgressBanner(it) }
override fun onStart(owner: LifecycleOwner) {
val queryObserver = DatabaseObserver.Observer {
owner.lifecycleScope.launch {
_mutableSharedFlow.emit(loadData())
}
}
attachmentObserver = queryObserver
queryObserver.onChanged()
AppDependencies.databaseObserver.registerAttachmentObserver(queryObserver)
}
override fun onStop(owner: LifecycleOwner) {
attachmentObserver?.let {
AppDependencies.databaseObserver.unregisterObserver(it)
}
}
private suspend fun loadData() = withContext(Dispatchers.IO) {
// TODO [backups]: define and query data for interrupted/paused restores
val totalRestoreSize = SignalStore.backup.totalRestorableAttachmentSize
val remainingAttachmentSize = SignalDatabase.attachments.getTotalRestorableAttachmentSize()
val completedBytes = totalRestoreSize - remainingAttachmentSize
MediaRestoreEvent(completedBytes, totalRestoreSize)
}
}
}

View File

@@ -6,6 +6,7 @@
package org.thoughtcrime.securesms.banner.banners
import android.content.Context
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
@@ -34,7 +35,7 @@ class OutdatedBuildBanner(val context: Context, private val daysUntilExpiry: Int
}
@Composable
override fun DisplayBanner() {
override fun DisplayBanner(contentPadding: PaddingValues) {
val bodyText = when (status) {
ExpiryStatus.OUTDATED_ONLY -> if (daysUntilExpiry == 0) {
stringResource(id = R.string.OutdatedBuildReminder_your_version_of_signal_will_expire_today)
@@ -63,7 +64,8 @@ class OutdatedBuildBanner(val context: Context, private val daysUntilExpiry: Int
Action(R.string.ExpiredBuildReminder_update_now) {
PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(context)
}
)
),
paddingValues = contentPadding
)
}

View File

@@ -5,6 +5,7 @@
package org.thoughtcrime.securesms.banner.banners
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.pluralStringResource
import kotlinx.coroutines.flow.Flow
@@ -17,7 +18,7 @@ import org.thoughtcrime.securesms.banner.ui.compose.DefaultBanner
class PendingGroupJoinRequestsBanner(override val enabled: Boolean, private val suggestionsSize: Int, private val onViewClicked: () -> Unit, private val onDismissListener: (() -> Unit)?) : Banner() {
@Composable
override fun DisplayBanner() {
override fun DisplayBanner(contentPadding: PaddingValues) {
DefaultBanner(
title = null,
body = pluralStringResource(
@@ -25,10 +26,11 @@ class PendingGroupJoinRequestsBanner(override val enabled: Boolean, private val
count = suggestionsSize,
suggestionsSize
),
onDismissListener = onDismissListener,
actions = listOf(
Action(R.string.PendingGroupJoinRequestsReminder_view, onClick = onViewClicked)
),
onDismissListener = onDismissListener
paddingValues = contentPadding
)
}

View File

@@ -6,6 +6,7 @@
package org.thoughtcrime.securesms.banner.banners
import android.content.Context
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import kotlinx.coroutines.flow.Flow
@@ -25,11 +26,12 @@ class ServiceOutageBanner(outageInProgress: Boolean) : Banner() {
override val enabled = outageInProgress
@Composable
override fun DisplayBanner() {
override fun DisplayBanner(contentPadding: PaddingValues) {
DefaultBanner(
title = null,
body = stringResource(id = R.string.reminder_header_service_outage_text),
importance = Importance.ERROR
importance = Importance.ERROR,
paddingValues = contentPadding
)
}

View File

@@ -6,6 +6,7 @@
package org.thoughtcrime.securesms.banner.banners
import android.content.Context
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import kotlinx.coroutines.flow.Flow
@@ -29,7 +30,7 @@ class UnauthorizedBanner(val context: Context) : Banner() {
override val enabled = TextSecurePreferences.isUnauthorizedReceived(context) || !SignalStore.account.isRegistered
@Composable
override fun DisplayBanner() {
override fun DisplayBanner(contentPadding: PaddingValues) {
DefaultBanner(
title = null,
body = stringResource(id = R.string.UnauthorizedReminder_this_is_likely_because_you_registered_your_phone_number_with_Signal_on_a_different_device),
@@ -39,7 +40,8 @@ class UnauthorizedBanner(val context: Context) : Banner() {
val registrationIntent = RegistrationActivity.newIntentForReRegistration(context)
context.startActivity(registrationIntent)
}
)
),
paddingValues = contentPadding
)
}

View File

@@ -6,6 +6,7 @@
package org.thoughtcrime.securesms.banner.banners
import android.content.Context
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import kotlinx.coroutines.flow.Flow
@@ -27,7 +28,7 @@ class UsernameOutOfSyncBanner(private val context: Context, private val username
}
@Composable
override fun DisplayBanner() {
override fun DisplayBanner(contentPadding: PaddingValues) {
DefaultBanner(
title = null,
body = if (usernameSyncState == UsernameSyncState.USERNAME_AND_LINK_CORRUPTED) {
@@ -40,7 +41,8 @@ class UsernameOutOfSyncBanner(private val context: Context, private val username
Action(R.string.UsernameOutOfSyncReminder__fix_now) {
onActionClick(usernameSyncState == UsernameSyncState.USERNAME_AND_LINK_CORRUPTED)
}
)
),
paddingValues = contentPadding
)
}

View File

@@ -10,6 +10,7 @@ import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -32,7 +33,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import org.signal.core.ui.Previews
import org.signal.core.ui.SignalPreview
import org.signal.core.ui.theme.SignalTheme
import org.signal.core.util.isNotNullOrBlank
import org.thoughtcrime.securesms.R
@@ -50,115 +50,116 @@ fun DefaultBanner(
actions: List<Action> = emptyList(),
showProgress: Boolean = false,
progressText: String = "",
progressPercent: Int = -1
progressPercent: Int = -1,
paddingValues: PaddingValues
) {
SignalTheme {
Box(
Box(
modifier = Modifier
.padding(paddingValues)
.background(
color = when (importance) {
Importance.NORMAL -> MaterialTheme.colorScheme.surface
Importance.ERROR -> colorResource(id = R.color.reminder_background)
}
)
.border(
width = 1.dp,
color = colorResource(id = R.color.signal_colorOutline_38),
shape = RoundedCornerShape(12.dp)
)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(horizontal = 12.dp, vertical = 8.dp)
.background(
color = when (importance) {
Importance.NORMAL -> MaterialTheme.colorScheme.surface
Importance.ERROR -> colorResource(id = R.color.reminder_background)
}
)
.border(
width = 1.dp,
color = colorResource(id = R.color.signal_colorOutline_38),
shape = RoundedCornerShape(12.dp)
)
.defaultMinSize(minHeight = 74.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.defaultMinSize(minHeight = 74.dp)
) {
Column {
Row(modifier = Modifier.fillMaxWidth()) {
Column(
modifier = Modifier
.weight(1f)
.padding(start = 16.dp, top = 16.dp)
) {
if (title.isNotNullOrBlank()) {
Text(
text = title,
color = when (importance) {
Importance.NORMAL -> MaterialTheme.colorScheme.onSurface
Importance.ERROR -> colorResource(id = R.color.signal_light_colorOnSurface)
},
style = MaterialTheme.typography.bodyLarge
Column {
Row(modifier = Modifier.fillMaxWidth()) {
Column(
modifier = Modifier
.weight(1f)
.padding(start = 16.dp, top = 16.dp)
) {
if (title.isNotNullOrBlank()) {
Text(
text = title,
color = when (importance) {
Importance.NORMAL -> MaterialTheme.colorScheme.onSurface
Importance.ERROR -> colorResource(id = R.color.signal_light_colorOnSurface)
},
style = MaterialTheme.typography.bodyLarge
)
}
Text(
text = body,
color = when (importance) {
Importance.NORMAL -> MaterialTheme.colorScheme.onSurfaceVariant
Importance.ERROR -> colorResource(id = R.color.signal_light_colorOnSurface)
},
style = MaterialTheme.typography.bodyMedium
)
if (showProgress) {
if (progressPercent >= 0) {
LinearProgressIndicator(
progress = { progressPercent / 100f },
color = MaterialTheme.colorScheme.primary,
trackColor = MaterialTheme.colorScheme.primaryContainer,
modifier = Modifier
.padding(vertical = 12.dp)
.fillMaxWidth()
)
} else {
LinearProgressIndicator(
color = MaterialTheme.colorScheme.primary,
trackColor = MaterialTheme.colorScheme.primaryContainer,
modifier = Modifier.padding(vertical = 12.dp)
)
}
Text(
text = body,
text = progressText,
style = MaterialTheme.typography.bodySmall,
color = when (importance) {
Importance.NORMAL -> MaterialTheme.colorScheme.onSurfaceVariant
Importance.ERROR -> colorResource(id = R.color.signal_light_colorOnSurface)
},
style = MaterialTheme.typography.bodyMedium
}
)
if (showProgress) {
if (progressPercent >= 0) {
LinearProgressIndicator(
progress = { progressPercent / 100f },
color = MaterialTheme.colorScheme.primary,
trackColor = MaterialTheme.colorScheme.primaryContainer,
modifier = Modifier.padding(vertical = 12.dp)
)
} else {
LinearProgressIndicator(
color = MaterialTheme.colorScheme.primary,
trackColor = MaterialTheme.colorScheme.primaryContainer,
modifier = Modifier.padding(vertical = 12.dp)
)
}
Text(
text = progressText,
style = MaterialTheme.typography.bodySmall,
color = when (importance) {
Importance.NORMAL -> MaterialTheme.colorScheme.onSurfaceVariant
Importance.ERROR -> colorResource(id = R.color.signal_light_colorOnSurface)
}
)
}
}
}
Box(modifier = Modifier.size(48.dp)) {
if (onDismissListener != null) {
IconButton(
onClick = {
onHideListener?.invoke()
onDismissListener()
},
modifier = Modifier.size(48.dp)
) {
Icon(
painter = painterResource(id = R.drawable.symbol_x_24),
contentDescription = stringResource(id = R.string.InviteActivity_cancel)
)
}
Box(modifier = Modifier.size(48.dp)) {
if (onDismissListener != null) {
IconButton(
onClick = {
onHideListener?.invoke()
onDismissListener()
},
modifier = Modifier.size(48.dp)
) {
Icon(
painter = painterResource(id = R.drawable.symbol_x_24),
contentDescription = stringResource(id = R.string.InviteActivity_cancel)
)
}
}
}
Row(
horizontalArrangement = Arrangement.End,
modifier = Modifier
.fillMaxWidth()
.padding(end = 8.dp)
) {
for (action in actions) {
TextButton(onClick = action.onClick) {
Text(
text = if (!action.isPluralizedLabel) {
stringResource(id = action.label)
} else {
pluralStringResource(id = action.label, count = action.pluralQuantity)
}
)
}
}
Row(
horizontalArrangement = Arrangement.End,
modifier = Modifier
.fillMaxWidth()
.padding(end = 8.dp)
) {
for (action in actions) {
TextButton(onClick = action.onClick) {
Text(
text = if (!action.isPluralizedLabel) {
stringResource(id = action.label)
} else {
pluralStringResource(id = action.label, count = action.pluralQuantity)
}
)
}
}
}
@@ -183,7 +184,8 @@ private fun BubblesOptOutPreview() {
actions = listOf(
Action(R.string.BubbleOptOutTooltip__turn_off) {},
Action(R.string.BubbleOptOutTooltip__not_now) {}
)
),
paddingValues = PaddingValues(horizontal = 12.dp, vertical = 8.dp)
)
}
}
@@ -196,9 +198,10 @@ private fun ForcedUpgradePreview() {
title = null,
body = stringResource(id = R.string.OutdatedBuildReminder_your_version_of_signal_will_expire_today),
importance = Importance.ERROR,
actions = listOf(Action(R.string.ExpiredBuildReminder_update_now) {}),
onDismissListener = {},
onHideListener = { },
onDismissListener = {}
actions = listOf(Action(R.string.ExpiredBuildReminder_update_now) {}),
paddingValues = PaddingValues(horizontal = 12.dp, vertical = 8.dp)
)
}
}
@@ -215,11 +218,12 @@ private fun FullyLoadedErrorPreview() {
title = "Error",
body = "Creating more errors.",
importance = Importance.ERROR,
onDismissListener = {},
actions = actions,
showProgress = true,
progressText = "4 out of 10 errors created.",
progressPercent = 40,
onDismissListener = {}
paddingValues = PaddingValues(horizontal = 12.dp, vertical = 8.dp)
)
}
}