diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowFragment.kt index 3c0e2114b1..f36dd75f0e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowFragment.kt @@ -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( diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/RemoteBackupsSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/RemoteBackupsSettingsFragment.kt index 9af42fdc46..08e7347845 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/RemoteBackupsSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/RemoteBackupsSettingsFragment.kt @@ -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() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/history/RemoteBackupsPaymentHistoryFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/history/RemoteBackupsPaymentHistoryFragment.kt new file mode 100644 index 0000000000..6543f0230b --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/history/RemoteBackupsPaymentHistoryFragment.kt @@ -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 + ) +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/history/RemoteBackupsPaymentHistoryRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/history/RemoteBackupsPaymentHistoryRepository.kt new file mode 100644 index 0000000000..5c30b35b09 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/history/RemoteBackupsPaymentHistoryRepository.kt @@ -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 { + return SignalDatabase.donationReceipts.getReceipts(DonationReceiptRecord.Type.RECURRING_BACKUP) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/history/RemoteBackupsPaymentHistoryState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/history/RemoteBackupsPaymentHistoryState.kt new file mode 100644 index 0000000000..1123025cd6 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/history/RemoteBackupsPaymentHistoryState.kt @@ -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 = persistentMapOf() +) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/history/RemoteBackupsPaymentHistoryViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/history/RemoteBackupsPaymentHistoryViewModel.kt new file mode 100644 index 0000000000..02ac79cc67 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/history/RemoteBackupsPaymentHistoryViewModel.kt @@ -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 = internalStateFlow + + init { + viewModelScope.launch { + val receipts = withContext(Dispatchers.IO) { + RemoteBackupsPaymentHistoryRepository.getReceipts() + } + + internalStateFlow.update { state -> state.copy(records = receipts.associateBy { it.id }.toPersistentMap()) } + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/type/BackupsTypeSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/type/BackupsTypeSettingsFragment.kt index 4702116d2f..f2014d5057 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/type/BackupsTypeSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/type/BackupsTypeSettingsFragment.kt @@ -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() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/receipts/detail/DonationReceiptDetailFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/receipts/detail/DonationReceiptDetailFragment.kt index 9a29cc2cc9..0368401666 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/receipts/detail/DonationReceiptDetailFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/receipts/detail/DonationReceiptDetailFragment.kt @@ -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.") } ) ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/receipts/list/DonationReceiptListItem.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/receipts/list/DonationReceiptListItem.kt index e241d8d750..2d2427beb3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/receipts/list/DonationReceiptListItem.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/receipts/list/DonationReceiptListItem.kt @@ -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) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/receipts/list/DonationReceiptListPageAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/receipts/list/DonationReceiptListPageAdapter.kt index 31a90f4b90..121cd06800 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/receipts/list/DonationReceiptListPageAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/receipts/list/DonationReceiptListPageAdapter.kt @@ -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") } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/receipts/list/DonationReceiptListPageFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/receipts/list/DonationReceiptListPageFragment.kt index 48fbc8dbc1..615b02ae27 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/receipts/list/DonationReceiptListPageFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/receipts/list/DonationReceiptListPageFragment.kt @@ -73,8 +73,8 @@ class DonationReceiptListPageFragment : Fragment(R.layout.donation_receipt_list_ private fun getBadgeForRecord(record: DonationReceiptRecord, badges: List): 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 } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/receipts/list/DonationReceiptListRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/receipts/list/DonationReceiptListRepository.kt index e1bdb0527b..e23c96550d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/receipts/list/DonationReceiptListRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/receipts/list/DonationReceiptListRepository.kt @@ -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 diff --git a/app/src/main/java/org/thoughtcrime/securesms/compose/Nav.kt b/app/src/main/java/org/thoughtcrime/securesms/compose/Nav.kt new file mode 100644 index 0000000000..1413f1f059 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/compose/Nav.kt @@ -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.() -> EnterTransition) = { slideInHorizontally(initialOffsetX = { it }) }, + exitTransition: (AnimatedContentTransitionScope.() -> ExitTransition) = { slideOutHorizontally(targetOffsetX = { -it }) }, + popEnterTransition: (AnimatedContentTransitionScope.() -> EnterTransition) = { slideInHorizontally(initialOffsetX = { -it }) }, + popExitTransition: (AnimatedContentTransitionScope.() -> 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 + ) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/DonationReceiptTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/DonationReceiptTable.kt index 3f478d725d..161e8dde9d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/DonationReceiptTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/DonationReceiptTable.kt @@ -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 -> diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/DonationReceiptRecord.kt b/app/src/main/java/org/thoughtcrime/securesms/database/model/DonationReceiptRecord.kt index ee81accca7..858bbd76b7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/DonationReceiptRecord.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/DonationReceiptRecord.kt @@ -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 ) } } diff --git a/app/src/main/res/navigation/app_settings.xml b/app/src/main/res/navigation/app_settings.xml index 5c9d3c2bcc..015405d2bc 100644 --- a/app/src/main/res/navigation/app_settings.xml +++ b/app/src/main/res/navigation/app_settings.xml @@ -969,11 +969,32 @@ app:exitAnim="@anim/fragment_open_exit" app:popEnterAnim="@anim/fragment_close_enter" app:popExitAnim="@anim/fragment_close_exit" /> + + + android:name="org.thoughtcrime.securesms.components.settings.app.chats.backups.type.BackupsTypeSettingsFragment"> + + + + + diff --git a/app/src/main/res/navigation/app_settings_with_change_number_v2.xml b/app/src/main/res/navigation/app_settings_with_change_number_v2.xml index 2d19f576c3..a21bbea854 100644 --- a/app/src/main/res/navigation/app_settings_with_change_number_v2.xml +++ b/app/src/main/res/navigation/app_settings_with_change_number_v2.xml @@ -969,11 +969,33 @@ app:exitAnim="@anim/fragment_open_exit" app:popEnterAnim="@anim/fragment_close_enter" app:popExitAnim="@anim/fragment_close_exit" /> + + + + + + android:name="org.thoughtcrime.securesms.components.settings.app.chats.backups.type.BackupsTypeSettingsFragment"> + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1d63c4bb4e..1879c613c9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -7047,6 +7047,20 @@ OK + + + Payment history + + Text and all media backup + + Payment details + + Backup type + + Date paid + + Share + Signal Backups