diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonateToSignalFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonateToSignalFragment.kt index d809b04942..376c3fed25 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonateToSignalFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonateToSignalFragment.kt @@ -348,7 +348,7 @@ class DonateToSignalFragment : if (state.oneTimeDonationState.isOneTimeDonationLongRunning) { R.string.DonateToSignalFragment__bank_transfers_usually_take_1_business_day_to_process_onetime } else if (state.oneTimeDonationState.isNonVerifiedIdeal) { - R.string.DonateToSignalFragment__your_ideal_payment_is_still_processing + R.string.DonateToSignalFragment__your_ideal_wero_payment_is_still_processing } else { R.string.DonateToSignalFragment__your_payment_is_still_being_processed_onetime } @@ -356,7 +356,7 @@ class DonateToSignalFragment : if (state.monthlyDonationState.activeSubscription?.paymentMethod == ActiveSubscription.PaymentMethod.SEPA_DEBIT) { R.string.DonateToSignalFragment__bank_transfers_usually_take_1_business_day_to_process_monthly } else if (state.monthlyDonationState.nonVerifiedMonthlyDonation != null) { - R.string.DonateToSignalFragment__your_ideal_payment_is_still_processing + R.string.DonateToSignalFragment__your_ideal_wero_payment_is_still_processing } else { R.string.DonateToSignalFragment__your_payment_is_still_being_processed_monthly } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/gateway/GatewaySelectorBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/gateway/GatewaySelectorBottomSheet.kt index d0c3737240..ce76981f60 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/gateway/GatewaySelectorBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/gateway/GatewaySelectorBottomSheet.kt @@ -18,10 +18,10 @@ import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter import org.thoughtcrime.securesms.components.settings.DSLSettingsBottomSheetFragment import org.thoughtcrime.securesms.components.settings.DSLSettingsIcon import org.thoughtcrime.securesms.components.settings.DSLSettingsText -import org.thoughtcrime.securesms.components.settings.NO_TINT import org.thoughtcrime.securesms.components.settings.app.subscription.DonationSerializationHelper.toFiatMoney import org.thoughtcrime.securesms.components.settings.app.subscription.GooglePayComponent import org.thoughtcrime.securesms.components.settings.app.subscription.models.GooglePayButton +import org.thoughtcrime.securesms.components.settings.app.subscription.models.IdealWeroButton import org.thoughtcrime.securesms.components.settings.app.subscription.models.PayPalButton import org.thoughtcrime.securesms.components.settings.configure import org.thoughtcrime.securesms.components.settings.models.IndeterminateLoadingCircle @@ -51,6 +51,7 @@ class GatewaySelectorBottomSheet : DSLSettingsBottomSheetFragment() { GooglePayButton.register(adapter) PayPalButton.register(adapter) IndeterminateLoadingCircle.register(adapter) + IdealWeroButton.register(adapter) lifecycleDisposable.bindTo(viewLifecycleOwner) @@ -190,17 +191,16 @@ class GatewaySelectorBottomSheet : DSLSettingsBottomSheetFragment() { if (state.isIDEALAvailable) { space(16.dp) - tonalButton( - text = DSLSettingsText.from(R.string.GatewaySelectorBottomSheet__ideal), - icon = DSLSettingsIcon.from(R.drawable.logo_ideal, NO_TINT), - disableOnClick = true, - onClick = { - lifecycleDisposable += viewModel.updateInAppPaymentMethod(InAppPaymentData.PaymentMethodType.IDEAL) - .subscribeBy { - findNavController().popBackStack() - setFragmentResult(REQUEST_KEY, bundleOf(REQUEST_KEY to it)) - } - } + customPref( + IdealWeroButton.Model( + onClick = { + lifecycleDisposable += viewModel.updateInAppPaymentMethod(InAppPaymentData.PaymentMethodType.IDEAL) + .subscribeBy { + findNavController().popBackStack() + setFragmentResult(REQUEST_KEY, bundleOf(REQUEST_KEY to it)) + } + } + ) ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/transfer/ideal/IdealTransferDetailsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/transfer/ideal/IdealTransferDetailsFragment.kt index d0d0b92a6f..9c836b055f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/transfer/ideal/IdealTransferDetailsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/transfer/ideal/IdealTransferDetailsFragment.kt @@ -153,7 +153,7 @@ class IdealTransferDetailsFragment : ComposeFragment(), InAppPaymentCheckoutDele if (state.inAppPayment!!.type.recurring) { // TODO [message-requests] -- handle backup val formattedMoney = FiatMoneyUtil.format(requireContext().resources, state.inAppPayment.data.amount!!.toFiatMoney(), FiatMoneyUtil.formatOptions().trimZerosAfterDecimal()) MaterialAlertDialogBuilder(requireContext()) - .setTitle(getString(R.string.IdealTransferDetailsFragment__confirm_your_donation_with_ideal)) + .setTitle(getString(R.string.IdealTransferDetailsFragment__confirm_your_donation_with_ideal_wero)) .setMessage(getString(R.string.IdealTransferDetailsFragment__to_setup_your_recurring_donation, formattedMoney)) .setPositiveButton(R.string.IdealTransferDetailsFragment__continue) { _, _ -> continueTransfer() @@ -218,7 +218,7 @@ private fun IdealTransferDetailsContent( onDonateClick: () -> Unit ) { Scaffolds.Settings( - title = stringResource(id = R.string.GatewaySelectorBottomSheet__ideal), + title = stringResource(id = R.string.GatewaySelectorBottomSheet__ideal_wero), onNavigationClick = onNavigationClick, navigationIcon = SignalIcons.ArrowStart.imageVector ) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/manage/ManageDonationsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/manage/ManageDonationsFragment.kt index d60d52f3a5..10977a56f5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/manage/ManageDonationsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/manage/ManageDonationsFragment.kt @@ -130,7 +130,7 @@ class ManageDonationsFragment : MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.ManageDonationsFragment__couldnt_confirm_donation) - .setMessage(getString(R.string.ManageDonationsFragment__your_monthly_s_donation_couldnt_be_confirmed, amount)) + .setMessage(getString(R.string.ManageDonationsFragment__your_monthly_s_donation_couldnt_be_confirmed_ideal_wero, amount)) .setPositiveButton(android.R.string.ok, null) .show() } else if (state.pendingOneTimeDonation?.pendingVerification == true && @@ -143,7 +143,7 @@ class ManageDonationsFragment : MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.ManageDonationsFragment__couldnt_confirm_donation) - .setMessage(getString(R.string.ManageDonationsFragment__your_one_time_s_donation_couldnt_be_confirmed, amount)) + .setMessage(getString(R.string.ManageDonationsFragment__your_one_time_s_donation_couldnt_be_confirmed_ideal_wero, amount)) .setPositiveButton(android.R.string.ok, null) .show() } @@ -440,7 +440,7 @@ class ManageDonationsFragment : else -> { val message = if (isIdeal) { - R.string.DonationsErrors__your_ideal_couldnt_be_processed + R.string.DonationsErrors__your_ideal_wero_couldnt_be_processed } else { R.string.DonationsErrors__try_another_payment_method } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/models/IdealWeroButton.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/models/IdealWeroButton.kt new file mode 100644 index 0000000000..a8468f2a7b --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/models/IdealWeroButton.kt @@ -0,0 +1,87 @@ +package org.thoughtcrime.securesms.components.settings.app.subscription.models + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.material3.ButtonColors +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.unit.dp +import org.signal.core.ui.compose.Buttons +import org.signal.core.ui.compose.DayNightPreviews +import org.signal.core.ui.compose.Previews +import org.signal.core.ui.compose.horizontalGutters +import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.components.settings.PreferenceModel +import org.thoughtcrime.securesms.components.settings.models.DSLComposePreference +import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter + +/** + * DSL Ideal | Wero button for the payments gateway. + */ +object IdealWeroButton { + + @Stable + class Model(val onClick: () -> Unit) : PreferenceModel() { + override fun areItemsTheSame(newItem: Model): Boolean = true + } + + class ViewHolder(itemView: ComposeView) : DSLComposePreference.ViewHolder(itemView) { + @Composable + override fun Content(model: Model) { + IdealWeroButton(model) + } + } + + fun register(adapter: MappingAdapter) { + DSLComposePreference.register(adapter) { ViewHolder(it) } + } +} + +@Composable +private fun IdealWeroButton(model: IdealWeroButton.Model) { + var enabled by remember { mutableStateOf(true) } + + Buttons.LargeTonal( + contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp), + onClick = { + enabled = false + model.onClick() + }, + enabled = enabled, + modifier = Modifier + .height(44.dp) + .horizontalGutters() + .fillMaxWidth(), + colors = ButtonColors( + containerColor = colorResource(org.signal.core.ui.R.color.signal_light_colorPrimaryContainer), + contentColor = colorResource(org.signal.core.ui.R.color.signal_light_colorOnPrimaryContainer), + disabledContainerColor = colorResource(org.signal.core.ui.R.color.signal_light_colorPrimaryContainer), + disabledContentColor = colorResource(org.signal.core.ui.R.color.signal_light_colorOnPrimaryContainer) + ) + ) { + Image( + imageVector = ImageVector.vectorResource(R.drawable.logo_ideal_wero), + contentDescription = stringResource(R.string.GatewaySelectorBottomSheet__ideal_wero) + ) + } +} + +@DayNightPreviews +@Composable +private fun IdealWeroButtonPreview() { + Previews.Preview { + IdealWeroButton(model = remember { IdealWeroButton.Model(onClick = {}) }) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/models/DSLComposePreference.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/models/DSLComposePreference.kt new file mode 100644 index 0000000000..fddc154eef --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/models/DSLComposePreference.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2026 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.components.settings.models + +import android.view.ViewGroup +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import org.signal.core.ui.compose.theme.SignalTheme +import org.thoughtcrime.securesms.util.adapter.mapping.Factory +import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter +import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel +import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder + +/** + * Allows hosting compose code in a DSL adapter. + */ +object DSLComposePreference { + /** + * Initializes the ComposeView to play nice with RecyclerView and manages the Model in a State. + */ + abstract class ViewHolder>(composeView: ComposeView) : MappingViewHolder(composeView) { + + private var model: T? by mutableStateOf(null) + + init { + composeView.setViewCompositionStrategy( + ViewCompositionStrategy.DisposeOnDetachedFromWindowOrReleasedFromPool + ) + + composeView.setContent { + val model = this.model ?: return@setContent + + SignalTheme { + Content(model) + } + } + } + + override fun bind(model: T) { + this.model = model + } + + @Composable + abstract fun Content(model: T) + } + + /** + * Does not need to be used directly, but does need to be non-private so that the inline register method can see it. + */ + class ComposeFactory>( + private val create: (ComposeView) -> MappingViewHolder + ) : Factory { + override fun createViewHolder(parent: ViewGroup): MappingViewHolder { + return create(ComposeView(parent.context)) + } + } + + inline fun > register(adapter: MappingAdapter, noinline create: (ComposeView) -> MappingViewHolder) { + adapter.registerFactory(T::class.java, ComposeFactory(create)) + } +} diff --git a/app/src/main/res/drawable/logo_ideal_wero.xml b/app/src/main/res/drawable/logo_ideal_wero.xml new file mode 100644 index 0000000000..bf8aa02663 --- /dev/null +++ b/app/src/main/res/drawable/logo_ideal_wero.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dsl_button_primary.xml b/app/src/main/res/layout/dsl_button_primary.xml index 3d4b176f15..3bfcfa530b 100644 --- a/app/src/main/res/layout/dsl_button_primary.xml +++ b/app/src/main/res/layout/dsl_button_primary.xml @@ -8,8 +8,6 @@ android:layout_height="wrap_content" android:layout_marginStart="@dimen/dsl_settings_gutter" android:layout_marginEnd="@dimen/dsl_settings_gutter" - android:insetTop="2dp" - android:insetBottom="2dp" app:iconGravity="textStart" app:iconSize="24dp" app:iconTint="@null" diff --git a/app/src/main/res/layout/dsl_button_tonal.xml b/app/src/main/res/layout/dsl_button_tonal.xml index 345e6468a7..24826e6e61 100644 --- a/app/src/main/res/layout/dsl_button_tonal.xml +++ b/app/src/main/res/layout/dsl_button_tonal.xml @@ -8,8 +8,8 @@ android:layout_height="wrap_content" android:layout_marginStart="@dimen/dsl_settings_gutter" android:layout_marginEnd="@dimen/dsl_settings_gutter" - android:insetTop="2dp" - android:insetBottom="2dp" + android:insetTop="0dp" + android:insetBottom="0dp" app:iconGravity="textStart" app:iconSize="24dp" app:iconTint="@null" diff --git a/app/src/main/res/layout/paypal_button.xml b/app/src/main/res/layout/paypal_button.xml index 2fffa16dbf..4a763cce20 100644 --- a/app/src/main/res/layout/paypal_button.xml +++ b/app/src/main/res/layout/paypal_button.xml @@ -7,12 +7,12 @@ Couldn\'t confirm donation - Your %1$s/month donation couldn\'t be confirmed. Check your banking app to approve your iDEAL payment. + Your %1$s/month donation couldn\'t be confirmed. Check your banking app to approve your iDEAL | Wero payment. - Your one-time %1$s donation couldn\'t be confirmed. Check your banking app to approve your iDEAL payment. + Your one-time %1$s donation couldn\'t be confirmed. Check your banking app to approve your iDEAL | Wero payment. Enter Custom Amount @@ -6650,7 +6650,7 @@ Your donation could not be sent because of a network error. Check your connection and try again. - Your iDEAL donation couldn\'t be processed. Try another payment method or contact your bank for more information. + Your iDEAL | Wero donation couldn\'t be processed. Try another payment method or contact your bank for more information. Donation on behalf of %1$s @@ -7575,7 +7575,7 @@ Your donation is still being processed. This can take a few minutes depending on your connection. Please wait until this payment completes before making another donation. - Your iDEAL donation is still processing. Check your banking app to approve your payment before making another donation. + Your iDEAL | Wero donation is still processing. Check your banking app to approve your payment before making another donation. Donation amount too high @@ -7605,7 +7605,7 @@ Donate for a friend - iDEAL + iDEAL | Wero Leave Signal to confirm donation? @@ -7673,7 +7673,7 @@ Choose your bank - Confirm your donation with iDEAL + Confirm your donation with iDEAL | Wero To setup your recurring donation tap continue to confirm a €0,01 charge with your bank. This will be automatically refunded and allows your %1$s/month donation to debited from your account. diff --git a/lib/donations/src/main/res/layout/donate_with_googlepay_button.xml b/lib/donations/src/main/res/layout/donate_with_googlepay_button.xml index c72a989634..a92730749f 100755 --- a/lib/donations/src/main/res/layout/donate_with_googlepay_button.xml +++ b/lib/donations/src/main/res/layout/donate_with_googlepay_button.xml @@ -3,8 +3,6 @@ android:layout_width="match_parent" android:layout_height="44sp" xmlns:app="http://schemas.android.com/apk/res-auto" - android:layout_marginTop="2sp" - android:layout_marginBottom="2sp" android:background="@drawable/donate_with_google_pay_rounded_background" android:clickable="true" android:contentDescription="@string/donate_with_googlepay_button_content_description"