mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 17:29:32 +01:00
Add restore local backupv2 infra.
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user