Skip to content

Commit

Permalink
Added App Details bottom sheet to Settings Screen
Browse files Browse the repository at this point in the history
  • Loading branch information
chRyNaN committed Feb 17, 2025
1 parent cf5049f commit 57ad040
Show file tree
Hide file tree
Showing 10 changed files with 244 additions and 14 deletions.
16 changes: 13 additions & 3 deletions app-shared/src/commonMain/composeResources/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
<resources>

<string name="global_not_available">N/A</string>
<string name="global_unexpected_error">Unexpected error</string>
<string name="global_yes">Yes</string>
<string name="global_no">No</string>

<string name="app_name">mooncloak VPN</string>
<string name="app_description">Go dark, stay bright</string>

Expand Down Expand Up @@ -100,9 +105,6 @@
<string name="data_format_abbr_gbps">Gbps</string>
<string name="data_format_abbr_tbps">Tbps</string>

<string name="global_not_available">N/A</string>
<string name="global_unexpected_error">Unexpected error</string>

<string name="country_list_error_title_no_vpn_servers">Unexpected error loading VPN servers</string>
<string name="country_list_error_description_no_vpn_servers">There was an unexpected error attempting to load the countries that have VPN servers. Please try again later. If the error persists, contact support.</string>
<string name="country_list_default_region_type">Regions</string>
Expand Down Expand Up @@ -137,6 +139,14 @@
<string name="settings_option_screen_lock_timeout_30_minutes">30 minutes</string>
<string name="settings_option_screen_lock_timeout_custom_time">Custom time</string>

<string name="settings_app_details_header">App Details</string>
<string name="settings_app_details_title_id">ID</string>
<string name="settings_app_details_title_name">Name</string>
<string name="settings_app_details_title_version">Version</string>
<string name="settings_app_details_title_debug">Debug</string>
<string name="settings_app_details_title_pre_release">Pre-release</string>
<string name="settings_app_details_title_build_time">Build Time</string>

<string name="subscription_no_active_plan">No active plan</string>
<string name="subscription_title_no_active_plan">No Active Plan</string>
<string name="subscription_title_active_plan">Active Plan</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.unit.dp
import com.mooncloak.kodetools.statex.persistence.ExperimentalPersistentStateAPI
import com.mooncloak.kodetools.statex.update
Expand Down Expand Up @@ -71,7 +70,6 @@ public fun SettingsScreen(
val bottomSheetState = rememberModalNavigationBottomSheetState<SettingsBottomSheetDestination>()
val scrollState = rememberScrollState()
val coroutineScope = rememberCoroutineScope()
val uriHandler = LocalUriHandler.current
val preferencesStorage = rememberDependency { keyValueStorage.preferences }

LaunchedEffect(Unit) {
Expand Down Expand Up @@ -135,9 +133,19 @@ public fun SettingsScreen(
color = MaterialTheme.colorScheme.outline.copy(alpha = SecondaryAlpha)
)

println("appDetails: ${viewModel.state.current.value.appDetails}")

SettingsAppGroup(
appVersion = viewModel.state.current.value.appVersion,
appVersion = viewModel.state.current.value.appDetails?.version,
sourceCodeUri = viewModel.state.current.value.sourceCodeUri,
appDetailsEnabled = viewModel.state.current.value.appDetails != null,
onOpenAppDetails = {
viewModel.state.current.value.appDetails?.let { details ->
coroutineScope.launch {
bottomSheetState.show(SettingsBottomSheetDestination.AppInfo(details))
}
}
},
onOpenDependencyList = {
coroutineScope.launch {
bottomSheetState.show(SettingsBottomSheetDestination.DependencyLicenseList)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package com.mooncloak.vpn.app.shared.feature.settings

import androidx.compose.runtime.Immutable
import com.mooncloak.vpn.app.shared.feature.settings.model.SettingsAppDetails
import kotlin.time.Duration
import kotlin.time.Duration.Companion.minutes

@Immutable
public data class SettingsStateModel public constructor(
public val appVersion: String? = null,
public val appDetails: SettingsAppDetails? = null,
public val currentPlan: String? = null,
public val privacyPolicyUri: String? = null,
public val termsUri: String? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import androidx.compose.runtime.Stable
import com.mooncloak.kodetools.konstruct.annotations.Inject
import com.mooncloak.kodetools.logpile.core.LogPile
import com.mooncloak.kodetools.logpile.core.error
import com.mooncloak.kodetools.logpile.core.info
import com.mooncloak.kodetools.statex.ViewModel
import com.mooncloak.kodetools.statex.persistence.ExperimentalPersistentStateAPI
import com.mooncloak.kodetools.statex.update
import com.mooncloak.vpn.app.shared.di.FeatureScoped
import com.mooncloak.vpn.app.shared.feature.settings.model.SettingsAppDetails
import com.mooncloak.vpn.app.shared.info.AppClientInfo
import com.mooncloak.vpn.app.shared.resource.Res
import com.mooncloak.vpn.app.shared.resource.app_copyright
Expand Down Expand Up @@ -36,10 +38,11 @@ public class SettingsViewModel @Inject public constructor(

@OptIn(ExperimentalPersistentStateAPI::class)
public fun load() {
LogPile.info("SettingsViewModel: load")
coroutineScope.launch {
emit(value = state.current.value.copy(isLoading = true))

var appVersion: String? = state.current.value.appVersion
var appDetails: SettingsAppDetails? = state.current.value.appDetails
var privacyPolicyUri: String? = state.current.value.privacyPolicyUri
var termsUri: String? = state.current.value.termsUri
var sourceCodeUri: String? = state.current.value.sourceCodeUri
Expand All @@ -51,7 +54,14 @@ public class SettingsViewModel @Inject public constructor(
var systemAuthTimeout = state.current.value.systemAuthTimeout

try {
appVersion = appClientInfo.versionName
appDetails = SettingsAppDetails(
id = appClientInfo.id,
name = appClientInfo.name,
version = appClientInfo.versionName,
isDebug = appClientInfo.isDebug,
isPreRelease = appClientInfo.isPreRelease,
buildTime = appClientInfo.buildTime
)
privacyPolicyUri = appClientInfo.privacyPolicyUri
termsUri = appClientInfo.termsAndConditionsUri
sourceCodeUri = appClientInfo.sourceCodeUri
Expand All @@ -75,7 +85,7 @@ public class SettingsViewModel @Inject public constructor(
emit(
value = state.current.value.copy(
isLoading = false,
appVersion = appVersion,
appDetails = appDetails,
currentPlan = currentPlan,
privacyPolicyUri = privacyPolicyUri,
termsUri = termsUri,
Expand All @@ -92,7 +102,7 @@ public class SettingsViewModel @Inject public constructor(
value = state.current.value.copy(
isLoading = false,
errorMessage = e.message ?: getString(Res.string.global_unexpected_error),
appVersion = appVersion,
appDetails = appDetails,
currentPlan = currentPlan,
privacyPolicyUri = privacyPolicyUri,
termsUri = termsUri,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package com.mooncloak.vpn.app.shared.feature.settings.composable

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.mooncloak.vpn.app.shared.feature.settings.model.SettingsAppDetails
import com.mooncloak.vpn.app.shared.resource.Res
import com.mooncloak.vpn.app.shared.resource.global_not_available
import com.mooncloak.vpn.app.shared.resource.global_yes
import com.mooncloak.vpn.app.shared.resource.settings_app_details_header
import com.mooncloak.vpn.app.shared.resource.settings_app_details_title_build_time
import com.mooncloak.vpn.app.shared.resource.settings_app_details_title_debug
import com.mooncloak.vpn.app.shared.resource.settings_app_details_title_id
import com.mooncloak.vpn.app.shared.resource.settings_app_details_title_name
import com.mooncloak.vpn.app.shared.resource.settings_app_details_title_pre_release
import com.mooncloak.vpn.app.shared.resource.settings_app_details_title_version
import com.mooncloak.vpn.app.shared.theme.SecondaryAlpha
import com.mooncloak.vpn.app.shared.util.time.DateTimeFormatter
import com.mooncloak.vpn.app.shared.util.time.Full
import com.mooncloak.vpn.app.shared.util.time.format
import org.jetbrains.compose.resources.stringResource

@Composable
internal fun AppDetailsBottomSheetLayout(
details: SettingsAppDetails,
modifier: Modifier = Modifier,
dateTimeFormatter: DateTimeFormatter = remember { DateTimeFormatter.Full }
) {
Surface(
modifier = modifier,
color = MaterialTheme.colorScheme.surface,
contentColor = MaterialTheme.colorScheme.onSurface
) {
Column(
modifier = Modifier.fillMaxWidth()
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
modifier = Modifier.fillMaxWidth()
.padding(horizontal = 16.dp),
text = stringResource(Res.string.settings_app_details_header),
style = MaterialTheme.typography.titleLarge
)

ListItem(
modifier = Modifier.fillMaxWidth(),
colors = ListItemDefaults.colors(
containerColor = MaterialTheme.colorScheme.surface
),
headlineContent = {
Text(text = stringResource(Res.string.settings_app_details_title_id))
},
supportingContent = {
Text(
text = details.id,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = SecondaryAlpha)
)
}
)

ListItem(
modifier = Modifier.fillMaxWidth(),
colors = ListItemDefaults.colors(
containerColor = MaterialTheme.colorScheme.surface
),
headlineContent = {
Text(text = stringResource(Res.string.settings_app_details_title_name))
},
supportingContent = {
Text(
text = details.name,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = SecondaryAlpha)
)
}
)

ListItem(
modifier = Modifier.fillMaxWidth(),
colors = ListItemDefaults.colors(
containerColor = MaterialTheme.colorScheme.surface
),
headlineContent = {
Text(text = stringResource(Res.string.settings_app_details_title_version))
},
supportingContent = {
Text(
text = details.version,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = SecondaryAlpha)
)
}
)

if (details.isDebug) {
ListItem(
modifier = Modifier.fillMaxWidth(),
colors = ListItemDefaults.colors(
containerColor = MaterialTheme.colorScheme.surface
),
headlineContent = {
Text(text = stringResource(Res.string.settings_app_details_title_debug))
},
supportingContent = {
Text(
text = stringResource(Res.string.global_yes),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = SecondaryAlpha)
)
}
)
}

if (details.isPreRelease) {
ListItem(
modifier = Modifier.fillMaxWidth(),
colors = ListItemDefaults.colors(
containerColor = MaterialTheme.colorScheme.surface
),
headlineContent = {
Text(text = stringResource(Res.string.settings_app_details_title_pre_release))
},
supportingContent = {
Text(
text = stringResource(Res.string.global_yes),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = SecondaryAlpha)
)
}
)
}

ListItem(
modifier = Modifier.fillMaxWidth(),
colors = ListItemDefaults.colors(
containerColor = MaterialTheme.colorScheme.surface
),
headlineContent = {
Text(text = stringResource(Res.string.settings_app_details_title_build_time))
},
supportingContent = {
Text(
text = details.buildTime?.let { dateTimeFormatter.format(it) }
?: stringResource(Res.string.global_not_available),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = SecondaryAlpha)
)
}
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import org.jetbrains.compose.resources.stringResource
internal fun ColumnScope.SettingsAppGroup(
appVersion: String?,
sourceCodeUri: String?,
appDetailsEnabled: Boolean = false,
onOpenAppDetails: () -> Unit,
onOpenDependencyList: () -> Unit,
onOpenCollaboratorList: () -> Unit,
uriHandler: UriHandler = LocalUriHandler.current
Expand All @@ -41,7 +43,10 @@ internal fun ColumnScope.SettingsAppGroup(
)

ListItem(
modifier = Modifier.fillMaxWidth(),
modifier = Modifier.fillMaxWidth()
.clickable(enabled = appDetailsEnabled) {
onOpenAppDetails.invoke()
},
colors = ListItemDefaults.colors(
containerColor = MaterialTheme.colorScheme.background
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ internal fun SettingsBottomSheet(
is SettingsBottomSheetDestination.Collaborators -> CollaboratorContainerScreen(
modifier = Modifier.fillMaxWidth()
)

is SettingsBottomSheetDestination.AppInfo -> AppDetailsBottomSheetLayout(
modifier = Modifier.fillMaxWidth(),
details = destination.details
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.mooncloak.vpn.app.shared.feature.settings.model

import androidx.compose.runtime.Immutable
import kotlinx.datetime.Instant
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Immutable
@Serializable
public data class SettingsAppDetails public constructor(
@SerialName(value = "id") val id: String,
@SerialName(value = "name") val name: String,
@SerialName(value = "version") val version: String,
@SerialName(value = "debug") val isDebug: Boolean,
@SerialName(value = "pre_release") val isPreRelease: Boolean,
@SerialName(value = "build_time") val buildTime: Instant?
)
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.mooncloak.vpn.app.shared.feature.settings.model

import androidx.compose.runtime.Immutable
import kotlinx.datetime.Instant
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

Expand All @@ -23,6 +24,13 @@ internal sealed interface SettingsBottomSheetDestination {
@SerialName(value = "plan")
data object SelectPlan : SettingsBottomSheetDestination

@Immutable
@Serializable
@SerialName(value = "app_info")
data class AppInfo internal constructor(
val details: SettingsAppDetails
) : SettingsBottomSheetDestination

@Immutable
@Serializable
@SerialName(value = "collaborators")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ public interface AppClientInfo {
public val versionName: String
get() = "$version-$versionCode"

public val buildTime: Instant
get() = Instant.parse(SharedBuildConfig.appBuildTime)
public val buildTime: Instant?
get() = runCatching { Instant.parse(SharedBuildConfig.appBuildTime) }.getOrNull()

public val flavor: String?

Expand Down

0 comments on commit 57ad040

Please sign in to comment.