Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 20 additions & 11 deletions upvpn-android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ android {
applicationId = "app.upvpn.upvpn"
minSdk = 24
targetSdk = 34
versionCode = 10
versionName = "u4"
versionCode = 12
versionName = "u5"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
Expand Down Expand Up @@ -68,7 +68,10 @@ android {
debug {
isMinifyEnabled = false
versionNameSuffix = ".debug"
val baseUrl = gradleLocalProperties(rootDir).getProperty("baseUrl", "https://upvpn.dev")
val baseUrl = gradleLocalProperties(rootDir, providers).getProperty(
"baseUrl",
"https://upvpn.dev"
)
buildConfigField(
"String",
"UPVPN_BASE_URL",
Expand Down Expand Up @@ -138,21 +141,22 @@ android {
}

dependencies {
val navVersion = "2.8.2"
val navVersion = "2.8.3"
val roomVersion = "2.6.1"
val sandwichVersion = "1.3.9"
val billingVersion = "7.1.1"

implementation("androidx.core:core-ktx:1.13.1")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.6")
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.6")
implementation("androidx.activity:activity-compose:1.9.2")
implementation(platform("androidx.compose:compose-bom:2024.09.03"))
implementation("androidx.compose.ui:ui:1.7.3")
implementation("androidx.compose.ui:ui-graphics:1.7.3")
implementation("androidx.compose.ui:ui-tooling-preview:1.7.3")
implementation("androidx.activity:activity-compose:1.9.3")
implementation(platform("androidx.compose:compose-bom:2024.10.00"))
implementation("androidx.compose.ui:ui:1.7.4")
implementation("androidx.compose.ui:ui-graphics:1.7.4")
implementation("androidx.compose.ui:ui-tooling-preview:1.7.4")
implementation("androidx.compose.material3:material3:1.3.0")
implementation("androidx.compose.material3:material3-window-size-class:1.3.0")
implementation("androidx.compose.material:material-icons-extended:1.7.3")
implementation("androidx.compose.material:material-icons-extended:1.7.4")
implementation("androidx.navigation:navigation-compose:$navVersion")
implementation("com.google.accompanist:accompanist-adaptive:0.32.0")

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

// IAP
implementation("com.android.billingclient:billing:$billingVersion")
implementation("com.android.billingclient:billing-ktx:$billingVersion")


// for java.time
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.2")

Expand All @@ -184,7 +193,7 @@ dependencies {
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.2.1")
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
androidTestImplementation(platform("androidx.compose:compose-bom:2024.09.03"))
androidTestImplementation(platform("androidx.compose:compose-bom:2024.10.00"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class DefaultAppContainer(private val context: Context) : AppContainer {
}

override val planRepository: PlanRepository by lazy {
DefaultPlanRepository(retrofitService)
DefaultPlanRepository(retrofitService, vpnDatabase)
}

override val serviceConnectionManager: VPNServiceConnectionManager by lazy {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
package app.upvpn.upvpn.data

import app.upvpn.upvpn.data.db.VPNDatabase
import app.upvpn.upvpn.model.UserPlan
import app.upvpn.upvpn.network.VPNApiService
import app.upvpn.upvpn.network.toResult
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import com.github.michaelbull.result.mapError
import java.util.UUID

interface PlanRepository {
suspend fun getUserPlan(): Result<UserPlan, String>
suspend fun getEmailAndDeviceId(): Result<EmailAndDeviceId, String>
}

data class EmailAndDeviceId(
val email: String,
val deviceId: UUID,
)

class DefaultPlanRepository(
private val vpnApiService: VPNApiService,
private val vpnDatabase: VPNDatabase,
) : PlanRepository {

private val tag = "DefaultPlanRepository"
Expand All @@ -20,4 +31,15 @@ class DefaultPlanRepository(
return vpnApiService.getUserPlan().toResult()
.mapError { e -> e.message }
}

override suspend fun getEmailAndDeviceId(): Result<EmailAndDeviceId, String> {
val user = vpnDatabase.userDao().getUser()
val device = vpnDatabase.deviceDao().getDevice()

return if (user != null && device != null) {
Ok(EmailAndDeviceId(user.email, device.uniqueId))
} else {
Err("user or device not found")
}
}
}
20 changes: 18 additions & 2 deletions upvpn-android/app/src/main/kotlin/app/upvpn/upvpn/ui/VPNApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import androidx.window.layout.DisplayFeature
import app.upvpn.upvpn.BuildConfig
import app.upvpn.upvpn.model.Location
import app.upvpn.upvpn.ui.components.LocationsPopup
import app.upvpn.upvpn.ui.components.PlanBottomSheet
import app.upvpn.upvpn.ui.components.VPNLayout
import app.upvpn.upvpn.ui.screens.HelpScreen
import app.upvpn.upvpn.ui.screens.HomeScreen
Expand Down Expand Up @@ -64,6 +65,8 @@ fun VPNApp(
val uiState = authViewModel.uiState.collectAsStateWithLifecycle()
val signOutUiState = authViewModel.signOutUiState.collectAsStateWithLifecycle()

var showPlanSheet by remember { mutableStateOf(false) }

val (startDestination, userEmail) = when (uiState.value.signInState) {
is SignInState.SignedIn -> {
VPNScreen.Home.name to (uiState.value.signInState as SignInState.SignedIn).email
Expand Down Expand Up @@ -116,12 +119,19 @@ fun VPNApp(

// show alert dialog for errors
vpnNotifications.value.forEach { notification ->
val dismissNotification = {
homeVM.ackVpnNotification(notification)
if (notification.msg.lowercase().contains("insufficient balance")) {
showPlanSheet = true
}
}

AlertDialog(
onDismissRequest = { homeVM.ackVpnNotification(notification) },
onDismissRequest = dismissNotification,
title = { Text("Oh No") },
text = { Text(notification.msg) },
confirmButton = {
TextButton(onClick = { homeVM.ackVpnNotification(notification) }) {
TextButton(onClick = dismissNotification) {
Text("OK")
}
}
Expand Down Expand Up @@ -193,6 +203,12 @@ fun VPNApp(
onRefresh = locationVM::onRefresh
)

PlanBottomSheet(
showPlanSheet = showPlanSheet,
dismissPlanSheet = { showPlanSheet = false },
planState = planState.value,
refresh = { planVM.fetchPlan() })

NavHost(navController = navController, startDestination = startDestination) {
composable(route = VPNScreen.Login.name) {
SignInScreen(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import app.upvpn.upvpn.VPNApplication
import app.upvpn.upvpn.ui.viewmodels.AuthViewModel
import app.upvpn.upvpn.ui.viewmodels.BillingViewModel
import app.upvpn.upvpn.ui.viewmodels.HomeViewModel
import app.upvpn.upvpn.ui.viewmodels.LocationViewModel
import app.upvpn.upvpn.ui.viewmodels.PlanViewModel
Expand Down Expand Up @@ -40,5 +41,11 @@ object VPNAppViewModelProvider {
(this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as VPNApplication)
PlanViewModel(application.container.planRepository)
}

initializer {
val application =
(this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as VPNApplication)
BillingViewModel(application.container.planRepository)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package app.upvpn.upvpn.ui.components

import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import app.upvpn.upvpn.ui.screens.PlanScreen
import app.upvpn.upvpn.ui.viewmodels.PlanState

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PlanBottomSheet(
showPlanSheet: Boolean,
dismissPlanSheet: () -> Unit,
planState: PlanState,
refresh: () -> Unit,
) {
val sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = false,
)

if (showPlanSheet) {
ModalBottomSheet(
modifier = Modifier.fillMaxHeight(),
sheetState = sheetState,
onDismissRequest = dismissPlanSheet
) {
PlanScreen(planState, refresh, dismissPlanSheet)
}
}
}
Loading