mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-25 20:23:19 +00:00
Migrate existing Reminders to Banners.
This commit is contained in:
committed by
mtang-signal
parent
16a732171a
commit
d45acd0e24
@@ -37,7 +37,7 @@ abstract class Banner {
|
||||
/**
|
||||
* Whether or not the [Banner] should be shown (enabled) or hidden (disabled).
|
||||
*/
|
||||
abstract var enabled: Boolean
|
||||
abstract val enabled: Boolean
|
||||
|
||||
/**
|
||||
* Composable function to display content when [enabled] is true.
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.banner.banners
|
||||
|
||||
import android.os.Build
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.banner.Banner
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.Action
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.DefaultBanner
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
|
||||
class BubbleOptOutBanner(inBubble: Boolean, private val actionListener: (Boolean) -> Unit) : Banner() {
|
||||
|
||||
override val enabled: Boolean = inBubble && !SignalStore.tooltips.hasSeenBubbleOptOutTooltip() && Build.VERSION.SDK_INT > 29
|
||||
|
||||
@Composable
|
||||
override fun DisplayBanner() {
|
||||
DefaultBanner(
|
||||
title = null,
|
||||
body = stringResource(id = R.string.BubbleOptOutTooltip__description),
|
||||
actions = listOf(
|
||||
Action(R.string.BubbleOptOutTooltip__turn_off) {
|
||||
actionListener(true)
|
||||
},
|
||||
Action(R.string.BubbleOptOutTooltip__not_now) {
|
||||
actionListener(false)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun createFlow(inBubble: Boolean, actionListener: (Boolean) -> Unit): Flow<BubbleOptOutBanner> = createAndEmit {
|
||||
BubbleOptOutBanner(inBubble, actionListener)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.banner.banners
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.banner.Banner
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.Action
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.DefaultBanner
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.Importance
|
||||
import org.thoughtcrime.securesms.contacts.sync.CdsPermanentErrorBottomSheet
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import kotlin.time.Duration.Companion.days
|
||||
|
||||
class CdsPermanentErrorBanner(private val fragmentManager: FragmentManager) : Banner() {
|
||||
private val timeUntilUnblock = SignalStore.misc.cdsBlockedUtil - System.currentTimeMillis()
|
||||
|
||||
override val enabled: Boolean = SignalStore.misc.isCdsBlocked && timeUntilUnblock >= PERMANENT_TIME_CUTOFF
|
||||
|
||||
@Composable
|
||||
override fun DisplayBanner() {
|
||||
DefaultBanner(
|
||||
title = null,
|
||||
body = stringResource(id = R.string.reminder_cds_permanent_error_body),
|
||||
importance = Importance.ERROR,
|
||||
actions = listOf(
|
||||
Action(R.string.reminder_cds_permanent_error_learn_more) {
|
||||
CdsPermanentErrorBottomSheet.show(fragmentManager)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Even if we're not truly "permanently blocked", if the time until we're unblocked is long enough, we'd rather show the permanent error message than
|
||||
* telling the user to wait for 3 months or something.
|
||||
*/
|
||||
val PERMANENT_TIME_CUTOFF = 30.days.inWholeMilliseconds
|
||||
|
||||
@JvmStatic
|
||||
fun createFlow(fragmentManager: FragmentManager): Flow<CdsPermanentErrorBanner> = createAndEmit {
|
||||
CdsPermanentErrorBanner(fragmentManager)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.banner.banners
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.banner.Banner
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.Action
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.DefaultBanner
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.Importance
|
||||
import org.thoughtcrime.securesms.contacts.sync.CdsTemporaryErrorBottomSheet
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
|
||||
class CdsTemporaryErrorBanner(private val fragmentManager: FragmentManager) : Banner() {
|
||||
private val timeUntilUnblock = SignalStore.misc.cdsBlockedUtil - System.currentTimeMillis()
|
||||
|
||||
override val enabled: Boolean = SignalStore.misc.isCdsBlocked && timeUntilUnblock < CdsPermanentErrorBanner.PERMANENT_TIME_CUTOFF
|
||||
|
||||
@Composable
|
||||
override fun DisplayBanner() {
|
||||
DefaultBanner(
|
||||
title = null,
|
||||
body = stringResource(id = R.string.reminder_cds_warning_body),
|
||||
importance = Importance.ERROR,
|
||||
actions = listOf(
|
||||
Action(R.string.reminder_cds_warning_learn_more) {
|
||||
CdsTemporaryErrorBottomSheet.show(fragmentManager)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun createFlow(fragmentManager: FragmentManager): Flow<CdsTemporaryErrorBanner> = createAndEmit {
|
||||
CdsTemporaryErrorBanner(fragmentManager)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.banner.banners
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.banner.Banner
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.Action
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.DefaultBanner
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.PowerManagerCompat
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
|
||||
@RequiresApi(23)
|
||||
class DozeBanner(private val context: Context) : Banner() {
|
||||
override val enabled: Boolean = !SignalStore.account.fcmEnabled && !TextSecurePreferences.hasPromptedOptimizeDoze(context) && Build.VERSION.SDK_INT >= 23 && !ServiceUtil.getPowerManager(context).isIgnoringBatteryOptimizations(context.packageName)
|
||||
|
||||
@Composable
|
||||
override fun DisplayBanner() {
|
||||
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),
|
||||
actions = listOf(
|
||||
Action(android.R.string.ok) {
|
||||
TextSecurePreferences.setPromptedOptimizeDoze(context, true)
|
||||
PowerManagerCompat.requestIgnoreBatteryOptimizations(context)
|
||||
}
|
||||
),
|
||||
onDismissListener = {
|
||||
TextSecurePreferences.setPromptedOptimizeDoze(context, true)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun createFlow(context: Context): Flow<DozeBanner> = createAndEmit {
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
DozeBanner(context)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,48 +9,35 @@ import android.content.Context
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.banner.Banner
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.Action
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.DefaultBanner
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.Importance
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.PlayStoreUtil
|
||||
|
||||
/**
|
||||
* Banner to let the user know their build is about to expire.
|
||||
*
|
||||
* This serves as an example for how we can replicate the functionality of the old [org.thoughtcrime.securesms.components.reminder.Reminder] system purely in the new [Banner] system.
|
||||
*/
|
||||
class ExpiredBuildBanner(val context: Context) : Banner() {
|
||||
|
||||
override var enabled = true
|
||||
class EnclaveFailureBanner(enclaveFailed: Boolean, private val context: Context) : Banner() {
|
||||
override val enabled: Boolean = enclaveFailed
|
||||
|
||||
@Composable
|
||||
override fun DisplayBanner() {
|
||||
DefaultBanner(
|
||||
title = null,
|
||||
body = stringResource(id = R.string.OutdatedBuildReminder_your_version_of_signal_will_expire_today),
|
||||
importance = Importance.TERMINAL,
|
||||
isDismissible = false,
|
||||
body = stringResource(id = R.string.EnclaveFailureReminder_update_signal),
|
||||
importance = Importance.ERROR,
|
||||
actions = listOf(
|
||||
Action(R.string.ExpiredBuildReminder_update_now) {
|
||||
PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(context)
|
||||
}
|
||||
),
|
||||
onHideListener = {},
|
||||
onDismissListener = {}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun createFlow(context: Context): Flow<Banner> = createAndEmit {
|
||||
if (SignalStore.misc.isClientDeprecated) {
|
||||
ExpiredBuildBanner(context)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
fun Flow<Boolean>.mapBooleanFlowToBannerFlow(context: Context): Flow<EnclaveFailureBanner> {
|
||||
return map { EnclaveFailureBanner(it, context) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.banner.banners
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.banner.Banner
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.Action
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.DefaultBanner
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
|
||||
class GroupsV1MigrationSuggestionsBanner(private val suggestionsSize: Int, private val onAddMembers: () -> Unit, private val onNoThanks: () -> Unit) : Banner() {
|
||||
private val timeUntilUnblock = SignalStore.misc.cdsBlockedUtil - System.currentTimeMillis()
|
||||
|
||||
override val enabled: Boolean = SignalStore.misc.isCdsBlocked && timeUntilUnblock < CdsPermanentErrorBanner.PERMANENT_TIME_CUTOFF
|
||||
|
||||
@Composable
|
||||
override fun DisplayBanner() {
|
||||
DefaultBanner(
|
||||
title = null,
|
||||
body = pluralStringResource(
|
||||
id = R.plurals.GroupsV1MigrationSuggestionsReminder_members_couldnt_be_added_to_the_new_group,
|
||||
count = suggestionsSize,
|
||||
suggestionsSize
|
||||
),
|
||||
actions = listOf(
|
||||
Action(R.plurals.GroupsV1MigrationSuggestionsReminder_add_members, isPluralizedLabel = true, pluralQuantity = suggestionsSize, onAddMembers),
|
||||
Action(R.string.GroupsV1MigrationSuggestionsReminder_no_thanks, onClick = onNoThanks)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun createFlow(suggestionsSize: Int, onAddMembers: () -> Unit, onNoThanks: () -> Unit): Flow<GroupsV1MigrationSuggestionsBanner> = createAndEmit {
|
||||
GroupsV1MigrationSuggestionsBanner(suggestionsSize, onAddMembers, onNoThanks)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.banner.banners
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.banner.Banner
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.Action
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.DefaultBanner
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.Importance
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.PlayStoreUtil
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
/**
|
||||
* Banner to let the user know their build is about to expire or has expired.
|
||||
*/
|
||||
class OutdatedBuildBanner(val context: Context, private val daysUntilExpiry: Int) : Banner() {
|
||||
|
||||
override val enabled = SignalStore.misc.isClientDeprecated || daysUntilExpiry <= MAX_DAYS_UNTIL_EXPIRE
|
||||
|
||||
@Composable
|
||||
override fun DisplayBanner() {
|
||||
DefaultBanner(
|
||||
title = null,
|
||||
body = if (SignalStore.misc.isClientDeprecated) {
|
||||
stringResource(id = R.string.OutdatedBuildReminder_your_version_of_signal_will_expire_today)
|
||||
} else if (daysUntilExpiry == 0) {
|
||||
stringResource(id = R.string.OutdatedBuildReminder_your_version_of_signal_will_expire_today)
|
||||
} else {
|
||||
pluralStringResource(id = R.plurals.OutdatedBuildReminder_your_version_of_signal_will_expire_in_n_days, count = daysUntilExpiry, daysUntilExpiry)
|
||||
},
|
||||
importance = if (SignalStore.misc.isClientDeprecated) {
|
||||
Importance.ERROR
|
||||
} else {
|
||||
Importance.NORMAL
|
||||
},
|
||||
actions = listOf(
|
||||
Action(R.string.ExpiredBuildReminder_update_now) {
|
||||
PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(context)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MAX_DAYS_UNTIL_EXPIRE = 10
|
||||
|
||||
@JvmStatic
|
||||
fun createFlow(context: Context): Flow<OutdatedBuildBanner> = createAndEmit {
|
||||
val daysUntilExpiry = Util.getTimeUntilBuildExpiry(SignalStore.misc.estimatedServerTime).milliseconds.inWholeDays.toInt()
|
||||
OutdatedBuildBanner(context, daysUntilExpiry)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.banner.banners
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.banner.Banner
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.Action
|
||||
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() {
|
||||
DefaultBanner(
|
||||
title = null,
|
||||
body = pluralStringResource(
|
||||
id = R.plurals.GroupsV1MigrationSuggestionsReminder_members_couldnt_be_added_to_the_new_group,
|
||||
count = suggestionsSize,
|
||||
suggestionsSize
|
||||
),
|
||||
actions = listOf(
|
||||
Action(R.string.PendingGroupJoinRequestsReminder_view, onClick = onViewClicked)
|
||||
),
|
||||
onDismissListener = onDismissListener
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun createFlow(suggestionsSize: Int, onViewClicked: () -> Unit): Flow<PendingGroupJoinRequestsBanner> = Producer(suggestionsSize, onViewClicked).flow
|
||||
}
|
||||
|
||||
private class Producer(suggestionsSize: Int, onViewClicked: () -> Unit) {
|
||||
val dismissListener: () -> Unit = {
|
||||
mutableStateFlow.tryEmit(PendingGroupJoinRequestsBanner(false, suggestionsSize, onViewClicked, null))
|
||||
}
|
||||
private val mutableStateFlow: MutableStateFlow<PendingGroupJoinRequestsBanner> = MutableStateFlow(PendingGroupJoinRequestsBanner(true, suggestionsSize, onViewClicked, dismissListener))
|
||||
val flow: Flow<PendingGroupJoinRequestsBanner> = mutableStateFlow
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.banner.banners
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.banner.Banner
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.DefaultBanner
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.Importance
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
|
||||
class ServiceOutageBanner(context: Context) : Banner() {
|
||||
|
||||
override val enabled = TextSecurePreferences.getServiceOutage(context)
|
||||
|
||||
@Composable
|
||||
override fun DisplayBanner() {
|
||||
DefaultBanner(
|
||||
title = null,
|
||||
body = stringResource(id = R.string.reminder_header_service_outage_text),
|
||||
importance = Importance.ERROR
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun createFlow(context: Context): Flow<ServiceOutageBanner> = createAndEmit {
|
||||
ServiceOutageBanner(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.banner.banners
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.banner.Banner
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.Action
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.DefaultBanner
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.Importance
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.registration.ui.RegistrationActivity
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
|
||||
class UnauthorizedBanner(val context: Context) : Banner() {
|
||||
|
||||
override val enabled = TextSecurePreferences.isUnauthorizedReceived(context) || !SignalStore.account.isRegistered
|
||||
|
||||
@Composable
|
||||
override fun DisplayBanner() {
|
||||
DefaultBanner(
|
||||
title = null,
|
||||
body = stringResource(id = R.string.UnauthorizedReminder_this_is_likely_because_you_registered_your_phone_number_with_Signal_on_a_different_device),
|
||||
importance = Importance.ERROR,
|
||||
actions = listOf(
|
||||
Action(R.string.UnauthorizedReminder_reregister_action) {
|
||||
val registrationIntent = RegistrationActivity.newIntentForReRegistration(context)
|
||||
context.startActivity(registrationIntent)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun createFlow(context: Context): Flow<UnauthorizedBanner> = createAndEmit {
|
||||
UnauthorizedBanner(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.banner.banners
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.banner.Banner
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.Action
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.DefaultBanner
|
||||
import org.thoughtcrime.securesms.banner.ui.compose.Importance
|
||||
import org.thoughtcrime.securesms.keyvalue.AccountValues
|
||||
import org.thoughtcrime.securesms.keyvalue.AccountValues.UsernameSyncState
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
|
||||
class UsernameOutOfSyncBanner(private val context: Context, private val usernameSyncState: UsernameSyncState, private val onActionClick: (Boolean) -> Unit) : Banner() {
|
||||
|
||||
override val enabled = when (usernameSyncState) {
|
||||
AccountValues.UsernameSyncState.USERNAME_AND_LINK_CORRUPTED -> true
|
||||
AccountValues.UsernameSyncState.LINK_CORRUPTED -> true
|
||||
AccountValues.UsernameSyncState.IN_SYNC -> false
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun DisplayBanner() {
|
||||
DefaultBanner(
|
||||
title = null,
|
||||
body = if (usernameSyncState == UsernameSyncState.USERNAME_AND_LINK_CORRUPTED) {
|
||||
stringResource(id = R.string.UsernameOutOfSyncReminder__username_and_link_corrupt)
|
||||
} else {
|
||||
stringResource(id = R.string.UsernameOutOfSyncReminder__link_corrupt)
|
||||
},
|
||||
importance = Importance.ERROR,
|
||||
actions = listOf(
|
||||
Action(R.string.UsernameOutOfSyncReminder__fix_now) {
|
||||
onActionClick(usernameSyncState == UsernameSyncState.USERNAME_AND_LINK_CORRUPTED)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun createFlow(context: Context, onActionClick: (Boolean) -> Unit): Flow<UsernameOutOfSyncBanner> = createAndEmit {
|
||||
UsernameOutOfSyncBanner(context, SignalStore.account.usernameSyncState, onActionClick)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,6 @@
|
||||
|
||||
package org.thoughtcrime.securesms.banner.ui.compose
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -17,7 +15,6 @@ import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
@@ -30,8 +27,10 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
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.util.isNotNullOrBlank
|
||||
import org.thoughtcrime.securesms.R
|
||||
@@ -44,11 +43,9 @@ import org.thoughtcrime.securesms.R
|
||||
fun DefaultBanner(
|
||||
title: String?,
|
||||
body: String,
|
||||
importance: Importance,
|
||||
isDismissible: Boolean,
|
||||
onDismissListener: () -> Unit,
|
||||
onHideListener: () -> Unit,
|
||||
@DrawableRes icon: Int? = null,
|
||||
importance: Importance = Importance.NORMAL,
|
||||
onDismissListener: (() -> Unit)? = null,
|
||||
onHideListener: (() -> Unit)? = null,
|
||||
actions: List<Action> = emptyList(),
|
||||
showProgress: Boolean = false,
|
||||
progressText: String = "",
|
||||
@@ -59,7 +56,7 @@ fun DefaultBanner(
|
||||
.background(
|
||||
color = when (importance) {
|
||||
Importance.NORMAL -> MaterialTheme.colorScheme.surface
|
||||
Importance.ERROR, Importance.TERMINAL -> colorResource(id = R.color.reminder_background)
|
||||
Importance.ERROR -> colorResource(id = R.color.reminder_background)
|
||||
}
|
||||
)
|
||||
.border(
|
||||
@@ -73,22 +70,6 @@ fun DefaultBanner(
|
||||
modifier = Modifier
|
||||
.defaultMinSize(minHeight = 74.dp)
|
||||
) {
|
||||
if (icon != null) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 12.dp)
|
||||
.size(48.dp)
|
||||
.background(MaterialTheme.colorScheme.primaryContainer, CircleShape),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = icon),
|
||||
contentDescription = stringResource(id = R.string.ReminderView_icon_content_description),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(22.5.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
Column {
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
Column(
|
||||
@@ -101,7 +82,7 @@ fun DefaultBanner(
|
||||
text = title,
|
||||
color = when (importance) {
|
||||
Importance.NORMAL -> MaterialTheme.colorScheme.onSurface
|
||||
Importance.ERROR, Importance.TERMINAL -> colorResource(id = R.color.signal_light_colorOnSurface)
|
||||
Importance.ERROR -> colorResource(id = R.color.signal_light_colorOnSurface)
|
||||
},
|
||||
style = MaterialTheme.typography.bodyLarge
|
||||
)
|
||||
@@ -111,7 +92,7 @@ fun DefaultBanner(
|
||||
text = body,
|
||||
color = when (importance) {
|
||||
Importance.NORMAL -> MaterialTheme.colorScheme.onSurfaceVariant
|
||||
Importance.ERROR, Importance.TERMINAL -> colorResource(id = R.color.signal_light_colorOnSurface)
|
||||
Importance.ERROR -> colorResource(id = R.color.signal_light_colorOnSurface)
|
||||
},
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
@@ -136,16 +117,16 @@ fun DefaultBanner(
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = when (importance) {
|
||||
Importance.NORMAL -> MaterialTheme.colorScheme.onSurfaceVariant
|
||||
Importance.ERROR, Importance.TERMINAL -> colorResource(id = R.color.signal_light_colorOnSurface)
|
||||
Importance.ERROR -> colorResource(id = R.color.signal_light_colorOnSurface)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (isDismissible) {
|
||||
if (onDismissListener != null) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
onHideListener()
|
||||
onHideListener?.invoke()
|
||||
onDismissListener()
|
||||
},
|
||||
modifier = Modifier.size(48.dp)
|
||||
@@ -163,7 +144,13 @@ fun DefaultBanner(
|
||||
) {
|
||||
for (action in actions) {
|
||||
TextButton(onClick = action.onClick) {
|
||||
Text(text = stringResource(id = action.label))
|
||||
Text(
|
||||
text = if (!action.isPluralizedLabel) {
|
||||
stringResource(id = action.label)
|
||||
} else {
|
||||
pluralStringResource(id = action.label, count = action.pluralQuantity)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,24 +159,40 @@ fun DefaultBanner(
|
||||
}
|
||||
}
|
||||
|
||||
data class Action(@StringRes val label: Int, val onClick: () -> Unit)
|
||||
data class Action(val label: Int, val isPluralizedLabel: Boolean = false, val pluralQuantity: Int = 0, val onClick: () -> Unit)
|
||||
|
||||
enum class Importance {
|
||||
NORMAL, ERROR, TERMINAL
|
||||
NORMAL, ERROR
|
||||
}
|
||||
|
||||
@Composable
|
||||
@SignalPreview
|
||||
private fun BubblesOptOutPreview() {
|
||||
Previews.Preview {
|
||||
DefaultBanner(
|
||||
title = null,
|
||||
body = stringResource(id = R.string.BubbleOptOutTooltip__description),
|
||||
actions = listOf(
|
||||
Action(R.string.BubbleOptOutTooltip__turn_off) {},
|
||||
Action(R.string.BubbleOptOutTooltip__not_now) {}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@SignalPreview
|
||||
private fun ForcedUpgradePreview() {
|
||||
DefaultBanner(
|
||||
title = null,
|
||||
body = stringResource(id = R.string.OutdatedBuildReminder_your_version_of_signal_will_expire_today),
|
||||
importance = Importance.TERMINAL,
|
||||
isDismissible = false,
|
||||
actions = listOf(Action(R.string.ExpiredBuildReminder_update_now) {}),
|
||||
onHideListener = { },
|
||||
onDismissListener = {}
|
||||
)
|
||||
Previews.Preview {
|
||||
DefaultBanner(
|
||||
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) {}),
|
||||
onHideListener = { },
|
||||
onDismissListener = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -199,39 +202,16 @@ private fun FullyLoadedErrorPreview() {
|
||||
Action(R.string.ExpiredBuildReminder_update_now) { },
|
||||
Action(R.string.BubbleOptOutTooltip__turn_off) { }
|
||||
)
|
||||
DefaultBanner(
|
||||
icon = R.drawable.symbol_error_circle_24,
|
||||
title = "Error",
|
||||
body = "Creating more errors.",
|
||||
importance = Importance.ERROR,
|
||||
isDismissible = true,
|
||||
actions = actions,
|
||||
showProgress = true,
|
||||
progressText = "4 out of 10 errors created.",
|
||||
progressPercent = 40,
|
||||
onHideListener = { },
|
||||
onDismissListener = {}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@SignalPreview
|
||||
private fun FullyLoadedTerminalPreview() {
|
||||
val actions = listOf(
|
||||
Action(R.string.ExpiredBuildReminder_update_now) { },
|
||||
Action(R.string.BubbleOptOutTooltip__turn_off) { }
|
||||
)
|
||||
DefaultBanner(
|
||||
icon = R.drawable.symbol_error_circle_24,
|
||||
title = "Terminal",
|
||||
body = "This is a terminal state.",
|
||||
importance = Importance.TERMINAL,
|
||||
isDismissible = true,
|
||||
actions = actions,
|
||||
showProgress = true,
|
||||
progressText = "93% terminated",
|
||||
progressPercent = 93,
|
||||
onHideListener = { },
|
||||
onDismissListener = {}
|
||||
)
|
||||
Previews.Preview {
|
||||
DefaultBanner(
|
||||
title = "Error",
|
||||
body = "Creating more errors.",
|
||||
importance = Importance.ERROR,
|
||||
actions = actions,
|
||||
showProgress = true,
|
||||
progressText = "4 out of 10 errors created.",
|
||||
progressPercent = 40,
|
||||
onDismissListener = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
package org.thoughtcrime.securesms.components.reminder;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||
import org.thoughtcrime.securesms.util.PlayStoreUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Showed when a build has fully expired (either via the compile-time constant, or remote
|
||||
|
||||
@@ -97,7 +97,7 @@ import org.thoughtcrime.securesms.badges.self.expired.ExpiredOneTimeBadgeBottomS
|
||||
import org.thoughtcrime.securesms.badges.self.expired.MonthlyDonationCanceledBottomSheetDialogFragment;
|
||||
import org.thoughtcrime.securesms.banner.Banner;
|
||||
import org.thoughtcrime.securesms.banner.BannerManager;
|
||||
import org.thoughtcrime.securesms.banner.banners.ExpiredBuildBanner;
|
||||
import org.thoughtcrime.securesms.banner.banners.OutdatedBuildBanner;
|
||||
import org.thoughtcrime.securesms.components.DeleteSyncEducationDialog;
|
||||
import org.thoughtcrime.securesms.components.Material3SearchToolbar;
|
||||
import org.thoughtcrime.securesms.components.RatingManager;
|
||||
@@ -882,7 +882,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
|
||||
private void initializeBanners() {
|
||||
if (RemoteConfig.newBannerUi()) {
|
||||
final List<Flow<Banner>> bannerRepositories = List.of(ExpiredBuildBanner.createFlow(requireContext()));
|
||||
final List<Flow<? extends Banner>> bannerRepositories = List.of(OutdatedBuildBanner.createFlow(requireContext()));
|
||||
final BannerManager bannerManager = new BannerManager(bannerRepositories);
|
||||
bannerManager.setContent(bannerView.get());
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@ import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.compose.ui.platform.ComposeView;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.lifecycle.FlowLiveDataConversions;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
@@ -25,6 +27,9 @@ import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.LoggingFragment;
|
||||
import org.thoughtcrime.securesms.PaymentPreferencesDirections;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.banner.Banner;
|
||||
import org.thoughtcrime.securesms.banner.BannerManager;
|
||||
import org.thoughtcrime.securesms.banner.banners.EnclaveFailureBanner;
|
||||
import org.thoughtcrime.securesms.components.reminder.EnclaveFailureReminder;
|
||||
import org.thoughtcrime.securesms.components.reminder.ReminderView;
|
||||
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity;
|
||||
@@ -40,13 +45,17 @@ import org.thoughtcrime.securesms.payments.preferences.model.PaymentItem;
|
||||
import org.thoughtcrime.securesms.registration.ui.RegistrationActivity;
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||
import org.thoughtcrime.securesms.util.PlayStoreUtil;
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig;
|
||||
import org.thoughtcrime.securesms.util.SpanUtil;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.navigation.SafeNavigation;
|
||||
import org.thoughtcrime.securesms.util.views.Stub;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import kotlinx.coroutines.flow.Flow;
|
||||
|
||||
public class PaymentsHomeFragment extends LoggingFragment {
|
||||
private static final int DAYS_UNTIL_REPROMPT_PAYMENT_LOCK = 30;
|
||||
private static final int MAX_PAYMENT_LOCK_SKIP_COUNT = 2;
|
||||
@@ -99,6 +108,7 @@ public class PaymentsHomeFragment extends LoggingFragment {
|
||||
View refresh = view.findViewById(R.id.payments_home_fragment_header_refresh);
|
||||
LottieAnimationView refreshAnimation = view.findViewById(R.id.payments_home_fragment_header_refresh_animation);
|
||||
Stub<ReminderView> reminderView = ViewUtil.findStubById(view, R.id.reminder);
|
||||
Stub<ComposeView> bannerView = ViewUtil.findStubById(view, R.id.banner_compose_view);
|
||||
|
||||
toolbar.setNavigationOnClickListener(v -> {
|
||||
viewModel.markAllPaymentsSeen();
|
||||
@@ -254,22 +264,33 @@ public class PaymentsHomeFragment extends LoggingFragment {
|
||||
}
|
||||
});
|
||||
|
||||
viewModel.getEnclaveFailure().observe(getViewLifecycleOwner(), failure -> {
|
||||
if (failure) {
|
||||
showUpdateIsRequiredDialog();
|
||||
reminderView.get().showReminder(new EnclaveFailureReminder(requireContext()));
|
||||
reminderView.get().setOnActionClickListener(actionId -> {
|
||||
if (actionId == R.id.reminder_action_update_now) {
|
||||
PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(requireContext());
|
||||
} else if (actionId == R.id.reminder_action_re_register) {
|
||||
startActivity(RegistrationActivity.newIntentForReRegistration(requireContext()));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
reminderView.get().requestDismiss();
|
||||
}
|
||||
});
|
||||
|
||||
if (RemoteConfig.newBannerUi()) {
|
||||
viewModel.getEnclaveFailure().observe(getViewLifecycleOwner(), failure -> {
|
||||
if (failure) {
|
||||
showUpdateIsRequiredDialog();
|
||||
}
|
||||
});
|
||||
final Flow<Boolean> enclaveFailureFlow = FlowLiveDataConversions.asFlow(viewModel.getEnclaveFailure());
|
||||
final List<Flow<? extends Banner>> bannerRepositories = List.of(EnclaveFailureBanner.Companion.mapBooleanFlowToBannerFlow(enclaveFailureFlow, requireContext()));
|
||||
final BannerManager bannerManager = new BannerManager(bannerRepositories);
|
||||
bannerManager.setContent(bannerView.get());
|
||||
} else {
|
||||
viewModel.getEnclaveFailure().observe(getViewLifecycleOwner(), failure -> {
|
||||
if (failure) {
|
||||
showUpdateIsRequiredDialog();
|
||||
reminderView.get().showReminder(new EnclaveFailureReminder(requireContext()));
|
||||
reminderView.get().setOnActionClickListener(actionId -> {
|
||||
if (actionId == R.id.reminder_action_update_now) {
|
||||
PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(requireContext());
|
||||
} else if (actionId == R.id.reminder_action_re_register) {
|
||||
startActivity(RegistrationActivity.newIntentForReRegistration(requireContext()));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
reminderView.get().requestDismiss();
|
||||
}
|
||||
});
|
||||
}
|
||||
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressed());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user