diff --git a/app/src/main/java/org/thoughtcrime/securesms/badges/BadgeImageView.kt b/app/src/main/java/org/thoughtcrime/securesms/badges/BadgeImageView.kt
index 3750f02674..5d12d596eb 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/badges/BadgeImageView.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/badges/BadgeImageView.kt
@@ -8,6 +8,7 @@ import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.badges.glide.BadgeSpriteTransformation
import org.thoughtcrime.securesms.badges.models.Badge
+import org.thoughtcrime.securesms.components.settings.app.subscription.BadgeImageSize
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
import org.thoughtcrime.securesms.glide.GiftBadgeModel
import org.thoughtcrime.securesms.mms.GlideApp
@@ -31,6 +32,10 @@ class BadgeImageView @JvmOverloads constructor(
isClickable = false
}
+ constructor(context: Context, badgeImageSize: BadgeImageSize) : this(context) {
+ badgeSize = badgeImageSize.sizeCode
+ }
+
override fun setOnClickListener(l: OnClickListener?) {
val wasClickable = isClickable
super.setOnClickListener(l)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/badges/models/Badge.kt b/app/src/main/java/org/thoughtcrime/securesms/badges/models/Badge.kt
index 2b616b49f6..c1cabbe0b7 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/badges/models/Badge.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/badges/models/Badge.kt
@@ -6,6 +6,7 @@ import android.os.Parcelable
import android.view.View
import android.widget.ImageView
import android.widget.TextView
+import androidx.compose.runtime.Stable
import com.bumptech.glide.load.Key
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy
@@ -25,6 +26,7 @@ typealias OnBadgeClicked = (Badge, Boolean, Boolean) -> Unit
/**
* A Badge that can be collected and displayed by a user.
*/
+@Stable
@Parcelize
data class Badge(
val id: String,
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/BadgeImage.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/BadgeImage.kt
new file mode 100644
index 0000000000..6b4b09d462
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/BadgeImage.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2023 Signal Messenger, LLC
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package org.thoughtcrime.securesms.components.settings.app.subscription
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalInspectionMode
+import androidx.compose.ui.viewinterop.AndroidView
+import org.thoughtcrime.securesms.badges.BadgeImageView
+import org.thoughtcrime.securesms.badges.models.Badge
+
+enum class BadgeImageSize(val sizeCode: Int) {
+ SMALL(0),
+ MEDIUM(1),
+ LARGE(2),
+ X_LARGE(3),
+ BADGE_64(4),
+ BADGE_112(5)
+}
+
+@Composable
+fun BadgeImage112(
+ badge: Badge?,
+ modifier: Modifier = Modifier
+) {
+ if (LocalInspectionMode.current) {
+ Box(modifier = modifier.background(color = Color.Red))
+ } else {
+ AndroidView(
+ factory = {
+ BadgeImageView(it, BadgeImageSize.BADGE_112)
+ },
+ update = {
+ it.setBadge(badge)
+ }
+ )
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/DonationPendingBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/DonationPendingBottomSheet.kt
new file mode 100644
index 0000000000..8909583a6f
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/DonationPendingBottomSheet.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2023 Signal Messenger, LLC
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package org.thoughtcrime.securesms.components.settings.app.subscription
+
+import android.content.DialogInterface
+import android.net.Uri
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.LocalTextStyle
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.navigation.fragment.findNavController
+import androidx.navigation.fragment.navArgs
+import org.signal.core.ui.BottomSheets
+import org.signal.core.ui.Buttons
+import org.signal.core.ui.Texts
+import org.signal.core.ui.theme.SignalTheme
+import org.thoughtcrime.securesms.R
+import org.thoughtcrime.securesms.badges.models.Badge
+import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity
+import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonateToSignalType
+import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment
+import org.thoughtcrime.securesms.util.SpanUtil
+
+/**
+ * Displayed after the user completes the donation flow for a bank transfer.
+ */
+class DonationPendingBottomSheet : ComposeBottomSheetDialogFragment() {
+
+ private val args: DonationPendingBottomSheetArgs by navArgs()
+
+ @Composable
+ override fun SheetContent() {
+ DonationPendingBottomSheetContent(
+ badge = args.request.badge,
+ onDoneClick = this::onDoneClick
+ )
+ }
+
+ private fun onDoneClick() {
+ dismissAllowingStateLoss()
+ }
+
+ override fun onDismiss(dialog: DialogInterface) {
+ super.onDismiss(dialog)
+
+ if (args.request.donateToSignalType == DonateToSignalType.ONE_TIME) {
+ findNavController().popBackStack()
+ } else {
+ requireActivity().finish()
+ requireActivity().startActivity(AppSettingsActivity.manageSubscriptions(requireContext()))
+ }
+ }
+}
+
+@Preview
+@Composable
+fun DonationPendingBottomSheetContentPreview() {
+ SignalTheme {
+ Surface {
+ DonationPendingBottomSheetContent(
+ badge = Badge(
+ id = "",
+ category = Badge.Category.Donor,
+ name = "Signal Star",
+ description = "",
+ imageUrl = Uri.EMPTY,
+ imageDensity = "",
+ expirationTimestamp = 0L,
+ visible = true,
+ duration = 0L
+ ),
+ onDoneClick = {}
+ )
+ }
+ }
+}
+
+@Composable
+private fun DonationPendingBottomSheetContent(
+ badge: Badge,
+ onDoneClick: () -> Unit
+) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.padding(horizontal = 44.dp)
+ ) {
+ BottomSheets.Handle()
+
+ BadgeImage112(
+ badge = badge,
+ modifier = Modifier
+ .padding(top = 21.dp, bottom = 16.dp)
+ .size(80.dp)
+ )
+
+ Text(
+ text = stringResource(id = R.string.DonationPendingBottomSheet__donation_pending),
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(bottom = 8.dp)
+ )
+
+ // TODO [sepa] -- Need proper copy here for one-time donations.
+ Text(
+ text = stringResource(id = R.string.DonationPendingBottomSheet__your_monthly_donation_is_pending, badge.name),
+ textAlign = TextAlign.Center,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ modifier = Modifier.padding(bottom = 20.dp)
+ )
+
+ val learnMore = stringResource(id = R.string.DonationPendingBottomSheet__learn_more)
+ val fullString = stringResource(id = R.string.DonationPendingBottomSheet__bank_transfers_usually_take, learnMore)
+ val spanned = SpanUtil.urlSubsequence(fullString, learnMore, "") // TODO [sepa] URL
+ Texts.LinkifiedText(
+ textWithUrlSpans = spanned,
+ onUrlClick = {}, // TODO [sepa] URL
+ style = LocalTextStyle.current.copy(textAlign = TextAlign.Center, color = MaterialTheme.colorScheme.onSurfaceVariant),
+ modifier = Modifier.padding(bottom = 48.dp)
+ )
+
+ Buttons.LargeTonal(
+ onClick = onDoneClick,
+ modifier = Modifier
+ .defaultMinSize(minWidth = 220.dp)
+ .padding(bottom = 56.dp)
+ ) {
+ Text(text = stringResource(id = R.string.DonationPendingBottomSheet__done))
+ }
+ }
+}
diff --git a/app/src/main/res/navigation/donate_to_signal.xml b/app/src/main/res/navigation/donate_to_signal.xml
index b7a162a528..213509859c 100644
--- a/app/src/main/res/navigation/donate_to_signal.xml
+++ b/app/src/main/res/navigation/donate_to_signal.xml
@@ -45,6 +45,9 @@
+
@@ -249,4 +252,16 @@
app:destination="@id/yourInformationIsPrivateBottomSheet" />
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 59badd3117..b0e32ec92e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -5865,6 +5865,17 @@
Look for your IBAN number at the top of your bank statement. IBAN numbers contain up to 32 characters. The name you enter should match your full name on your bank account. Contact your bank for more information.
+
+ Donation pending
+
+ Your monthly donation is pending. You’ll be able to display the %1$s badge on your profile when your donation is received.
+
+ Bank transfers usually take 1 business day to process. %1$s
+
+ Learn more
+
+ Done
+
Cancelling…