Add payment history screens for backups.

This commit is contained in:
Alex Hart 2024-06-21 14:08:10 -03:00 committed by Greyson Parrelli
parent 690236c4e5
commit 45239c2264
18 changed files with 518 additions and 37 deletions

View file

@ -5,15 +5,12 @@
package org.thoughtcrime.securesms.backup.v2.ui.subscription
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.fragment.findNavController
@ -22,6 +19,7 @@ import org.signal.donations.InAppPaymentType
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonationCheckoutDelegate
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonationProcessorAction
import org.thoughtcrime.securesms.compose.ComposeFragment
import org.thoughtcrime.securesms.compose.Nav
import org.thoughtcrime.securesms.database.InAppPaymentTable
import org.thoughtcrime.securesms.util.navigation.safeNavigate
import org.thoughtcrime.securesms.util.viewModel
@ -62,13 +60,9 @@ class MessageBackupsFlowFragment : ComposeFragment(), DonationCheckoutDelegate.C
navController.enableOnBackPressed(true)
}
NavHost(
Nav.Host(
navController = navController,
startDestination = state.startScreen.name,
enterTransition = { slideInHorizontally(initialOffsetX = { it }) },
exitTransition = { slideOutHorizontally(targetOffsetX = { -it }) },
popEnterTransition = { slideInHorizontally(initialOffsetX = { -it }) },
popExitTransition = { slideOutHorizontally(targetOffsetX = { it }) }
startDestination = state.startScreen.name
) {
composable(route = MessageBackupsScreen.EDUCATION.name) {
MessageBackupsEducationScreen(

View file

@ -119,7 +119,7 @@ class RemoteBackupsSettingsFragment : ComposeFragment() {
}
override fun onViewPaymentHistory() {
// TODO [message-backups] Navigate to payment history
findNavController().safeNavigate(R.id.action_remoteBackupsSettingsFragment_to_remoteBackupsPaymentHistoryFragment)
}
override fun onBackupNowClick() {

View file

@ -0,0 +1,311 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.settings.app.chats.backups.history
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.fragment.app.viewModels
import androidx.navigation.NavType
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.fragment.findNavController
import androidx.navigation.navArgument
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toPersistentList
import org.signal.core.ui.Buttons
import org.signal.core.ui.Dividers
import org.signal.core.ui.Previews
import org.signal.core.ui.Rows
import org.signal.core.ui.Scaffolds
import org.signal.core.ui.SignalPreview
import org.signal.core.ui.Texts
import org.signal.core.util.money.FiatMoney
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.compose.ComposeFragment
import org.thoughtcrime.securesms.compose.Nav
import org.thoughtcrime.securesms.database.model.DonationReceiptRecord
import org.thoughtcrime.securesms.payments.FiatMoneyUtil
import org.thoughtcrime.securesms.util.DateUtils
import java.math.BigDecimal
import java.util.Calendar
import java.util.Currency
import java.util.Locale
/**
* Displays a list or detail view of in-app-payment receipts related to
* backups.
*/
class RemoteBackupsPaymentHistoryFragment : ComposeFragment() {
private val viewModel: RemoteBackupsPaymentHistoryViewModel by viewModels()
@Composable
override fun FragmentContent() {
val state by viewModel.state.collectAsState()
val navController = rememberNavController()
LaunchedEffect(Unit) {
navController.setOnBackPressedDispatcher(requireActivity().onBackPressedDispatcher)
navController.enableOnBackPressed(true)
}
val onNavigationClick = remember {
{
if (!navController.popBackStack()) {
findNavController().popBackStack()
}
}
}
Nav.Host(navController = navController, startDestination = "list") {
composable("list") {
PaymentHistoryContent(
state = state,
onNavigationClick = onNavigationClick,
onRecordClick = { navController.navigate("detail/${it.id}") }
)
}
composable("detail/{recordId}", listOf(navArgument("recordId") { type = NavType.LongType })) { backStackEntry ->
val recordId = backStackEntry.arguments?.getLong("recordId")!!
val record = state.records[recordId]!!
PaymentHistoryDetails(
record = record,
onNavigationClick = onNavigationClick,
onShareClick = {} // TODO [message-backups] Generate shareable png
)
}
}
}
}
@Composable
private fun PaymentHistoryContent(
state: RemoteBackupsPaymentHistoryState,
onNavigationClick: () -> Unit,
onRecordClick: (DonationReceiptRecord) -> Unit
) {
Scaffolds.Settings(
title = stringResource(id = R.string.RemoteBackupsPaymentHistoryFragment__payment_history),
navigationIconPainter = painterResource(id = R.drawable.symbol_arrow_left_24),
onNavigationClick = onNavigationClick
) {
val itemList = remember(state.records) { state.records.values.toPersistentList() }
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(it)
) {
itemsIndexed(
items = itemList,
key = { _, item -> item.id }
) { idx, item ->
val previous = itemList.getOrNull(idx - 1)
val previousYear = rememberYear(timestamp = previous?.timestamp ?: 0)
val ourYear = rememberYear(timestamp = item.timestamp)
if (previousYear != ourYear) {
Texts.SectionHeader(text = "$ourYear")
}
PaymentHistoryRow(item, onRecordClick)
}
}
}
}
@Composable
private fun rememberYear(timestamp: Long): Int {
if (timestamp == 0L) {
return -1
}
val calendar = remember {
Calendar.getInstance()
}
return remember(timestamp) {
calendar.timeInMillis = timestamp
calendar.get(Calendar.YEAR)
}
}
@Composable
private fun PaymentHistoryRow(
record: DonationReceiptRecord,
onRecordClick: (DonationReceiptRecord) -> Unit
) {
val date = remember(record.timestamp) {
DateUtils.formatDateWithYear(Locale.getDefault(), record.timestamp)
}
val onClick = remember(record) {
{ onRecordClick(record) }
}
Rows.TextRow(text = {
Column(
modifier = Modifier.weight(1f)
) {
Text(
text = date,
style = MaterialTheme.typography.bodyLarge
)
Text(
text = stringResource(id = R.string.RemoteBackupsPaymentHistoryFragment__text_and_all_media_backup),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
val resources = LocalContext.current.resources
val fiat = remember(record.amount) {
FiatMoneyUtil.format(resources, record.amount, FiatMoneyUtil.formatOptions().trimZerosAfterDecimal())
}
Text(text = fiat)
}, onClick = onClick)
}
@Composable
private fun PaymentHistoryDetails(
record: DonationReceiptRecord,
onNavigationClick: () -> Unit,
onShareClick: () -> Unit
) {
Scaffolds.Settings(
title = stringResource(id = R.string.RemoteBackupsPaymentHistoryFragment__payment_details),
onNavigationClick = onNavigationClick,
navigationIconPainter = painterResource(id = R.drawable.symbol_arrow_left_24)
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(it)
) {
val resources = LocalContext.current.resources
val formattedAmount = remember(record.amount) {
FiatMoneyUtil.format(resources, record.amount, FiatMoneyUtil.formatOptions().trimZerosAfterDecimal())
}
Image(
painter = painterResource(id = R.drawable.ic_signal_logo_type),
contentDescription = null,
modifier = Modifier
.align(alignment = Alignment.CenterHorizontally)
.padding(top = 24.dp, bottom = 16.dp)
)
Text(
text = formattedAmount,
style = MaterialTheme.typography.displayMedium,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
Dividers.Default()
Rows.TextRow(
text = stringResource(id = R.string.RemoteBackupsPaymentHistoryFragment__backup_type),
label = stringResource(id = R.string.RemoteBackupsPaymentHistoryFragment__text_and_all_media_backup)
)
val formattedDate = remember(record.timestamp) {
DateUtils.formatDateWithYear(Locale.getDefault(), record.timestamp)
}
Rows.TextRow(
text = stringResource(id = R.string.RemoteBackupsPaymentHistoryFragment__date_paid),
label = formattedDate
)
Spacer(modifier = Modifier.weight(1f))
Buttons.LargePrimary(
onClick = onShareClick,
modifier = Modifier
.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter))
.padding(bottom = 24.dp)
.fillMaxWidth()
) {
Text(text = stringResource(id = R.string.RemoteBackupsPaymentHistoryFragment__share))
}
}
}
}
@SignalPreview
@Composable
private fun PaymentHistoryContentPreview() {
Previews.Preview {
PaymentHistoryContent(
state = RemoteBackupsPaymentHistoryState(
records = persistentMapOf(
1L to testRecord()
)
),
onNavigationClick = {},
onRecordClick = {}
)
}
}
@SignalPreview
@Composable
private fun PaymentHistoryRowPreview() {
Previews.Preview {
PaymentHistoryRow(
record = testRecord(),
onRecordClick = {}
)
}
}
@SignalPreview
@Composable
private fun PaymentDetailsContentPreview() {
Previews.Preview {
PaymentHistoryDetails(
record = testRecord(),
onNavigationClick = {},
onShareClick = {}
)
}
}
private fun testRecord(): DonationReceiptRecord {
return DonationReceiptRecord(
id = 1,
amount = FiatMoney(BigDecimal.ONE, Currency.getInstance("USD")),
timestamp = 1718739691000,
type = DonationReceiptRecord.Type.RECURRING_BACKUP,
subscriptionLevel = 201
)
}

View file

@ -0,0 +1,16 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.settings.app.chats.backups.history
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.DonationReceiptRecord
object RemoteBackupsPaymentHistoryRepository {
fun getReceipts(): List<DonationReceiptRecord> {
return SignalDatabase.donationReceipts.getReceipts(DonationReceiptRecord.Type.RECURRING_BACKUP)
}
}

View file

@ -0,0 +1,16 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.settings.app.chats.backups.history
import androidx.compose.runtime.Stable
import kotlinx.collections.immutable.PersistentMap
import kotlinx.collections.immutable.persistentMapOf
import org.thoughtcrime.securesms.database.model.DonationReceiptRecord
@Stable
data class RemoteBackupsPaymentHistoryState(
val records: PersistentMap<Long, DonationReceiptRecord> = persistentMapOf()
)

View file

@ -0,0 +1,32 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.settings.app.chats.backups.history
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.collections.immutable.toPersistentMap
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class RemoteBackupsPaymentHistoryViewModel : ViewModel() {
private val internalStateFlow = MutableStateFlow(RemoteBackupsPaymentHistoryState())
val state: StateFlow<RemoteBackupsPaymentHistoryState> = internalStateFlow
init {
viewModelScope.launch {
val receipts = withContext(Dispatchers.IO) {
RemoteBackupsPaymentHistoryRepository.getReceipts()
}
internalStateFlow.update { state -> state.copy(records = receipts.associateBy { it.id }.toPersistentMap()) }
}
}
}

View file

@ -33,6 +33,7 @@ import org.thoughtcrime.securesms.components.settings.app.subscription.donate.Ch
import org.thoughtcrime.securesms.compose.ComposeFragment
import org.thoughtcrime.securesms.payments.FiatMoneyUtil
import org.thoughtcrime.securesms.util.DateUtils
import org.thoughtcrime.securesms.util.navigation.safeNavigate
import org.thoughtcrime.securesms.util.viewModel
import java.math.BigDecimal
import java.util.Currency
@ -67,7 +68,7 @@ class BackupsTypeSettingsFragment : ComposeFragment() {
}
override fun onPaymentHistoryClick() {
// TODO [message-backups] Navigate to payment history
findNavController().safeNavigate(R.id.action_backupsTypeSettingsFragment_to_remoteBackupsPaymentHistoryFragment)
}
override fun onChangeOrCancelSubscriptionClick() {

View file

@ -68,9 +68,10 @@ class DonationReceiptDetailFragment : DSLSettingsFragment(layoutId = R.layout.do
val today: String = DateUtils.formatDateWithDayOfWeek(Locale.getDefault(), System.currentTimeMillis())
val amount: String = FiatMoneyUtil.format(resources, record.amount)
val type: String = when (record.type) {
DonationReceiptRecord.Type.RECURRING -> getString(R.string.DonationReceiptDetailsFragment__s_dash_s, subscriptionName, getString(R.string.DonationReceiptListFragment__recurring))
DonationReceiptRecord.Type.BOOST -> getString(R.string.DonationReceiptListFragment__one_time)
DonationReceiptRecord.Type.GIFT -> getString(R.string.DonationReceiptListFragment__donation_for_a_friend)
DonationReceiptRecord.Type.RECURRING_DONATION -> getString(R.string.DonationReceiptDetailsFragment__s_dash_s, subscriptionName, getString(R.string.DonationReceiptListFragment__recurring))
DonationReceiptRecord.Type.ONE_TIME_DONATION -> getString(R.string.DonationReceiptListFragment__one_time)
DonationReceiptRecord.Type.ONE_TIME_GIFT -> getString(R.string.DonationReceiptListFragment__donation_for_a_friend)
DonationReceiptRecord.Type.RECURRING_BACKUP -> error("Not supported in this fragment")
}
val datePaid: String = DateUtils.formatDate(Locale.getDefault(), record.timestamp)
@ -140,9 +141,10 @@ class DonationReceiptDetailFragment : DSLSettingsFragment(layoutId = R.layout.do
title = DSLSettingsText.from(R.string.DonationReceiptDetailsFragment__donation_type),
summary = DSLSettingsText.from(
when (record.type) {
DonationReceiptRecord.Type.RECURRING -> getString(R.string.DonationReceiptDetailsFragment__s_dash_s, subscriptionName, getString(R.string.DonationReceiptListFragment__recurring))
DonationReceiptRecord.Type.BOOST -> getString(R.string.DonationReceiptListFragment__one_time)
DonationReceiptRecord.Type.GIFT -> getString(R.string.DonationReceiptListFragment__donation_for_a_friend)
DonationReceiptRecord.Type.RECURRING_DONATION -> getString(R.string.DonationReceiptDetailsFragment__s_dash_s, subscriptionName, getString(R.string.DonationReceiptListFragment__recurring))
DonationReceiptRecord.Type.ONE_TIME_DONATION -> getString(R.string.DonationReceiptListFragment__one_time)
DonationReceiptRecord.Type.ONE_TIME_GIFT -> getString(R.string.DonationReceiptListFragment__donation_for_a_friend)
DonationReceiptRecord.Type.RECURRING_BACKUP -> error("Not supported in this fragment.")
}
)
)

View file

@ -42,9 +42,10 @@ object DonationReceiptListItem {
dateView.text = DateUtils.formatDate(Locale.getDefault(), model.record.timestamp)
typeView.setText(
when (model.record.type) {
DonationReceiptRecord.Type.RECURRING -> R.string.DonationReceiptListFragment__recurring
DonationReceiptRecord.Type.BOOST -> R.string.DonationReceiptListFragment__one_time
DonationReceiptRecord.Type.GIFT -> R.string.DonationReceiptListFragment__donation_for_a_friend
DonationReceiptRecord.Type.RECURRING_DONATION -> R.string.DonationReceiptListFragment__recurring
DonationReceiptRecord.Type.ONE_TIME_DONATION -> R.string.DonationReceiptListFragment__one_time
DonationReceiptRecord.Type.ONE_TIME_GIFT -> R.string.DonationReceiptListFragment__donation_for_a_friend
DonationReceiptRecord.Type.RECURRING_BACKUP -> error("Not supported in this fragment")
}
)
moneyView.text = FiatMoneyUtil.format(context.resources, model.record.amount)

View file

@ -10,9 +10,9 @@ class DonationReceiptListPageAdapter(fragment: Fragment) : FragmentStateAdapter(
override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> DonationReceiptListPageFragment.create(null)
1 -> DonationReceiptListPageFragment.create(DonationReceiptRecord.Type.RECURRING)
2 -> DonationReceiptListPageFragment.create(DonationReceiptRecord.Type.BOOST)
3 -> DonationReceiptListPageFragment.create(DonationReceiptRecord.Type.GIFT)
1 -> DonationReceiptListPageFragment.create(DonationReceiptRecord.Type.RECURRING_DONATION)
2 -> DonationReceiptListPageFragment.create(DonationReceiptRecord.Type.ONE_TIME_DONATION)
3 -> DonationReceiptListPageFragment.create(DonationReceiptRecord.Type.ONE_TIME_GIFT)
else -> error("Unsupported position $position")
}
}

View file

@ -73,8 +73,8 @@ class DonationReceiptListPageFragment : Fragment(R.layout.donation_receipt_list_
private fun getBadgeForRecord(record: DonationReceiptRecord, badges: List<DonationReceiptBadge>): Badge? {
return when (record.type) {
DonationReceiptRecord.Type.BOOST -> badges.firstOrNull { it.type == DonationReceiptRecord.Type.BOOST }?.badge
DonationReceiptRecord.Type.GIFT -> badges.firstOrNull { it.type == DonationReceiptRecord.Type.GIFT }?.badge
DonationReceiptRecord.Type.ONE_TIME_DONATION -> badges.firstOrNull { it.type == DonationReceiptRecord.Type.ONE_TIME_DONATION }?.badge
DonationReceiptRecord.Type.ONE_TIME_GIFT -> badges.firstOrNull { it.type == DonationReceiptRecord.Type.ONE_TIME_GIFT }?.badge
else -> badges.firstOrNull { it.level == record.subscriptionLevel }?.badge
}
}

View file

@ -17,13 +17,13 @@ class DonationReceiptListRepository {
}.map { response ->
if (response.result.isPresent) {
val config = response.result.get()
val boostBadge = DonationReceiptBadge(DonationReceiptRecord.Type.BOOST, -1, config.getBoostBadges().first())
val giftBadge = DonationReceiptBadge(DonationReceiptRecord.Type.GIFT, -1, config.getGiftBadges().first())
val boostBadge = DonationReceiptBadge(DonationReceiptRecord.Type.ONE_TIME_DONATION, -1, config.getBoostBadges().first())
val giftBadge = DonationReceiptBadge(DonationReceiptRecord.Type.ONE_TIME_GIFT, -1, config.getGiftBadges().first())
val subBadges = config.getSubscriptionLevels().map {
DonationReceiptBadge(
level = it.key,
badge = Badges.fromServiceBadge(it.value.badge),
type = DonationReceiptRecord.Type.RECURRING
type = DonationReceiptRecord.Type.RECURRING_DONATION
)
}
subBadges + boostBadge + giftBadge

View file

@ -0,0 +1,49 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.compose
import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
/**
* Default Navigation utilities for compose.
*/
object Nav {
@Composable
fun Host(
navController: NavHostController,
startDestination: String,
modifier: Modifier = Modifier,
route: String? = null,
enterTransition: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition) = { slideInHorizontally(initialOffsetX = { it }) },
exitTransition: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition) = { slideOutHorizontally(targetOffsetX = { -it }) },
popEnterTransition: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition) = { slideInHorizontally(initialOffsetX = { -it }) },
popExitTransition: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition) = { slideOutHorizontally(targetOffsetX = { it }) },
builder: NavGraphBuilder.() -> Unit
) {
NavHost(
navController = navController,
startDestination = startDestination,
modifier = modifier,
route = route,
enterTransition = enterTransition,
exitTransition = exitTransition,
popEnterTransition = popEnterTransition,
popExitTransition = popExitTransition,
builder = builder
)
}
}

View file

@ -74,7 +74,7 @@ class DonationReceiptTable(context: Context, databaseHelper: SignalDatabase) : D
val (where, whereArgs) = if (type != null) {
"$TYPE = ?" to SqlUtil.buildArgs(type.code)
} else {
null to null
"$TYPE != ?" to SqlUtil.buildArgs(DonationReceiptRecord.Type.RECURRING_DONATION)
}
readableDatabase.query(TABLE_NAME, null, where, whereArgs, null, null, "$DATE DESC").use { cursor ->

View file

@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.database.model
import org.signal.core.util.money.FiatMoney
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription
import org.whispersystems.signalservice.internal.push.SubscriptionsConfiguration
import java.util.Currency
data class DonationReceiptRecord(
@ -12,9 +13,10 @@ data class DonationReceiptRecord(
val subscriptionLevel: Int
) {
enum class Type(val code: String) {
RECURRING("recurring"),
BOOST("boost"),
GIFT("gift");
RECURRING_BACKUP("recurring_backup"),
RECURRING_DONATION("recurring"),
ONE_TIME_DONATION("boost"),
ONE_TIME_GIFT("gift");
companion object {
fun fromCode(code: String): Type {
@ -34,7 +36,7 @@ data class DonationReceiptRecord(
amount = FiatMoney(activeAmount, activeCurrency),
timestamp = System.currentTimeMillis(),
subscriptionLevel = subscription.level,
type = Type.RECURRING
type = if (subscription.level == SubscriptionsConfiguration.BACKUPS_LEVEL) Type.RECURRING_BACKUP else Type.RECURRING_DONATION
)
}
@ -44,7 +46,7 @@ data class DonationReceiptRecord(
amount = amount,
timestamp = System.currentTimeMillis(),
subscriptionLevel = -1,
type = Type.BOOST
type = Type.ONE_TIME_DONATION
)
}
@ -54,7 +56,7 @@ data class DonationReceiptRecord(
amount = amount,
timestamp = System.currentTimeMillis(),
subscriptionLevel = -1,
type = Type.GIFT
type = Type.ONE_TIME_GIFT
)
}
}

View file

@ -969,11 +969,32 @@
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_close_enter"
app:popExitAnim="@anim/fragment_close_exit" />
<action
android:id="@+id/action_remoteBackupsSettingsFragment_to_remoteBackupsPaymentHistoryFragment"
app:destination="@id/remoteBackupsPaymentHistoryFragment"
app:enterAnim="@anim/fragment_open_enter"
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_close_enter"
app:popExitAnim="@anim/fragment_close_exit" />
</fragment>
<fragment
android:id="@+id/backupsTypeSettingsFragment"
android:name="org.thoughtcrime.securesms.components.settings.app.chats.backups.type.BackupsTypeSettingsFragment" />
android:name="org.thoughtcrime.securesms.components.settings.app.chats.backups.type.BackupsTypeSettingsFragment">
<action
android:id="@+id/action_backupsTypeSettingsFragment_to_remoteBackupsPaymentHistoryFragment"
app:destination="@id/remoteBackupsPaymentHistoryFragment"
app:enterAnim="@anim/fragment_open_enter"
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_close_enter"
app:popExitAnim="@anim/fragment_close_exit" />
</fragment>
<fragment
android:id="@+id/remoteBackupsPaymentHistoryFragment"
android:name="org.thoughtcrime.securesms.components.settings.app.chats.backups.history.RemoteBackupsPaymentHistoryFragment"/>
<include app:graph="@navigation/username_link_settings" />
<include app:graph="@navigation/story_privacy_settings" />

View file

@ -969,11 +969,33 @@
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_close_enter"
app:popExitAnim="@anim/fragment_close_exit" />
<action
android:id="@+id/action_remoteBackupsSettingsFragment_to_remoteBackupsPaymentHistoryFragment"
app:destination="@id/remoteBackupsPaymentHistoryFragment"
app:enterAnim="@anim/fragment_open_enter"
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_close_enter"
app:popExitAnim="@anim/fragment_close_exit" />
</fragment>
<fragment
android:id="@+id/remoteBackupsPaymentHistoryFragment"
android:name="org.thoughtcrime.securesms.components.settings.app.chats.backups.history.RemoteBackupsPaymentHistoryFragment">
</fragment>
<fragment
android:id="@+id/backupsTypeSettingsFragment"
android:name="org.thoughtcrime.securesms.components.settings.app.chats.backups.type.BackupsTypeSettingsFragment" />
android:name="org.thoughtcrime.securesms.components.settings.app.chats.backups.type.BackupsTypeSettingsFragment">
<action
android:id="@+id/action_backupsTypeSettingsFragment_to_remoteBackupsPaymentHistoryFragment"
app:destination="@id/remoteBackupsPaymentHistoryFragment"
app:enterAnim="@anim/fragment_open_enter"
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_close_enter"
app:popExitAnim="@anim/fragment_close_exit" />
</fragment>
<include app:graph="@navigation/username_link_settings" />
<include app:graph="@navigation/story_privacy_settings" />

View file

@ -7047,6 +7047,20 @@
<!-- Educational bottom sheet confirm/dismiss button text shown to notify about delete syncs causing deletes to happen across all devices -->
<string name="DeleteSyncEducation_acknowledge_button">OK</string>
<!-- RemoteBackupsPaymentHistoryFragment -->
<!-- Title of the screen for payment history -->
<string name="RemoteBackupsPaymentHistoryFragment__payment_history">Payment history</string>
<!-- Description for backup rows -->
<string name="RemoteBackupsPaymentHistoryFragment__text_and_all_media_backup">Text and all media backup</string>
<!-- Title of the screen for payment details -->
<string name="RemoteBackupsPaymentHistoryFragment__payment_details">Payment details</string>
<!-- Title of row specifying the type of backup -->
<string name="RemoteBackupsPaymentHistoryFragment__backup_type">Backup type</string>
<!-- Title of row specifying the date the backup was paid on -->
<string name="RemoteBackupsPaymentHistoryFragment__date_paid">Date paid</string>
<!-- Button label to share the receipt -->
<string name="RemoteBackupsPaymentHistoryFragment__share">Share</string>
<!-- RemoteBackupsSettingsFragment -->
<!-- Displayed on the title bar -->
<string name="RemoteBackupsSettingsFragment__signal_backups">Signal Backups</string>