diff --git a/app/src/main/java/com/ivy/wallet/android/billing/IvyBilling.kt b/app/src/main/java/com/ivy/wallet/android/billing/IvyBilling.kt index 054c483cc8..7d69127f8a 100644 --- a/app/src/main/java/com/ivy/wallet/android/billing/IvyBilling.kt +++ b/app/src/main/java/com/ivy/wallet/android/billing/IvyBilling.kt @@ -16,6 +16,14 @@ class IvyBilling( private const val LIFETIME_V1 = "ivy_wallet_lifetime_v1" + const val DONATE_2 = "donate_2" + const val DONATE_5 = "donate_5" + const val DONATE_10 = "donate_10" + const val DONATE_15 = "donate_15" + const val DONATE_25 = "donate_25" + const val DONATE_50 = "donate_50" + const val DONATE_100 = "donate_100" + val SUBSCRIPTIONS = listOf( MONTHLY_V1, SIX_MONTH_V1, @@ -23,7 +31,13 @@ class IvyBilling( ) val ONE_TIME_PLANS = listOf( - LIFETIME_V1 + DONATE_2, + DONATE_5, + DONATE_10, + DONATE_15, + DONATE_25, + DONATE_50, + DONATE_100 ) } @@ -129,7 +143,7 @@ class IvyBilling( .filterNotNull() } - private suspend fun fetchOneTimePlans(): List { + suspend fun fetchOneTimePlans(): List { val params = SkuDetailsParams.newBuilder() .setSkusList(ONE_TIME_PLANS) .setType(BillingClient.SkuType.INAPP) diff --git a/app/src/main/java/com/ivy/wallet/domain/deprecated/logic/CustomerJourneyLogic.kt b/app/src/main/java/com/ivy/wallet/domain/deprecated/logic/CustomerJourneyLogic.kt index c989a63fba..d5edfadfbc 100644 --- a/app/src/main/java/com/ivy/wallet/domain/deprecated/logic/CustomerJourneyLogic.kt +++ b/app/src/main/java/com/ivy/wallet/domain/deprecated/logic/CustomerJourneyLogic.kt @@ -2,6 +2,7 @@ package com.ivy.wallet.domain.deprecated.logic import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview +import com.ivy.design.l0_system.SunsetNight import com.ivy.wallet.Constants import com.ivy.wallet.R import com.ivy.wallet.domain.data.TransactionType @@ -11,6 +12,7 @@ import com.ivy.wallet.io.persistence.dao.PlannedPaymentRuleDao import com.ivy.wallet.io.persistence.dao.TransactionDao import com.ivy.wallet.stringRes import com.ivy.wallet.ui.* +import com.ivy.wallet.ui.donate.DonateScreen import com.ivy.wallet.ui.home.CustomerJourneyCard import com.ivy.wallet.ui.main.MainTab import com.ivy.wallet.ui.theme.* @@ -55,11 +57,12 @@ class CustomerJourneyLogic( didYouKnow_expensesPieChart(), rateUsCard(), shareIvyWalletCard(), -// buyLifetimeOfferCard(), + joinIvyTelegramCard(), makeReportCard(), rateUsCard_2(), shareIvyWalletCard_2(), - ivyWalletIsOpenSource() + ivyWalletIsOpenSource(), + donateIvyWallet() ) fun adjustBalanceCard() = CustomerJourneyCardData( @@ -71,7 +74,7 @@ class CustomerJourneyLogic( description = stringRes(R.string.adjust_initial_balance_description), cta = stringRes(R.string.to_accounts), ctaIcon = R.drawable.ic_custom_account_s, - backgroundColor = Ivy, + background = Gradient.solid(Ivy), hasDismiss = false, onAction = { _, ivyContext, _ -> ivyContext.selectMainTab(MainTab.ACCOUNTS) @@ -87,7 +90,7 @@ class CustomerJourneyLogic( description = stringRes(R.string.create_first_planned_payment_description), cta = stringRes(R.string.add_planned_payment), ctaIcon = R.drawable.ic_planned_payments, - backgroundColor = Orange, + background = Gradient.solid(Orange), hasDismiss = true, onAction = { navigation, _, _ -> navigation.navigateTo( @@ -108,7 +111,7 @@ class CustomerJourneyLogic( description = stringRes(R.string.widget_description), cta = stringRes(R.string.add_widget), ctaIcon = R.drawable.ic_custom_atom_s, - backgroundColor = GreenLight, + background = Gradient.solid(GreenLight), hasDismiss = true, onAction = { _, _, ivyActivity -> ivyActivity.pinWidget(AddTransactionWidgetCompact::class.java) @@ -121,10 +124,10 @@ class CustomerJourneyLogic( trnCount >= 5 }, title = stringRes(R.string.set_a_budget), - description = stringRes(R.string.set_a_budget_description), + description = stringRes(R.string.set_a_budget_description), cta = stringRes(R.string.add_budget), ctaIcon = R.drawable.ic_budget_xs, - backgroundColor = Green2, + background = Gradient.solid(Green2), hasDismiss = true, onAction = { navigation, _, _ -> navigation.navigateTo(BudgetScreen) @@ -140,7 +143,7 @@ class CustomerJourneyLogic( description = stringRes(R.string.expenses_piechart_description), cta = stringRes(R.string.expenses_piechart), ctaIcon = R.drawable.ic_custom_bills_s, - backgroundColor = Red, + background = Gradient.solid(Red), hasDismiss = true, onAction = { navigation, _, _ -> navigation.navigateTo(PieChartStatistic(type = TransactionType.EXPENSE)) @@ -156,7 +159,7 @@ class CustomerJourneyLogic( description = stringRes(R.string.review_ivy_wallet_description), cta = stringRes(R.string.rate_us_on_google_play), ctaIcon = R.drawable.ic_custom_star_s, - backgroundColor = Green, + background = Gradient.solid(Green), hasDismiss = true, onAction = { _, _, ivyActivity -> ivyActivity.reviewIvyWallet(dismissReviewCard = true) @@ -172,27 +175,26 @@ class CustomerJourneyLogic( description = stringRes(R.string.help_us_grow), cta = stringRes(R.string.share_with_friends), ctaIcon = R.drawable.ic_custom_family_s, - backgroundColor = Red3, + background = Gradient.solid(Red3), hasDismiss = true, onAction = { _, _, ivyActivity -> ivyActivity.shareIvyWallet() } ) - fun buyLifetimeOfferCard() = CustomerJourneyCardData( - id = "buy_lifetime_offer", - condition = { trnCount, _, ivyContext -> - trnCount >= 16 && !ivyContext.isPremium + fun joinIvyTelegramCard() = CustomerJourneyCardData( + id = "join_ivy_telegram", + condition = { trnCount, _, _ -> + trnCount >= 16 }, - title = "Lifetime Premium", - description = "We understand that owning something is better than just paying a subscription for it." + - " That's why we've included this special limited lifetime offer only for our best users like you.", - cta = "Get Lifetime Premium", - ctaIcon = R.drawable.ic_custom_crown_s, - backgroundColor = Ivy, + description = "It looks like that you're enjoying Ivy Wallet! Feel free join our invite-only Ivy Telegram Community and make our app better :)", + title = "Ivy Community", + cta = "Join now", + ctaIcon = R.drawable.ic_telegram_24dp, + background = Gradient.solid(Blue), hasDismiss = true, - onAction = { navigation, _, _ -> - navigation.navigateTo(Paywall(paywallReason = null)) + onAction = { _, _, rootActivity -> + rootActivity.openUrlInBrowser(Constants.URL_IVY_TELEGRAM_INVITE) } ) @@ -205,7 +207,7 @@ class CustomerJourneyLogic( description = stringRes(R.string.make_a_report_description), cta = stringRes(R.string.make_a_report), ctaIcon = R.drawable.ic_statistics_xs, - backgroundColor = Green2, + background = Gradient.solid(Green2), hasDismiss = true, onAction = { navigation, _, _ -> navigation.navigateTo(Report) @@ -221,7 +223,7 @@ class CustomerJourneyLogic( description = stringRes(R.string.make_ivy_wallet_better_description), cta = stringRes(R.string.rate_us_on_google_play), ctaIcon = R.drawable.ic_custom_star_s, - backgroundColor = GreenLight, + background = Gradient.solid(GreenLight), hasDismiss = true, onAction = { _, _, ivyActivity -> ivyActivity.reviewIvyWallet(dismissReviewCard = true) @@ -237,7 +239,7 @@ class CustomerJourneyLogic( description = stringRes(R.string.we_need_your_help_description), cta = stringRes(R.string.share_ivy_wallet), ctaIcon = R.drawable.ic_custom_family_s, - backgroundColor = Purple2, + background = Gradient.solid(Purple2), hasDismiss = true, onAction = { _, _, ivyActivity -> ivyActivity.shareIvyWallet() @@ -253,12 +255,29 @@ class CustomerJourneyLogic( description = stringRes(R.string.ivy_wallet_is_opensource_description), cta = stringRes(R.string.contribute), ctaIcon = R.drawable.github_logo, - backgroundColor = Blue3, + background = Gradient.solid(Blue3), hasDismiss = true, onAction = { _, _, ivyActivity -> ivyActivity.openUrlInBrowser(Constants.URL_IVY_WALLET_REPO) } ) + + fun donateIvyWallet() = CustomerJourneyCardData( + id = "donate_ivy_wallet", + condition = { trnCount, _, _ -> + trnCount >= 30 + }, + title = "Support Ivy Wallet", + description = "It seems like you enjoy free and open-source software. We too! " + + "That's why we opened a donations channel to sustain and improve our small project.", + cta = "Donate", + ctaIcon = R.drawable.ic_donate_crown, + background = Gradient.from(SunsetNight), + hasDismiss = true, + onAction = { nav, _, _ -> + nav.navigateTo(DonateScreen) + } + ) } } @@ -348,10 +367,10 @@ private fun PreviewShareIvyWallet() { @Preview @Composable -private fun PreviewBuyLifetimeOffer() { +private fun PreviewJoinTelegram() { IvyWalletComponentPreview { CustomerJourneyCard( - cardData = CustomerJourneyLogic.buyLifetimeOfferCard(), + cardData = CustomerJourneyLogic.joinIvyTelegramCard(), onCTA = { }, onDismiss = {} ) @@ -406,6 +425,18 @@ private fun PreviewIvyWallet_isOpenSource() { } } +@Preview +@Composable +private fun PreviewDonateCard() { + IvyWalletComponentPreview { + CustomerJourneyCard( + cardData = CustomerJourneyLogic.donateIvyWallet(), + onCTA = { }, + onDismiss = {} + ) + } +} + diff --git a/app/src/main/java/com/ivy/wallet/domain/deprecated/logic/model/CustomerJourneyCardData.kt b/app/src/main/java/com/ivy/wallet/domain/deprecated/logic/model/CustomerJourneyCardData.kt index ad4118e7f3..30aa7f138a 100644 --- a/app/src/main/java/com/ivy/wallet/domain/deprecated/logic/model/CustomerJourneyCardData.kt +++ b/app/src/main/java/com/ivy/wallet/domain/deprecated/logic/model/CustomerJourneyCardData.kt @@ -1,10 +1,10 @@ package com.ivy.wallet.domain.deprecated.logic.model import androidx.annotation.DrawableRes -import androidx.compose.ui.graphics.Color import com.ivy.frp.view.navigation.Navigation import com.ivy.wallet.ui.IvyWalletCtx import com.ivy.wallet.ui.RootActivity +import com.ivy.wallet.ui.theme.Gradient data class CustomerJourneyCardData( val id: String, @@ -17,6 +17,6 @@ data class CustomerJourneyCardData( val hasDismiss: Boolean = true, - val backgroundColor: Color, + val background: Gradient, val onAction: (Navigation, IvyWalletCtx, RootActivity) -> Unit ) \ No newline at end of file diff --git a/app/src/main/java/com/ivy/wallet/ui/RootActivity.kt b/app/src/main/java/com/ivy/wallet/ui/RootActivity.kt index 2f50d7dd7e..a8054b073d 100644 --- a/app/src/main/java/com/ivy/wallet/ui/RootActivity.kt +++ b/app/src/main/java/com/ivy/wallet/ui/RootActivity.kt @@ -55,6 +55,7 @@ import com.ivy.wallet.ui.budget.BudgetScreen import com.ivy.wallet.ui.category.CategoriesScreen import com.ivy.wallet.ui.charts.ChartsScreen import com.ivy.wallet.ui.csvimport.ImportCSVScreen +import com.ivy.wallet.ui.donate.DonateScreen import com.ivy.wallet.ui.edit.EditTransactionScreen import com.ivy.wallet.ui.experiment.images.ImagesScreen import com.ivy.wallet.ui.loan.LoansScreen @@ -212,6 +213,7 @@ class RootActivity : AppCompatActivity() { is IvyWebView -> WebViewScreen(screen = screen) is ImagesScreen -> ImagesScreen(screen = screen) is ExperimentalScreen -> ExperimentalScreen(screen = screen) + is DonateScreen -> DonateScreen(screen = screen) null -> { } } diff --git a/app/src/main/java/com/ivy/wallet/ui/donate/DonateEvent.kt b/app/src/main/java/com/ivy/wallet/ui/donate/DonateEvent.kt new file mode 100644 index 0000000000..a15cb407c5 --- /dev/null +++ b/app/src/main/java/com/ivy/wallet/ui/donate/DonateEvent.kt @@ -0,0 +1,13 @@ +package com.ivy.wallet.ui.donate + +import com.ivy.wallet.ui.RootActivity +import com.ivy.wallet.ui.donate.data.DonateOption + +sealed class DonateEvent { + data class Load(val activity: RootActivity) : DonateEvent() + + data class Donate( + val activity: RootActivity, + val option: DonateOption + ) : DonateEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/ivy/wallet/ui/donate/DonateScreen.kt b/app/src/main/java/com/ivy/wallet/ui/donate/DonateScreen.kt new file mode 100644 index 0000000000..d11e4d1936 --- /dev/null +++ b/app/src/main/java/com/ivy/wallet/ui/donate/DonateScreen.kt @@ -0,0 +1,291 @@ +package com.ivy.wallet.ui.donate + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.google.accompanist.insets.navigationBarsPadding +import com.ivy.design.l0_system.Black +import com.ivy.design.l0_system.UI +import com.ivy.design.l0_system.White +import com.ivy.design.l0_system.style +import com.ivy.design.l1_buildingBlocks.ColumnRoot +import com.ivy.design.l1_buildingBlocks.IvyText +import com.ivy.design.l1_buildingBlocks.SpacerHor +import com.ivy.design.l1_buildingBlocks.SpacerVer +import com.ivy.design.l1_buildingBlocks.data.Background +import com.ivy.design.l2_components.IconButton +import com.ivy.design.utils.padding +import com.ivy.frp.view.FRP +import com.ivy.frp.view.navigation.Screen +import com.ivy.frp.view.navigation.navigation +import com.ivy.wallet.R +import com.ivy.wallet.ui.IvyWalletPreview +import com.ivy.wallet.ui.RootActivity +import com.ivy.wallet.ui.donate.data.DonateOption +import com.ivy.wallet.ui.rootActivity +import com.ivy.wallet.ui.theme.Gradient +import com.ivy.wallet.ui.theme.components.IvyButton + +object DonateScreen : Screen + +@Composable +fun BoxWithConstraintsScope.DonateScreen(screen: DonateScreen) { + FRP( + initialEvent = DonateEvent.Load(rootActivity()) + ) { state, onEvent -> + UI(state, onEvent) + } +} + +@Composable +private fun BoxWithConstraintsScope.UI( + state: DonateState, + onEvent: (DonateEvent) -> Unit +) { + var donateOption by remember { + mutableStateOf(DonateOption.DONATE_5) + } + + Column { + Image( + modifier = Modifier.fillMaxWidth(), + painter = painterResource(id = R.drawable.donate_illustration), + contentDescription = "rocket illustration", + contentScale = ContentScale.FillWidth + ) + + ScreenContent() + } + + ColumnRoot { + SpacerVer(height = 16.dp) + + val nav = navigation() + IconButton( + modifier = Modifier.padding(start = 16.dp), + icon = R.drawable.ic_back_android, + background = Background.Outlined( + width = 2.dp, + color = White, + shape = CircleShape, + padding = padding(all = 12.dp) + ) + ) { + nav.back() + } + + SpacerVer(height = 16.dp) + + IvyText( + modifier = Modifier.padding(start = 24.dp), + text = "Donate", + typo = UI.typo.h2.style( + fontWeight = FontWeight.Bold, + color = White + ) + ) + + SpacerVer(height = 4.dp) + + DonateOptionPicker(option = donateOption) { + donateOption = it + } + } + + val context = LocalView.current.context + DonateButton { + onEvent(DonateEvent.Donate(context as RootActivity, donateOption)) + } +} + +@Composable +private fun DonateOptionPicker( + option: DonateOption, + onSelect: (DonateOption) -> Unit +) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + SpacerHor(width = 16.dp) + + if (option != DonateOption.DONATE_2) + OptionPickerButton( + icon = R.drawable.ic_donate_minus, + contentDescription = "btn_minus" + ) { + val newOption = when (option) { + DonateOption.DONATE_2 -> DonateOption.DONATE_2 + DonateOption.DONATE_5 -> DonateOption.DONATE_2 + DonateOption.DONATE_10 -> DonateOption.DONATE_5 + DonateOption.DONATE_15 -> DonateOption.DONATE_10 + DonateOption.DONATE_25 -> DonateOption.DONATE_15 + DonateOption.DONATE_50 -> DonateOption.DONATE_25 + DonateOption.DONATE_100 -> DonateOption.DONATE_50 + } + onSelect(newOption) + } + + SpacerHor(width = 12.dp) + + IvyText( + text = "$${ + when (option) { + DonateOption.DONATE_2 -> 2 + DonateOption.DONATE_5 -> 5 + DonateOption.DONATE_10 -> 10 + DonateOption.DONATE_15 -> 15 + DonateOption.DONATE_25 -> 25 + DonateOption.DONATE_50 -> 50 + DonateOption.DONATE_100 -> 100 + } + }", + typo = UI.typo.nH1.style( + fontWeight = FontWeight.Bold, + color = White + ) + ) + + SpacerHor(width = 12.dp) + + if (option != DonateOption.DONATE_100) + OptionPickerButton( + icon = R.drawable.ic_donate_plus, + contentDescription = "btn_plus" + ) { + val newOption = when (option) { + DonateOption.DONATE_2 -> DonateOption.DONATE_5 + DonateOption.DONATE_5 -> DonateOption.DONATE_10 + DonateOption.DONATE_10 -> DonateOption.DONATE_15 + DonateOption.DONATE_15 -> DonateOption.DONATE_25 + DonateOption.DONATE_25 -> DonateOption.DONATE_50 + DonateOption.DONATE_50 -> DonateOption.DONATE_100 + DonateOption.DONATE_100 -> DonateOption.DONATE_100 + } + onSelect(newOption) + } + } +} + +@Composable +private fun OptionPickerButton( + @DrawableRes icon: Int, + contentDescription: String, + onClick: () -> Unit +) { + Image( + modifier = Modifier + .clip(UI.shapes.r4) + .background(Black) + .clickable { onClick() } + .padding(horizontal = 8.dp, vertical = 4.dp), + painter = painterResource(icon), + contentDescription = contentDescription, + ) +} + +@Composable +private fun ScreenContent() { + LazyColumn { + item { + SpacerVer(height = 32.dp) + + IvyText( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 32.dp), + text = "It seems like you enjoy free and open-source software. We too!", + typo = UI.typo.b1.style( + color = UI.colors.pureInverse, + fontWeight = FontWeight.Bold + ) + ) + } + + item { + SpacerVer(height = 12.dp) + + IvyText( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 32.dp), + text = "BIG THANKS to all Ivy contributors who made Ivy Wallet possible! That's why we opened a donations channel to sustain and improve our small project.", + typo = UI.typo.b2.style( + color = UI.colors.gray, + fontWeight = FontWeight.Medium + ) + ) + } + + item { + SpacerVer(height = 24.dp) + + Text( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp) + .background(UI.colors.medium, UI.shapes.r4) + .padding(horizontal = 24.dp, vertical = 16.dp), + text = "If you want to support us feel free to donate whatever amount you're comfortable with - it all helps! (local taxes may apply)".uppercase(), + style = UI.typo.c.style( + fontWeight = FontWeight.Bold, + color = UI.colors.red1Inverse + ) + ) + } + + item { + SpacerVer(height = 120.dp) //scroll hack + } + } +} + +@Composable +private fun BoxWithConstraintsScope.DonateButton( + onClick: () -> Unit +) { + IvyButton( + modifier = Modifier + .align(Alignment.BottomCenter) + .fillMaxWidth() + .navigationBarsPadding( + bottom = true, start = false, end = false + ) + .padding(horizontal = 20.dp) + .padding(bottom = 16.dp), + iconStart = R.drawable.ic_donate_crown, + wrapContentMode = false, + iconTint = UI.colors.pure, + iconEdgePadding = 16.dp, + text = "Donate", + backgroundGradient = Gradient.solid(UI.colors.pureInverse), + textStyle = UI.typo.b1.style( + fontWeight = FontWeight.Bold, + color = UI.colors.pure + ) + ) { + onClick() + } +} + +@Preview +@Composable +private fun Preview() { + IvyWalletPreview { + UI(state = DonateState.Success, onEvent = {}) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ivy/wallet/ui/donate/DonateState.kt b/app/src/main/java/com/ivy/wallet/ui/donate/DonateState.kt new file mode 100644 index 0000000000..2f135cc90e --- /dev/null +++ b/app/src/main/java/com/ivy/wallet/ui/donate/DonateState.kt @@ -0,0 +1,11 @@ +package com.ivy.wallet.ui.donate + +sealed class DonateState { + object Loading : DonateState() + + object Success : DonateState() + + data class Error( + val errMsg: String + ) : DonateState() +} \ No newline at end of file diff --git a/app/src/main/java/com/ivy/wallet/ui/donate/DonateViewModel.kt b/app/src/main/java/com/ivy/wallet/ui/donate/DonateViewModel.kt new file mode 100644 index 0000000000..941986b9d6 --- /dev/null +++ b/app/src/main/java/com/ivy/wallet/ui/donate/DonateViewModel.kt @@ -0,0 +1,71 @@ +package com.ivy.wallet.ui.donate + +import androidx.lifecycle.viewModelScope +import com.ivy.frp.then +import com.ivy.frp.viewmodel.FRPViewModel +import com.ivy.wallet.android.billing.IvyBilling +import com.ivy.wallet.android.billing.Plan +import com.ivy.wallet.ui.donate.data.DonateOption +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch +import timber.log.Timber +import javax.inject.Inject + +@HiltViewModel +class DonateViewModel @Inject constructor( + private val ivyBilling: IvyBilling +) : FRPViewModel() { + override val _state: MutableStateFlow = MutableStateFlow(DonateState.Loading) + + val plans = mutableListOf() + + override suspend fun handleEvent(event: DonateEvent): suspend () -> DonateState = when (event) { + is DonateEvent.Load -> load(event) + is DonateEvent.Donate -> donate(event) + } + + private fun load(event: DonateEvent.Load) = suspend { + ivyBilling.init( + activity = event.activity, + onReady = { + viewModelScope.launch { + plans.clear() + plans.addAll(ivyBilling.fetchOneTimePlans()) + updateStateNonBlocking { DonateState.Success } + } + }, + onError = { code, msg -> + updateStateNonBlocking { + DonateState.Error(errMsg = "Google Play Billing error: $code - $msg") + } + }, + onPurchases = {} + ) + stateVal() + } + + private fun donate(event: DonateEvent.Donate) = suspend { + when (event.option) { + DonateOption.DONATE_2 -> IvyBilling.DONATE_2 + DonateOption.DONATE_5 -> IvyBilling.DONATE_5 + DonateOption.DONATE_10 -> IvyBilling.DONATE_10 + DonateOption.DONATE_15 -> IvyBilling.DONATE_15 + DonateOption.DONATE_25 -> IvyBilling.DONATE_25 + DonateOption.DONATE_50 -> IvyBilling.DONATE_50 + DonateOption.DONATE_100 -> IvyBilling.DONATE_100 + } + } then { targetSku -> + Timber.i("Donating to sku \"$targetSku\"") + plans.find { it.sku == targetSku } + } then { plan -> + if (plan != null) { + ivyBilling.buy( + activity = event.activity, + skuToBuy = plan.skuDetails, + oldSubscriptionPurchaseToken = null + ) + } + stateVal() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ivy/wallet/ui/donate/data/DonateOption.kt b/app/src/main/java/com/ivy/wallet/ui/donate/data/DonateOption.kt new file mode 100644 index 0000000000..06bb421daa --- /dev/null +++ b/app/src/main/java/com/ivy/wallet/ui/donate/data/DonateOption.kt @@ -0,0 +1,5 @@ +package com.ivy.wallet.ui.donate.data + +enum class DonateOption { + DONATE_2, DONATE_5, DONATE_10, DONATE_15, DONATE_25, DONATE_50, DONATE_100 +} \ No newline at end of file diff --git a/app/src/main/java/com/ivy/wallet/ui/home/CustomerJourney.kt b/app/src/main/java/com/ivy/wallet/ui/home/CustomerJourney.kt index ce6714c00d..3a416bce1b 100644 --- a/app/src/main/java/com/ivy/wallet/ui/home/CustomerJourney.kt +++ b/app/src/main/java/com/ivy/wallet/ui/home/CustomerJourney.kt @@ -67,8 +67,8 @@ fun CustomerJourneyCard( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp) - .drawColoredShadow(cardData.backgroundColor) - .background(cardData.backgroundColor, UI.shapes.r3) + .drawColoredShadow(cardData.background.startColor) + .background(cardData.background.asHorizontalBrush(), UI.shapes.r3) .clip(UI.shapes.r3) .clickable { onCTA() @@ -86,7 +86,7 @@ fun CustomerJourneyCard( text = cardData.title, style = UI.typo.b1.style( fontWeight = FontWeight.ExtraBold, - color = findContrastTextColor(cardData.backgroundColor) + color = findContrastTextColor(cardData.background.startColor) ) ) @@ -98,7 +98,7 @@ fun CustomerJourneyCard( } .padding(8.dp), //enlarge click area icon = R.drawable.ic_dismiss, - tint = cardData.backgroundColor.dynamicContrast(), + tint = cardData.background.startColor.dynamicContrast(), contentDescription = "prompt_dismiss", ) @@ -115,7 +115,7 @@ fun CustomerJourneyCard( text = cardData.description, style = UI.typo.b2.style( fontWeight = FontWeight.Medium, - color = findContrastTextColor(cardData.backgroundColor) + color = findContrastTextColor(cardData.background.startColor) ) ) @@ -129,13 +129,13 @@ fun CustomerJourneyCard( text = cardData.cta, shadowAlpha = 0f, iconStart = cardData.ctaIcon, - iconTint = cardData.backgroundColor, + iconTint = cardData.background.startColor, textStyle = UI.typo.b2.style( - color = cardData.backgroundColor, + color = cardData.background.startColor, fontWeight = FontWeight.Bold ), padding = 8.dp, - backgroundGradient = Gradient.solid(findContrastTextColor(cardData.backgroundColor)) + backgroundGradient = Gradient.solid(findContrastTextColor(cardData.background.startColor)) ) { onCTA() } diff --git a/app/src/main/java/com/ivy/wallet/ui/home/HomeMoreMenu.kt b/app/src/main/java/com/ivy/wallet/ui/home/HomeMoreMenu.kt index 9c1012b6e7..dd87930332 100644 --- a/app/src/main/java/com/ivy/wallet/ui/home/HomeMoreMenu.kt +++ b/app/src/main/java/com/ivy/wallet/ui/home/HomeMoreMenu.kt @@ -27,6 +27,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import com.google.accompanist.insets.navigationBarsPadding import com.google.accompanist.insets.statusBarsPadding +import com.ivy.design.l0_system.SunsetNight import com.ivy.design.l0_system.Theme import com.ivy.design.l0_system.UI import com.ivy.design.l0_system.style @@ -34,10 +35,13 @@ import com.ivy.frp.view.navigation.navigation import com.ivy.wallet.Constants import com.ivy.wallet.R import com.ivy.wallet.ui.* +import com.ivy.wallet.ui.donate.DonateScreen import com.ivy.wallet.ui.theme.Blue +import com.ivy.wallet.ui.theme.Gradient import com.ivy.wallet.ui.theme.Gray import com.ivy.wallet.ui.theme.components.BufferBattery import com.ivy.wallet.ui.theme.components.CircleButtonFilled +import com.ivy.wallet.ui.theme.components.IvyButton import com.ivy.wallet.ui.theme.components.IvyIcon import com.ivy.wallet.ui.theme.modal.AddModalBackHandling import com.ivy.wallet.ui.theme.wallet.AmountCurrencyB1 @@ -156,12 +160,17 @@ fun BoxWithConstraintsScope.MoreMenu( } } + if (percentExpanded > 0.01f) { + DonateButton( + percentExpanded = percentExpanded + ) + } + CircleButtonFilled( modifier = Modifier .layout { measurable, constraints -> val placeable = measurable.measure(constraints) - layout(placeable.width, placeable.height) { placeable.place( x = xBase.roundToInt() - buttonSizePx.roundToInt(), @@ -179,7 +188,6 @@ fun BoxWithConstraintsScope.MoreMenu( ) { setExpanded(!expanded) } - } @Composable @@ -553,6 +561,28 @@ private fun Preview_Expanded() { } } +@Composable +private fun BoxWithConstraintsScope.DonateButton( + percentExpanded: Float +) { + val nav = navigation() + IvyButton( + modifier = Modifier + .align(Alignment.BottomCenter) + .navigationBarsPadding() + .padding(bottom = 40.dp) + .zIndex(510f) + .alpha(percentExpanded), + text = "Donate", + iconStart = R.drawable.ic_donate_crown, + iconEdgePadding = 16.dp, + iconTextPadding = 12.dp, + backgroundGradient = Gradient.from(SunsetNight) + ) { + nav.navigateTo(DonateScreen) + } +} + @Preview @Composable private fun Preview() { diff --git a/app/src/main/java/com/ivy/wallet/ui/settings/SettingsScreen.kt b/app/src/main/java/com/ivy/wallet/ui/settings/SettingsScreen.kt index 412bac75d1..2b8a2bc6fc 100644 --- a/app/src/main/java/com/ivy/wallet/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/ivy/wallet/ui/settings/SettingsScreen.kt @@ -30,6 +30,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel import coil.compose.AsyncImage import com.google.accompanist.insets.navigationBarsPadding import com.google.accompanist.insets.statusBarsPadding +import com.ivy.design.l0_system.SunsetNight import com.ivy.design.l0_system.UI import com.ivy.design.l0_system.style import com.ivy.design.l1_buildingBlocks.IconScale @@ -43,7 +44,7 @@ import com.ivy.wallet.domain.data.AuthProviderType import com.ivy.wallet.domain.data.IvyCurrency import com.ivy.wallet.domain.data.core.User import com.ivy.wallet.ui.* -import com.ivy.wallet.ui.settings.experimental.ExperimentalScreen +import com.ivy.wallet.ui.donate.DonateScreen import com.ivy.wallet.ui.theme.* import com.ivy.wallet.ui.theme.components.IvyButton import com.ivy.wallet.ui.theme.components.IvySwitch @@ -152,7 +153,7 @@ private fun BoxWithConstraintsScope.UI( onDeleteAllUserData: () -> Unit = {}, onDeleteCloudUserData: () -> Unit = {}, -) { + ) { var currencyModalVisible by remember { mutableStateOf(false) } var nameModalVisible by remember { mutableStateOf(false) } var chooseStartDateOfMonthVisible by remember { mutableStateOf(false) } @@ -310,19 +311,19 @@ private fun BoxWithConstraintsScope.UI( } } - item { - SettingsSectionDivider(text = stringResource(R.string.experimental)) - - Spacer(Modifier.height(16.dp)) - - val nav = navigation() - SettingsDefaultButton( - icon = R.drawable.ic_custom_atom_m, - text = stringResource(R.string.experimental_settings) - ) { - nav.navigateTo(ExperimentalScreen) - } - } +// item { +// SettingsSectionDivider(text = stringResource(R.string.experimental)) +// +// Spacer(Modifier.height(16.dp)) +// +// val nav = navigation() +// SettingsDefaultButton( +// icon = R.drawable.ic_custom_atom_m, +// text = stringResource(R.string.experimental_settings) +// ) { +// nav.navigateTo(ExperimentalScreen) +// } +// } item { SettingsSectionDivider(text = stringResource(R.string.other)) @@ -347,6 +348,18 @@ private fun BoxWithConstraintsScope.UI( ) { ivyActivity.shareIvyWallet() } + + Spacer(Modifier.height(12.dp)) + + val nav = navigation() + SettingsPrimaryButton( + icon = R.drawable.ic_donate_crown, + text = "Donate", + iconPadding = 8.dp, + backgroundGradient = Gradient.from(SunsetNight) + ) { + nav.navigateTo(DonateScreen) + } } item { @@ -399,7 +412,7 @@ private fun BoxWithConstraintsScope.UI( deleteAllDataModalVisible = true } - if(user != null){ + if (user != null) { Spacer(Modifier.height(16.dp)) SettingsPrimaryButton( diff --git a/app/src/main/java/com/ivy/wallet/ui/theme/IvyColors.kt b/app/src/main/java/com/ivy/wallet/ui/theme/IvyColors.kt index 7a5da8ff1e..e13f41772b 100644 --- a/app/src/main/java/com/ivy/wallet/ui/theme/IvyColors.kt +++ b/app/src/main/java/com/ivy/wallet/ui/theme/IvyColors.kt @@ -180,6 +180,9 @@ data class Gradient( val endColor: Color ) { companion object { + fun from(gradient: com.ivy.design.l0_system.Gradient) = + Gradient(gradient.startColor, gradient.endColor) + fun from(startColor: Int, endColor: Int?) = Gradient( startColor = startColor.toComposeColor(), endColor = (endColor ?: startColor).toComposeColor() diff --git a/app/src/main/java/com/ivy/wallet/ui/theme/components/IvyButton.kt b/app/src/main/java/com/ivy/wallet/ui/theme/components/IvyButton.kt index 4b1edaa55f..79e4ed5921 100644 --- a/app/src/main/java/com/ivy/wallet/ui/theme/components/IvyButton.kt +++ b/app/src/main/java/com/ivy/wallet/ui/theme/components/IvyButton.kt @@ -47,6 +47,7 @@ fun IvyButton( hasGlow: Boolean = true, padding: Dp = 12.dp, iconEdgePadding: Dp = 12.dp, + iconTextPadding: Dp = 4.dp, onClick: () -> Unit ) { Row( @@ -77,6 +78,7 @@ fun IvyButton( icon = iconStart, tint = iconTint, iconEdgePadding = iconEdgePadding, + iconTextPadding = iconTextPadding ) } iconEnd != null && !wrapContentMode -> { @@ -84,6 +86,7 @@ fun IvyButton( icon = iconEnd, tint = Color.Transparent, iconEdgePadding = iconEdgePadding, + iconTextPadding = iconTextPadding ) } else -> { @@ -113,6 +116,7 @@ fun IvyButton( icon = iconStart, tint = Color.Transparent, iconEdgePadding = iconEdgePadding, + iconTextPadding = iconTextPadding ) } iconEnd != null -> { @@ -120,6 +124,7 @@ fun IvyButton( icon = iconEnd, tint = iconTint, iconEdgePadding = iconEdgePadding, + iconTextPadding = iconTextPadding ) } else -> { @@ -132,6 +137,7 @@ fun IvyButton( @Composable private fun IconStart( iconEdgePadding: Dp, + iconTextPadding: Dp, icon: Int, tint: Color, ) { @@ -144,16 +150,17 @@ private fun IconStart( tint = tint, ) - Spacer(modifier = Modifier.width(4.dp)) + Spacer(modifier = Modifier.width(iconTextPadding)) } @Composable private fun IconEnd( iconEdgePadding: Dp, + iconTextPadding: Dp, icon: Int, tint: Color, ) { - Spacer(modifier = Modifier.width(4.dp)) + Spacer(modifier = Modifier.width(iconTextPadding)) Icon( modifier = Modifier, diff --git a/app/src/main/res/drawable-nodpi/donate_illustration.png b/app/src/main/res/drawable-nodpi/donate_illustration.png new file mode 100644 index 0000000000..b89131be8c Binary files /dev/null and b/app/src/main/res/drawable-nodpi/donate_illustration.png differ diff --git a/app/src/main/res/drawable/ic_back_android.xml b/app/src/main/res/drawable/ic_back_android.xml new file mode 100644 index 0000000000..a8129acd52 --- /dev/null +++ b/app/src/main/res/drawable/ic_back_android.xml @@ -0,0 +1,20 @@ + + + + diff --git a/app/src/main/res/drawable/ic_donate_crown.xml b/app/src/main/res/drawable/ic_donate_crown.xml new file mode 100644 index 0000000000..5ce35184d8 --- /dev/null +++ b/app/src/main/res/drawable/ic_donate_crown.xml @@ -0,0 +1,27 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_donate_minus.xml b/app/src/main/res/drawable/ic_donate_minus.xml new file mode 100644 index 0000000000..7cf76b0b6b --- /dev/null +++ b/app/src/main/res/drawable/ic_donate_minus.xml @@ -0,0 +1,29 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_donate_plus.xml b/app/src/main/res/drawable/ic_donate_plus.xml new file mode 100644 index 0000000000..46c62e582f --- /dev/null +++ b/app/src/main/res/drawable/ic_donate_plus.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + diff --git a/buildSrc/src/main/java/com/ivy/wallet/buildsrc/dependencies.kt b/buildSrc/src/main/java/com/ivy/wallet/buildsrc/dependencies.kt index 7fd69af07b..3a4fd6e709 100644 --- a/buildSrc/src/main/java/com/ivy/wallet/buildsrc/dependencies.kt +++ b/buildSrc/src/main/java/com/ivy/wallet/buildsrc/dependencies.kt @@ -22,8 +22,8 @@ import org.gradle.kotlin.dsl.project object Project { //Version - const val versionName = "4.2.1" - const val versionCode = 113 + const val versionName = "4.3.0" + const val versionCode = 114 //Compile SDK & Build Tools const val compileSdkVersion = 31 diff --git a/ivy-design/src/main/java/com/ivy/design/api/systems/IvyWalletDesign.kt b/ivy-design/src/main/java/com/ivy/design/api/systems/IvyWalletDesign.kt index df2279e011..54265736d6 100644 --- a/ivy-design/src/main/java/com/ivy/design/api/systems/IvyWalletDesign.kt +++ b/ivy-design/src/main/java/com/ivy/design/api/systems/IvyWalletDesign.kt @@ -130,6 +130,7 @@ abstract class IvyWalletDesign : IvyDesign { override val red = Red override val red1 = RedLight + override val red1Inverse = RedDark override val isLight = true } @@ -151,6 +152,7 @@ abstract class IvyWalletDesign : IvyDesign { override val red = Red override val red1 = RedDark + override val red1Inverse = RedLight override val isLight = false } diff --git a/ivy-design/src/main/java/com/ivy/design/l0_system/Colors.kt b/ivy-design/src/main/java/com/ivy/design/l0_system/Colors.kt index c20b741f1d..55277ce3ce 100644 --- a/ivy-design/src/main/java/com/ivy/design/l0_system/Colors.kt +++ b/ivy-design/src/main/java/com/ivy/design/l0_system/Colors.kt @@ -100,6 +100,7 @@ val GradientOrange = Gradient(Orange, OrangeLight) val GradientOrangeDark = Gradient(OrangeDark, Color(0xFFF2CD9E)) val GradientOrangeRevert = Gradient(Color(0xFFF2CD9E), Orange) val GradientPurple = Gradient(Purple, Color(0xFFAA99FF)) +val SunsetNight = Gradient(Red, Orange) @Composable fun pureBlur() = UI.colors.pure.copy(alpha = 0.95f) diff --git a/ivy-design/src/main/java/com/ivy/design/l0_system/IvyColors.kt b/ivy-design/src/main/java/com/ivy/design/l0_system/IvyColors.kt index 19fdf8eddc..d783d6dcb8 100644 --- a/ivy-design/src/main/java/com/ivy/design/l0_system/IvyColors.kt +++ b/ivy-design/src/main/java/com/ivy/design/l0_system/IvyColors.kt @@ -21,6 +21,7 @@ interface IvyColors { val red: Color val red1: Color + val red1Inverse: Color val isLight: Boolean } \ No newline at end of file diff --git a/ivy-design/src/main/java/com/ivy/design/l5_concept/SettingsScreen.kt b/ivy-design/src/main/java/com/ivy/design/l5_concept/SettingsScreen.kt deleted file mode 100644 index 784cec38c4..0000000000 --- a/ivy-design/src/main/java/com/ivy/design/l5_concept/SettingsScreen.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.ivy.design.l5_concept - -//Upgraded Ivy Wallet's settings screen \ No newline at end of file