From 16b4a9f125309ff3a9a80c671031b80a23b42736 Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Mon, 27 Oct 2025 11:31:07 +0530 Subject: [PATCH] feat(billing): implement a simple RevenueCat billing system Only for the Google Play version. The FOSS version remains free. --- app/build.gradle.kts | 3 +- .../pomodoro/billing/FossBillingManager.kt | 32 ++++++++++ .../billing/TomatoPlusPaywallDialog.kt | 27 ++++++++ .../pomodoro/billing/initializePurchases.kt | 22 +++++++ .../java/org/nsh07/pomodoro/MainActivity.kt | 6 +- .../org/nsh07/pomodoro/TomatoApplication.kt | 20 ++++++ .../nsh07/pomodoro/billing/BillingManager.kt | 24 +++++++ .../org/nsh07/pomodoro/data/AppContainer.kt | 5 ++ .../java/org/nsh07/pomodoro/ui/AppScreen.kt | 13 +++- .../ui/settingsScreen/SettingsScreen.kt | 49 +++++++++++++++ .../viewModel/SettingsViewModel.kt | 6 ++ .../pomodoro/billing/PlayBillingManager.kt | 58 +++++++++++++++++ .../billing/TomatoPlusPaywallDialog.kt | 63 +++++++++++++++++++ .../pomodoro/billing/initializePurchases.kt | 30 +++++++++ gradle/libs.versions.toml | 5 +- 15 files changed, 354 insertions(+), 9 deletions(-) create mode 100644 app/src/foss/java/org/nsh07/pomodoro/billing/FossBillingManager.kt create mode 100644 app/src/foss/java/org/nsh07/pomodoro/billing/TomatoPlusPaywallDialog.kt create mode 100644 app/src/foss/java/org/nsh07/pomodoro/billing/initializePurchases.kt create mode 100644 app/src/main/java/org/nsh07/pomodoro/billing/BillingManager.kt create mode 100644 app/src/play/java/org/nsh07/pomodoro/billing/PlayBillingManager.kt create mode 100644 app/src/play/java/org/nsh07/pomodoro/billing/TomatoPlusPaywallDialog.kt create mode 100644 app/src/play/java/org/nsh07/pomodoro/billing/initializePurchases.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 31e1df6..a396eba 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -116,7 +116,8 @@ dependencies { implementation(libs.androidx.room.ktx) ksp(libs.androidx.room.compiler) - "playImplementation"(libs.purchases) + "playImplementation"(libs.revenuecat.purchases) + "playImplementation"(libs.revenuecat.purchases.ui) testImplementation(libs.junit) diff --git a/app/src/foss/java/org/nsh07/pomodoro/billing/FossBillingManager.kt b/app/src/foss/java/org/nsh07/pomodoro/billing/FossBillingManager.kt new file mode 100644 index 0000000..4a155a3 --- /dev/null +++ b/app/src/foss/java/org/nsh07/pomodoro/billing/FossBillingManager.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2025 Nishant Mishra + * + * This file is part of Tomato - a minimalist pomodoro timer for Android. + * + * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU + * General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tomato. + * If not, see . + */ + +package org.nsh07.pomodoro.billing + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +/** + * Google Play implementation of BillingManager + */ +class FossBillingManager : BillingManager { + override val isPlus = MutableStateFlow(true).asStateFlow() +} + +object BillingManagerProvider { + val manager: BillingManager = FossBillingManager() +} \ No newline at end of file diff --git a/app/src/foss/java/org/nsh07/pomodoro/billing/TomatoPlusPaywallDialog.kt b/app/src/foss/java/org/nsh07/pomodoro/billing/TomatoPlusPaywallDialog.kt new file mode 100644 index 0000000..973041c --- /dev/null +++ b/app/src/foss/java/org/nsh07/pomodoro/billing/TomatoPlusPaywallDialog.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025 Nishant Mishra + * + * This file is part of Tomato - a minimalist pomodoro timer for Android. + * + * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU + * General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tomato. + * If not, see . + */ + +package org.nsh07.pomodoro.billing + +import androidx.compose.runtime.Composable + +@Composable +fun TomatoPlusPaywallDialog( + isPlus: Boolean, + onDismiss: () -> Unit +) { +} \ No newline at end of file diff --git a/app/src/foss/java/org/nsh07/pomodoro/billing/initializePurchases.kt b/app/src/foss/java/org/nsh07/pomodoro/billing/initializePurchases.kt new file mode 100644 index 0000000..7a44002 --- /dev/null +++ b/app/src/foss/java/org/nsh07/pomodoro/billing/initializePurchases.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025 Nishant Mishra + * + * This file is part of Tomato - a minimalist pomodoro timer for Android. + * + * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU + * General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tomato. + * If not, see . + */ + +package org.nsh07.pomodoro.billing + +import android.content.Context + +fun initializePurchases(context: Context) {} \ No newline at end of file diff --git a/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt b/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt index a129af6..4f34773 100644 --- a/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt +++ b/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt @@ -30,12 +30,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import org.nsh07.pomodoro.ui.AppScreen import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsViewModel import org.nsh07.pomodoro.ui.theme.TomatoTheme -import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerViewModel import org.nsh07.pomodoro.utils.toColor class MainActivity : ComponentActivity() { - private val timerViewModel: TimerViewModel by viewModels(factoryProducer = { TimerViewModel.Factory }) private val settingsViewModel: SettingsViewModel by viewModels(factoryProducer = { SettingsViewModel.Factory }) private val appContainer by lazy { @@ -62,6 +60,8 @@ class MainActivity : ComponentActivity() { val seed = preferencesState.colorScheme.toColor() + val isPlus by settingsViewModel.isPlus.collectAsStateWithLifecycle() + TomatoTheme( darkTheme = darkTheme, seedColor = seed, @@ -73,7 +73,7 @@ class MainActivity : ComponentActivity() { } AppScreen( - timerViewModel = timerViewModel, + isPlus = isPlus, isAODEnabled = preferencesState.aodEnabled, setTimerFrequency = { appContainer.appTimerRepository.timerFrequency = it diff --git a/app/src/main/java/org/nsh07/pomodoro/TomatoApplication.kt b/app/src/main/java/org/nsh07/pomodoro/TomatoApplication.kt index 87be588..47de7fe 100644 --- a/app/src/main/java/org/nsh07/pomodoro/TomatoApplication.kt +++ b/app/src/main/java/org/nsh07/pomodoro/TomatoApplication.kt @@ -1,8 +1,26 @@ +/* + * Copyright (c) 2025 Nishant Mishra + * + * This file is part of Tomato - a minimalist pomodoro timer for Android. + * + * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU + * General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tomato. + * If not, see . + */ + package org.nsh07.pomodoro import android.app.Application import android.app.NotificationChannel import android.app.NotificationManager +import org.nsh07.pomodoro.billing.initializePurchases import org.nsh07.pomodoro.data.AppContainer import org.nsh07.pomodoro.data.DefaultAppContainer @@ -12,6 +30,8 @@ class TomatoApplication : Application() { super.onCreate() container = DefaultAppContainer(this) + initializePurchases(this) + val notificationChannel = NotificationChannel( "timer", getString(R.string.timer_progress), diff --git a/app/src/main/java/org/nsh07/pomodoro/billing/BillingManager.kt b/app/src/main/java/org/nsh07/pomodoro/billing/BillingManager.kt new file mode 100644 index 0000000..66b1659 --- /dev/null +++ b/app/src/main/java/org/nsh07/pomodoro/billing/BillingManager.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025 Nishant Mishra + * + * This file is part of Tomato - a minimalist pomodoro timer for Android. + * + * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU + * General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tomato. + * If not, see . + */ + +package org.nsh07.pomodoro.billing + +import kotlinx.coroutines.flow.StateFlow + +interface BillingManager { + val isPlus: StateFlow +} \ No newline at end of file diff --git a/app/src/main/java/org/nsh07/pomodoro/data/AppContainer.kt b/app/src/main/java/org/nsh07/pomodoro/data/AppContainer.kt index 4ca23c9..bf445c8 100644 --- a/app/src/main/java/org/nsh07/pomodoro/data/AppContainer.kt +++ b/app/src/main/java/org/nsh07/pomodoro/data/AppContainer.kt @@ -26,6 +26,8 @@ import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import kotlinx.coroutines.flow.MutableStateFlow import org.nsh07.pomodoro.R +import org.nsh07.pomodoro.billing.BillingManager +import org.nsh07.pomodoro.billing.BillingManagerProvider import org.nsh07.pomodoro.service.addTimerActions import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerState import org.nsh07.pomodoro.utils.millisecondsToStr @@ -34,6 +36,7 @@ interface AppContainer { val appPreferenceRepository: AppPreferenceRepository val appStatRepository: AppStatRepository val appTimerRepository: AppTimerRepository + val billingManager: BillingManager val notificationManager: NotificationManagerCompat val notificationManagerService: NotificationManager val notificationBuilder: NotificationCompat.Builder @@ -54,6 +57,8 @@ class DefaultAppContainer(context: Context) : AppContainer { override val appTimerRepository: AppTimerRepository by lazy { AppTimerRepository() } + override val billingManager: BillingManager by lazy { BillingManagerProvider.manager } + override val notificationManager: NotificationManagerCompat by lazy { NotificationManagerCompat.from(context) } diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt index 905e377..5455ad9 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt @@ -41,8 +41,10 @@ import androidx.compose.material3.Text import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLayoutDirection @@ -54,6 +56,7 @@ import androidx.navigation3.runtime.entryProvider import androidx.navigation3.runtime.rememberNavBackStack import androidx.navigation3.ui.NavDisplay import androidx.window.core.layout.WindowSizeClass +import org.nsh07.pomodoro.billing.TomatoPlusPaywallDialog import org.nsh07.pomodoro.service.TimerService import org.nsh07.pomodoro.ui.settingsScreen.SettingsScreenRoot import org.nsh07.pomodoro.ui.statsScreen.StatsScreenRoot @@ -65,10 +68,11 @@ import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerViewModel @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable fun AppScreen( - modifier: Modifier = Modifier, - timerViewModel: TimerViewModel = viewModel(factory = TimerViewModel.Factory), isAODEnabled: Boolean, - setTimerFrequency: (Float) -> Unit + isPlus: Boolean, + setTimerFrequency: (Float) -> Unit, + modifier: Modifier = Modifier, + timerViewModel: TimerViewModel = viewModel(factory = TimerViewModel.Factory) ) { val context = LocalContext.current @@ -91,6 +95,7 @@ fun AppScreen( } } + var showPaywall by remember { mutableStateOf(false) } Scaffold( bottomBar = { @@ -218,6 +223,7 @@ fun AppScreen( entry { SettingsScreenRoot( + setShowPaywall = { showPaywall = it }, modifier = modifier.padding( start = contentPadding.calculateStartPadding(layoutDirection), end = contentPadding.calculateEndPadding(layoutDirection), @@ -240,4 +246,5 @@ fun AppScreen( ) } } + if (showPaywall) TomatoPlusPaywallDialog(isPlus = isPlus) { showPaywall = false } } \ No newline at end of file diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt index a22b1f5..356c922 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt @@ -17,6 +17,7 @@ package org.nsh07.pomodoro.ui.settingsScreen +import android.annotation.SuppressLint import android.content.Intent import android.net.Uri import androidx.compose.animation.fadeIn @@ -25,19 +26,26 @@ import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.togetherWith import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.SliderState import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar @@ -50,6 +58,7 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext @@ -80,6 +89,7 @@ import org.nsh07.pomodoro.ui.theme.CustomColors.topBarColors @OptIn(ExperimentalMaterial3Api::class) @Composable fun SettingsScreenRoot( + setShowPaywall: (Boolean) -> Unit, modifier: Modifier = Modifier, viewModel: SettingsViewModel = viewModel(factory = SettingsViewModel.Factory) ) { @@ -102,6 +112,7 @@ fun SettingsScreenRoot( viewModel.longBreakTimeTextFieldState } + val isPlus by viewModel.isPlus.collectAsStateWithLifecycle() val alarmEnabled by viewModel.alarmEnabled.collectAsStateWithLifecycle(true) val vibrateEnabled by viewModel.vibrateEnabled.collectAsStateWithLifecycle(true) val dndEnabled by viewModel.dndEnabled.collectAsStateWithLifecycle(false) @@ -119,6 +130,7 @@ fun SettingsScreenRoot( } SettingsScreen( + isPlus = isPlus, preferencesState = preferencesState, backStack = backStack, focusTimeInputFieldState = focusTimeInputFieldState, @@ -143,13 +155,16 @@ fun SettingsScreenRoot( }, onThemeChange = viewModel::saveTheme, onColorSchemeChange = viewModel::saveColorScheme, + setShowPaywall = setShowPaywall, modifier = modifier ) } +@SuppressLint("LocalContextGetResourceValueCall") @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable private fun SettingsScreen( + isPlus: Boolean, preferencesState: PreferencesState, backStack: SnapshotStateList, focusTimeInputFieldState: TextFieldState, @@ -168,6 +183,7 @@ private fun SettingsScreen( onAlarmSoundChanged: (Uri?) -> Unit, onThemeChange: (String) -> Unit, onColorSchemeChange: (Color) -> Unit, + setShowPaywall: (Boolean) -> Unit, modifier: Modifier = Modifier ) { val context = LocalContext.current @@ -217,6 +233,39 @@ private fun SettingsScreen( ) { item { Spacer(Modifier.height(12.dp)) } + if (!isPlus) item { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .clip(CircleShape) + .background(colorScheme.primary) + .padding(16.dp) + .clickable { setShowPaywall(true) } + ) { + Icon( + painterResource(R.drawable.tomato_logo_notification), + null, + tint = colorScheme.onPrimary, + modifier = Modifier + .size(24.dp) + ) + Spacer(Modifier.width(8.dp)) + Text( + "Get Tomato+", + style = typography.titleLarge, + fontFamily = robotoFlexTopBar, + color = colorScheme.onPrimary + ) + Spacer(Modifier.weight(1f)) + Icon( + painterResource(R.drawable.arrow_forward_big), + null, + tint = colorScheme.onPrimary + ) + } + Spacer(Modifier.height(14.dp)) + } + item { AboutCard() } item { Spacer(Modifier.height(12.dp)) } diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt index 0a3262f..6f9041d 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt @@ -40,17 +40,21 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.nsh07.pomodoro.TomatoApplication +import org.nsh07.pomodoro.billing.BillingManager import org.nsh07.pomodoro.data.AppPreferenceRepository import org.nsh07.pomodoro.data.TimerRepository import org.nsh07.pomodoro.ui.Screen @OptIn(FlowPreview::class, ExperimentalMaterial3Api::class) class SettingsViewModel( + private val billingManager: BillingManager, private val preferenceRepository: AppPreferenceRepository, private val timerRepository: TimerRepository, ) : ViewModel() { val backStack = mutableStateListOf(Screen.Settings.Main) + val isPlus = billingManager.isPlus + private val _preferencesState = MutableStateFlow(PreferencesState()) val preferencesState = _preferencesState.asStateFlow() @@ -237,8 +241,10 @@ class SettingsViewModel( val application = (this[APPLICATION_KEY] as TomatoApplication) val appPreferenceRepository = application.container.appPreferenceRepository val appTimerRepository = application.container.appTimerRepository + val appBillingManager = application.container.billingManager SettingsViewModel( + billingManager = appBillingManager, preferenceRepository = appPreferenceRepository, timerRepository = appTimerRepository, ) diff --git a/app/src/play/java/org/nsh07/pomodoro/billing/PlayBillingManager.kt b/app/src/play/java/org/nsh07/pomodoro/billing/PlayBillingManager.kt new file mode 100644 index 0000000..94bfcb2 --- /dev/null +++ b/app/src/play/java/org/nsh07/pomodoro/billing/PlayBillingManager.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025 Nishant Mishra + * + * This file is part of Tomato - a minimalist pomodoro timer for Android. + * + * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU + * General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tomato. + * If not, see . + */ + +package org.nsh07.pomodoro.billing + +import android.util.Log +import com.revenuecat.purchases.Purchases +import com.revenuecat.purchases.getCustomerInfoWith +import com.revenuecat.purchases.interfaces.UpdatedCustomerInfoListener +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +private const val ENTITLEMENT_ID = "plus" + +/** + * Google Play implementation of BillingManager + */ +class PlayBillingManager : BillingManager { + private val _isPlus = MutableStateFlow(false) + override val isPlus = _isPlus.asStateFlow() + + private val purchases by lazy { Purchases.sharedInstance } + + init { + purchases.updatedCustomerInfoListener = + UpdatedCustomerInfoListener { customerInfo -> + _isPlus.value = customerInfo.entitlements[ENTITLEMENT_ID]?.isActive == true + } + + // Fetch initial customer info + purchases.getCustomerInfoWith( + onSuccess = { customerInfo -> + _isPlus.value = customerInfo.entitlements[ENTITLEMENT_ID]?.isActive == true + }, + onError = { error -> + Log.e("GooglePlayPaywallManager", "Error fetching customer info: $error") + } + ) + } +} + +object BillingManagerProvider { + val manager: BillingManager = PlayBillingManager() +} \ No newline at end of file diff --git a/app/src/play/java/org/nsh07/pomodoro/billing/TomatoPlusPaywallDialog.kt b/app/src/play/java/org/nsh07/pomodoro/billing/TomatoPlusPaywallDialog.kt new file mode 100644 index 0000000..1ecda02 --- /dev/null +++ b/app/src/play/java/org/nsh07/pomodoro/billing/TomatoPlusPaywallDialog.kt @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025 Nishant Mishra + * + * This file is part of Tomato - a minimalist pomodoro timer for Android. + * + * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU + * General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tomato. + * If not, see . + */ + +package org.nsh07.pomodoro.billing + +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.FilledTonalIconButton +import androidx.compose.material3.Icon +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import com.revenuecat.purchases.ui.revenuecatui.Paywall +import com.revenuecat.purchases.ui.revenuecatui.PaywallOptions +import com.revenuecat.purchases.ui.revenuecatui.customercenter.CustomerCenter +import org.nsh07.pomodoro.R + +@Composable +fun TomatoPlusPaywallDialog( + isPlus: Boolean, + onDismiss: () -> Unit +) { + val paywallOptions = remember { + PaywallOptions.Builder(dismissRequest = onDismiss) + .build() + } + + Scaffold { innerPadding -> + if (!isPlus) { + Paywall(paywallOptions) + + FilledTonalIconButton( + onClick = onDismiss, + modifier = Modifier + .padding(innerPadding) + .padding(16.dp) + ) { + Icon( + painterResource(R.drawable.arrow_back), + null + ) + } + } else { + CustomerCenter(onDismiss = onDismiss) + } + } +} \ No newline at end of file diff --git a/app/src/play/java/org/nsh07/pomodoro/billing/initializePurchases.kt b/app/src/play/java/org/nsh07/pomodoro/billing/initializePurchases.kt new file mode 100644 index 0000000..00e2437 --- /dev/null +++ b/app/src/play/java/org/nsh07/pomodoro/billing/initializePurchases.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2025 Nishant Mishra + * + * This file is part of Tomato - a minimalist pomodoro timer for Android. + * + * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU + * General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tomato. + * If not, see . + */ + +package org.nsh07.pomodoro.billing + +import android.content.Context +import com.revenuecat.purchases.Purchases +import com.revenuecat.purchases.PurchasesConfiguration + +fun initializePurchases(context: Context) { + Purchases.configure( + PurchasesConfiguration + .Builder(context, "goog_jBpRIBjTYvhKYluCqkPXSHbuFbX") + .build() + ) +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9356ceb..85f21bf 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,7 +12,7 @@ ksp = "2.2.20-2.0.4" lifecycleRuntimeKtx = "2.9.4" materialKolor = "3.0.1" navigation3 = "1.0.0-beta01" -purchases = "9.12.0" +revenuecat = "9.12.0" room = "2.8.3" vico = "2.2.1" @@ -40,7 +40,8 @@ androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } junit = { group = "junit", name = "junit", version.ref = "junit" } material-kolor = { module = "com.materialkolor:material-kolor", version.ref = "materialKolor" } -purchases = { module = "com.revenuecat.purchases:purchases", version.ref = "purchases" } +revenuecat-purchases = { module = "com.revenuecat.purchases:purchases", version.ref = "revenuecat" } +revenuecat-purchases-ui = { module = "com.revenuecat.purchases:purchases-ui", version.ref = "revenuecat" } vico-compose-m3 = { group = "com.patrykandpatrick.vico", name = "compose-m3", version.ref = "vico" } [plugins]