Skip to content

Commit 96c261f

Browse files
authored
Android u5 (12): in-app purchase (#36)
* Android u5 (12): in-app purchase * upgdated gradle * updated build.gradle.kts
1 parent e9f6b2f commit 96c261f

File tree

13 files changed

+745
-23
lines changed

13 files changed

+745
-23
lines changed

upvpn-android/app/build.gradle.kts

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ android {
2727
applicationId = "app.upvpn.upvpn"
2828
minSdk = 24
2929
targetSdk = 34
30-
versionCode = 10
31-
versionName = "u4"
30+
versionCode = 12
31+
versionName = "u5"
3232

3333
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
3434
vectorDrawables {
@@ -68,7 +68,10 @@ android {
6868
debug {
6969
isMinifyEnabled = false
7070
versionNameSuffix = ".debug"
71-
val baseUrl = gradleLocalProperties(rootDir).getProperty("baseUrl", "https://upvpn.dev")
71+
val baseUrl = gradleLocalProperties(rootDir, providers).getProperty(
72+
"baseUrl",
73+
"https://upvpn.dev"
74+
)
7275
buildConfigField(
7376
"String",
7477
"UPVPN_BASE_URL",
@@ -138,21 +141,22 @@ android {
138141
}
139142

140143
dependencies {
141-
val navVersion = "2.8.2"
144+
val navVersion = "2.8.3"
142145
val roomVersion = "2.6.1"
143146
val sandwichVersion = "1.3.9"
147+
val billingVersion = "7.1.1"
144148

145149
implementation("androidx.core:core-ktx:1.13.1")
146150
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.6")
147151
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.6")
148-
implementation("androidx.activity:activity-compose:1.9.2")
149-
implementation(platform("androidx.compose:compose-bom:2024.09.03"))
150-
implementation("androidx.compose.ui:ui:1.7.3")
151-
implementation("androidx.compose.ui:ui-graphics:1.7.3")
152-
implementation("androidx.compose.ui:ui-tooling-preview:1.7.3")
152+
implementation("androidx.activity:activity-compose:1.9.3")
153+
implementation(platform("androidx.compose:compose-bom:2024.10.00"))
154+
implementation("androidx.compose.ui:ui:1.7.4")
155+
implementation("androidx.compose.ui:ui-graphics:1.7.4")
156+
implementation("androidx.compose.ui:ui-tooling-preview:1.7.4")
153157
implementation("androidx.compose.material3:material3:1.3.0")
154158
implementation("androidx.compose.material3:material3-window-size-class:1.3.0")
155-
implementation("androidx.compose.material:material-icons-extended:1.7.3")
159+
implementation("androidx.compose.material:material-icons-extended:1.7.4")
156160
implementation("androidx.navigation:navigation-compose:$navVersion")
157161
implementation("com.google.accompanist:accompanist-adaptive:0.32.0")
158162

@@ -176,6 +180,11 @@ dependencies {
176180
// Network response
177181
implementation("com.github.skydoves:sandwich:$sandwichVersion")
178182

183+
// IAP
184+
implementation("com.android.billingclient:billing:$billingVersion")
185+
implementation("com.android.billingclient:billing-ktx:$billingVersion")
186+
187+
179188
// for java.time
180189
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.2")
181190

@@ -184,7 +193,7 @@ dependencies {
184193
testImplementation("junit:junit:4.13.2")
185194
androidTestImplementation("androidx.test.ext:junit:1.2.1")
186195
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
187-
androidTestImplementation(platform("androidx.compose:compose-bom:2024.09.03"))
196+
androidTestImplementation(platform("androidx.compose:compose-bom:2024.10.00"))
188197
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
189198
debugImplementation("androidx.compose.ui:ui-tooling")
190199
debugImplementation("androidx.compose.ui:ui-test-manifest")

upvpn-android/app/src/main/kotlin/app/upvpn/upvpn/data/AppContainer.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ class DefaultAppContainer(private val context: Context) : AppContainer {
8282
}
8383

8484
override val planRepository: PlanRepository by lazy {
85-
DefaultPlanRepository(retrofitService)
85+
DefaultPlanRepository(retrofitService, vpnDatabase)
8686
}
8787

8888
override val serviceConnectionManager: VPNServiceConnectionManager by lazy {

upvpn-android/app/src/main/kotlin/app/upvpn/upvpn/data/PlanRepository.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,28 @@
11
package app.upvpn.upvpn.data
22

3+
import app.upvpn.upvpn.data.db.VPNDatabase
34
import app.upvpn.upvpn.model.UserPlan
45
import app.upvpn.upvpn.network.VPNApiService
56
import app.upvpn.upvpn.network.toResult
7+
import com.github.michaelbull.result.Err
8+
import com.github.michaelbull.result.Ok
69
import com.github.michaelbull.result.Result
710
import com.github.michaelbull.result.mapError
11+
import java.util.UUID
812

913
interface PlanRepository {
1014
suspend fun getUserPlan(): Result<UserPlan, String>
15+
suspend fun getEmailAndDeviceId(): Result<EmailAndDeviceId, String>
1116
}
1217

18+
data class EmailAndDeviceId(
19+
val email: String,
20+
val deviceId: UUID,
21+
)
22+
1323
class DefaultPlanRepository(
1424
private val vpnApiService: VPNApiService,
25+
private val vpnDatabase: VPNDatabase,
1526
) : PlanRepository {
1627

1728
private val tag = "DefaultPlanRepository"
@@ -20,4 +31,15 @@ class DefaultPlanRepository(
2031
return vpnApiService.getUserPlan().toResult()
2132
.mapError { e -> e.message }
2233
}
34+
35+
override suspend fun getEmailAndDeviceId(): Result<EmailAndDeviceId, String> {
36+
val user = vpnDatabase.userDao().getUser()
37+
val device = vpnDatabase.deviceDao().getDevice()
38+
39+
return if (user != null && device != null) {
40+
Ok(EmailAndDeviceId(user.email, device.uniqueId))
41+
} else {
42+
Err("user or device not found")
43+
}
44+
}
2345
}

upvpn-android/app/src/main/kotlin/app/upvpn/upvpn/ui/VPNApp.kt

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import androidx.window.layout.DisplayFeature
2323
import app.upvpn.upvpn.BuildConfig
2424
import app.upvpn.upvpn.model.Location
2525
import app.upvpn.upvpn.ui.components.LocationsPopup
26+
import app.upvpn.upvpn.ui.components.PlanBottomSheet
2627
import app.upvpn.upvpn.ui.components.VPNLayout
2728
import app.upvpn.upvpn.ui.screens.HelpScreen
2829
import app.upvpn.upvpn.ui.screens.HomeScreen
@@ -64,6 +65,8 @@ fun VPNApp(
6465
val uiState = authViewModel.uiState.collectAsStateWithLifecycle()
6566
val signOutUiState = authViewModel.signOutUiState.collectAsStateWithLifecycle()
6667

68+
var showPlanSheet by remember { mutableStateOf(false) }
69+
6770
val (startDestination, userEmail) = when (uiState.value.signInState) {
6871
is SignInState.SignedIn -> {
6972
VPNScreen.Home.name to (uiState.value.signInState as SignInState.SignedIn).email
@@ -116,12 +119,19 @@ fun VPNApp(
116119

117120
// show alert dialog for errors
118121
vpnNotifications.value.forEach { notification ->
122+
val dismissNotification = {
123+
homeVM.ackVpnNotification(notification)
124+
if (notification.msg.lowercase().contains("insufficient balance")) {
125+
showPlanSheet = true
126+
}
127+
}
128+
119129
AlertDialog(
120-
onDismissRequest = { homeVM.ackVpnNotification(notification) },
130+
onDismissRequest = dismissNotification,
121131
title = { Text("Oh No") },
122132
text = { Text(notification.msg) },
123133
confirmButton = {
124-
TextButton(onClick = { homeVM.ackVpnNotification(notification) }) {
134+
TextButton(onClick = dismissNotification) {
125135
Text("OK")
126136
}
127137
}
@@ -193,6 +203,12 @@ fun VPNApp(
193203
onRefresh = locationVM::onRefresh
194204
)
195205

206+
PlanBottomSheet(
207+
showPlanSheet = showPlanSheet,
208+
dismissPlanSheet = { showPlanSheet = false },
209+
planState = planState.value,
210+
refresh = { planVM.fetchPlan() })
211+
196212
NavHost(navController = navController, startDestination = startDestination) {
197213
composable(route = VPNScreen.Login.name) {
198214
SignInScreen(

upvpn-android/app/src/main/kotlin/app/upvpn/upvpn/ui/VPNAppViewModelProvider.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import androidx.lifecycle.viewmodel.initializer
55
import androidx.lifecycle.viewmodel.viewModelFactory
66
import app.upvpn.upvpn.VPNApplication
77
import app.upvpn.upvpn.ui.viewmodels.AuthViewModel
8+
import app.upvpn.upvpn.ui.viewmodels.BillingViewModel
89
import app.upvpn.upvpn.ui.viewmodels.HomeViewModel
910
import app.upvpn.upvpn.ui.viewmodels.LocationViewModel
1011
import app.upvpn.upvpn.ui.viewmodels.PlanViewModel
@@ -40,5 +41,11 @@ object VPNAppViewModelProvider {
4041
(this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as VPNApplication)
4142
PlanViewModel(application.container.planRepository)
4243
}
44+
45+
initializer {
46+
val application =
47+
(this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as VPNApplication)
48+
BillingViewModel(application.container.planRepository)
49+
}
4350
}
4451
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package app.upvpn.upvpn.ui.components
2+
3+
import androidx.compose.foundation.layout.fillMaxHeight
4+
import androidx.compose.material3.ExperimentalMaterial3Api
5+
import androidx.compose.material3.ModalBottomSheet
6+
import androidx.compose.material3.rememberModalBottomSheetState
7+
import androidx.compose.runtime.Composable
8+
import androidx.compose.ui.Modifier
9+
import app.upvpn.upvpn.ui.screens.PlanScreen
10+
import app.upvpn.upvpn.ui.viewmodels.PlanState
11+
12+
@OptIn(ExperimentalMaterial3Api::class)
13+
@Composable
14+
fun PlanBottomSheet(
15+
showPlanSheet: Boolean,
16+
dismissPlanSheet: () -> Unit,
17+
planState: PlanState,
18+
refresh: () -> Unit,
19+
) {
20+
val sheetState = rememberModalBottomSheetState(
21+
skipPartiallyExpanded = false,
22+
)
23+
24+
if (showPlanSheet) {
25+
ModalBottomSheet(
26+
modifier = Modifier.fillMaxHeight(),
27+
sheetState = sheetState,
28+
onDismissRequest = dismissPlanSheet
29+
) {
30+
PlanScreen(planState, refresh, dismissPlanSheet)
31+
}
32+
}
33+
}

0 commit comments

Comments
 (0)