Skip to content

Commit 1caf900

Browse files
committed
feat: Add settings screen with build information
This commit introduces a new "Settings" screen that displays build and environment information. The screen is accessible from the main recording interface and provides developers with key details about the current build. ### Key Changes: - **New Settings Screen:** - A `SettingsScreen` composable has been created, showing "Build Information" such as Environment, Build Type, API URL, and App Version. - This screen is conditionally displayed only for `debug` builds, controlled by `canShowDeveloperInfo`. - **Build Information Provider:** - A `BuildConfigProvider` interface has been added to the `core:domain` module to abstract away direct access to `BuildConfig`. - An `AppBuildConfigProvider` implementation is provided in the `app` module to supply `BuildConfig` values (version, build type, etc.) via dependency injection. - **Navigation:** - A floating action button with a settings icon has been added to the `RecordingScreen` to navigate to the new `SettingsScreen`. - The `app` module dependencies were updated to include the new `feature:settings` and `feature:settings:api` modules. - **Minor Refinements:** - The `API_BASE_URL` definition in `app/build.gradle.kts` was moved to be consistent with other `buildConfigField` declarations. - `consumeWindowInsets` has been removed from `MainActivity` to simplify layout handling.
1 parent 0d132f1 commit 1caf900

8 files changed

Lines changed: 235 additions & 21 deletions

File tree

app/build.gradle.kts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@ android {
1818
dimension = "environment"
1919
applicationIdSuffix = ".dev"
2020
versionNameSuffix = "-dev"
21-
buildConfigField("String", "API_BASE_URL", "\"https://dev-api.zencastr.com/\"")
2221
buildConfigField("String", "ENVIRONMENT", "\"development\"")
22+
buildConfigField("String", "API_BASE_URL", "\"https://dev-api.zencastr.com/\"")
2323
}
2424

2525
create("prod") {
2626
dimension = "environment"
27-
buildConfigField("String", "API_BASE_URL", "\"https://api.zencastr.com/\"")
2827
buildConfigField("String", "ENVIRONMENT", "\"production\"")
28+
buildConfigField("String", "API_BASE_URL", "\"https://api.zencastr.com/\"")
2929
}
3030
}
3131

@@ -96,6 +96,8 @@ dependencies {
9696
implementation(project(":feature:recording"))
9797
implementation(project(":feature:recording:api"))
9898
implementation(project(":feature:profile"))
99+
implementation(project(":feature:settings"))
100+
implementation(project(":feature:settings:api"))
99101

100102
// Firebase (using BOM for version management)
101103
implementation(platform(libs.firebase.bom))
@@ -111,4 +113,4 @@ dependencies {
111113
debugImplementation(libs.leakcanary)
112114
debugImplementation(libs.chucker)
113115
releaseImplementation(libs.chucker.no.op)
114-
}
116+
}

app/src/main/java/com/acksession/zencastr/MainActivity.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package com.acksession.zencastr
22

3+
import android.annotation.SuppressLint
34
import android.os.Bundle
45
import androidx.activity.ComponentActivity
56
import androidx.activity.compose.setContent
67
import androidx.activity.enableEdgeToEdge
7-
import androidx.compose.foundation.layout.consumeWindowInsets
88
import androidx.compose.material3.Scaffold
99
import androidx.compose.ui.Modifier
1010
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
@@ -40,10 +40,11 @@ class MainActivity : ComponentActivity() {
4040

4141
setContent {
4242
ZencastrTheme {
43-
Scaffold { paddingValues ->
43+
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
44+
Scaffold { _ ->
4445
NavDisplay(
4546
backStack = navigator.backStack,
46-
modifier = Modifier.consumeWindowInsets(paddingValues),
47+
modifier = Modifier,
4748
onBack = {
4849
// Handle back navigation
4950
if (!navigator.navigateBack()) {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.acksession.zencastr.config
2+
3+
import com.acksession.domain.config.BuildConfigProvider
4+
import com.acksession.zencastr.BuildConfig
5+
import javax.inject.Inject
6+
import javax.inject.Singleton
7+
8+
@Singleton
9+
class AppBuildConfigProvider @Inject constructor() : BuildConfigProvider {
10+
override val isDebug: Boolean = BuildConfig.DEBUG
11+
override val buildType: String = BuildConfig.BUILD_TYPE
12+
override val environment: String = BuildConfig.ENVIRONMENT
13+
override val versionName: String = BuildConfig.VERSION_NAME
14+
}

app/src/main/java/com/acksession/zencastr/di/AppConfigModule.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package com.acksession.zencastr.di
22

3+
import com.acksession.domain.config.BuildConfigProvider
34
import com.acksession.network.qualifier.ApiBaseUrl
45
import com.acksession.zencastr.BuildConfig
6+
import com.acksession.zencastr.config.AppBuildConfigProvider
7+
import dagger.Binds
58
import dagger.Module
69
import dagger.Provides
710
import dagger.hilt.InstallIn
@@ -25,3 +28,13 @@ object AppConfigModule {
2528
return BuildConfig.API_BASE_URL
2629
}
2730
}
31+
32+
@Suppress("unused")
33+
@Module
34+
@InstallIn(SingletonComponent::class)
35+
interface AppConfigBindingModule {
36+
37+
@Binds
38+
@Singleton
39+
fun bindBuildConfigProvider(impl: AppBuildConfigProvider): BuildConfigProvider
40+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.acksession.domain.config
2+
3+
/**
4+
* Provider for BuildConfig values.
5+
* This is injected to allow testing and avoid direct BuildConfig access.
6+
*/
7+
interface BuildConfigProvider {
8+
val isDebug: Boolean
9+
val buildType: String
10+
val environment: String
11+
val versionName: String
12+
}

feature/recording/src/main/java/com/acksession/feature/recording/RecordingScreen.kt

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ import android.widget.Toast
44
import androidx.camera.view.CameraController
55
import androidx.camera.view.LifecycleCameraController
66
import androidx.compose.foundation.layout.Box
7+
import androidx.compose.foundation.layout.Column
78
import androidx.compose.foundation.layout.consumeWindowInsets
89
import androidx.compose.foundation.layout.fillMaxSize
910
import androidx.compose.foundation.layout.padding
1011
import androidx.compose.material.icons.Icons
1112
import androidx.compose.material.icons.filled.Person
13+
import androidx.compose.material.icons.filled.Settings
1214
import androidx.compose.material3.FloatingActionButton
1315
import androidx.compose.material3.Icon
1416
import androidx.compose.material3.Scaffold
@@ -26,6 +28,7 @@ import androidx.compose.ui.unit.dp
2628
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
2729
import androidx.lifecycle.compose.LocalLifecycleOwner
2830
import com.acksession.feature.profile.api.navigateToProfile
31+
import com.acksession.feature.settings.api.navigateToSettings
2932
import com.acksession.navigation.Navigator
3033
import com.acksession.ui.utils.openAppSettings
3134

@@ -78,23 +81,35 @@ fun RecordingScreen(
7881
Scaffold(
7982
snackbarHost = { SnackbarHost(snackbarHostState) },
8083
floatingActionButton = {
81-
// Test navigation button - navigate to Profile with parameters
82-
FloatingActionButton(
83-
onClick = {
84-
navigator.navigateToProfile(
85-
userId = "USER_123",
86-
name = "Test User",
87-
role = "Video Creator"
88-
)
89-
},
90-
modifier = Modifier.padding(16.dp)
84+
Column(
85+
modifier = Modifier.padding(16.dp),
9186
) {
92-
Icon(
93-
imageVector = Icons.Default.Person,
94-
contentDescription = "Navigate to Profile"
95-
)
87+
FloatingActionButton(
88+
onClick = { navigator.navigateToSettings() },
89+
modifier = Modifier.padding(bottom = 16.dp),
90+
) {
91+
Icon(
92+
imageVector = Icons.Default.Settings,
93+
contentDescription = "Navigate to Settings",
94+
)
95+
}
96+
97+
FloatingActionButton(
98+
onClick = {
99+
navigator.navigateToProfile(
100+
userId = "USER_123",
101+
name = "Test User",
102+
role = "Video Creator",
103+
)
104+
},
105+
) {
106+
Icon(
107+
imageVector = Icons.Default.Person,
108+
contentDescription = "Navigate to Profile",
109+
)
110+
}
96111
}
97-
}
112+
},
98113
) { paddingValues ->
99114
RecordingPermissionGate(
100115
content = {
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package com.acksession.feature.settings
2+
3+
import androidx.compose.foundation.layout.Arrangement
4+
import androidx.compose.foundation.layout.Column
5+
import androidx.compose.foundation.layout.Row
6+
import androidx.compose.foundation.layout.fillMaxSize
7+
import androidx.compose.foundation.layout.fillMaxWidth
8+
import androidx.compose.foundation.layout.padding
9+
import androidx.compose.foundation.rememberScrollState
10+
import androidx.compose.foundation.verticalScroll
11+
import androidx.compose.material.icons.Icons
12+
import androidx.compose.material.icons.automirrored.filled.ArrowBack
13+
import androidx.compose.material3.Card
14+
import androidx.compose.material3.CardDefaults
15+
import androidx.compose.material3.ExperimentalMaterial3Api
16+
import androidx.compose.material3.Icon
17+
import androidx.compose.material3.IconButton
18+
import androidx.compose.material3.MaterialTheme
19+
import androidx.compose.material3.Scaffold
20+
import androidx.compose.material3.Text
21+
import androidx.compose.material3.TopAppBar
22+
import androidx.compose.material3.TopAppBarDefaults
23+
import androidx.compose.runtime.Composable
24+
import androidx.compose.runtime.collectAsState
25+
import androidx.compose.runtime.getValue
26+
import androidx.compose.ui.Modifier
27+
import androidx.compose.ui.text.font.FontWeight
28+
import androidx.compose.ui.unit.dp
29+
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
30+
31+
@OptIn(ExperimentalMaterial3Api::class)
32+
@Composable
33+
fun SettingsScreen(
34+
viewModel: SettingsViewModel = hiltViewModel(),
35+
) {
36+
val uiState by viewModel.uiState.collectAsState()
37+
38+
Scaffold(
39+
topBar = {
40+
TopAppBar(
41+
title = { Text("Settings") },
42+
navigationIcon = {
43+
IconButton(onClick = viewModel::navigateBack) {
44+
Icon(
45+
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
46+
contentDescription = "Navigate back",
47+
)
48+
}
49+
},
50+
colors = TopAppBarDefaults.topAppBarColors(
51+
containerColor = MaterialTheme.colorScheme.primaryContainer,
52+
titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
53+
),
54+
)
55+
},
56+
) { paddingValues ->
57+
Column(
58+
modifier = Modifier
59+
.fillMaxSize()
60+
.padding(paddingValues)
61+
.padding(16.dp)
62+
.verticalScroll(rememberScrollState()),
63+
verticalArrangement = Arrangement.spacedBy(16.dp),
64+
) {
65+
if (uiState.canShowDeveloperInfo) {
66+
Card(
67+
modifier = Modifier.fillMaxWidth(),
68+
colors = CardDefaults.cardColors(
69+
containerColor = MaterialTheme.colorScheme.surfaceVariant,
70+
),
71+
) {
72+
Column(
73+
modifier = Modifier
74+
.fillMaxWidth()
75+
.padding(16.dp),
76+
verticalArrangement = Arrangement.spacedBy(12.dp),
77+
) {
78+
Text(
79+
text = "Build Information",
80+
style = MaterialTheme.typography.titleMedium,
81+
fontWeight = FontWeight.Bold,
82+
)
83+
84+
InfoRow(label = "Environment", value = uiState.flavorEnvironment)
85+
InfoRow(label = "Build Type", value = uiState.buildType)
86+
InfoRow(label = "API URL", value = uiState.currentApiUrl)
87+
InfoRow(label = "Version", value = uiState.appVersion)
88+
}
89+
}
90+
}
91+
}
92+
}
93+
}
94+
95+
@Composable
96+
private fun InfoRow(label: String, value: String) {
97+
Row(
98+
modifier = Modifier.fillMaxWidth(),
99+
horizontalArrangement = Arrangement.SpaceBetween,
100+
) {
101+
Text(
102+
text = "$label:",
103+
style = MaterialTheme.typography.bodyMedium,
104+
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),
105+
)
106+
Text(
107+
text = value,
108+
style = MaterialTheme.typography.bodyMedium,
109+
fontWeight = FontWeight.Medium,
110+
)
111+
}
112+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.acksession.feature.settings
2+
3+
import androidx.lifecycle.ViewModel
4+
import com.acksession.domain.config.BuildConfigProvider
5+
import com.acksession.navigation.Navigator
6+
import com.acksession.network.qualifier.ApiBaseUrl
7+
import dagger.hilt.android.lifecycle.HiltViewModel
8+
import kotlinx.coroutines.flow.MutableStateFlow
9+
import kotlinx.coroutines.flow.StateFlow
10+
import kotlinx.coroutines.flow.asStateFlow
11+
import javax.inject.Inject
12+
13+
/**
14+
* UI state for Settings screen (read-only build information).
15+
*/
16+
data class SettingsUiState(
17+
val currentApiUrl: String = "",
18+
val flavorEnvironment: String = "",
19+
val buildType: String = "",
20+
val appVersion: String = "",
21+
val canShowDeveloperInfo: Boolean = false
22+
)
23+
24+
@HiltViewModel
25+
class SettingsViewModel @Inject constructor(
26+
private val navigator: Navigator,
27+
@param:ApiBaseUrl private val currentApiUrl: String,
28+
buildConfigProvider: BuildConfigProvider
29+
) : ViewModel() {
30+
31+
private val _uiState = MutableStateFlow(
32+
SettingsUiState(
33+
currentApiUrl = currentApiUrl,
34+
flavorEnvironment = buildConfigProvider.environment,
35+
buildType = buildConfigProvider.buildType,
36+
appVersion = buildConfigProvider.versionName,
37+
canShowDeveloperInfo = buildConfigProvider.isDebug
38+
)
39+
)
40+
val uiState: StateFlow<SettingsUiState> = _uiState.asStateFlow()
41+
42+
fun navigateBack() {
43+
navigator.navigateBack()
44+
}
45+
}

0 commit comments

Comments
 (0)