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
7 changes: 7 additions & 0 deletions app/src/main/java/com/example/theloop/OnboardingViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,23 @@ class OnboardingViewModel @Inject constructor(
private val _name = MutableStateFlow("")
val name: StateFlow<String> = _name.asStateFlow()

private val _nameError = MutableStateFlow<Int?>(null)
val nameError: StateFlow<Int?> = _nameError.asStateFlow()

fun onNameChange(newName: String) {
_name.value = newName
_nameError.value = null
}

fun saveName(): Boolean {
val isNameValid = name.value.isNotBlank()
if (isNameValid) {
_nameError.value = null
viewModelScope.launch {
userPreferencesRepository.saveUserName(name.value)
}
} else {
_nameError.value = R.string.error_name_blank
}
return isNameValid
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.health.connect.client.HealthConnectClient
Expand All @@ -28,6 +29,7 @@ fun OnboardingScreen(
val context = LocalContext.current
var currentStep by remember { mutableIntStateOf(0) }
val name by viewModel.name.collectAsState()
val nameError by viewModel.nameError.collectAsState()
val totalSteps = 5

// Permissions state
Expand Down Expand Up @@ -99,7 +101,6 @@ fun OnboardingScreen(
if (currentStep == 0) {
// Save Name
if (!viewModel.saveName()) {
Toast.makeText(context, "Name cannot be blank", Toast.LENGTH_SHORT).show()
return@Button
}
}
Expand Down Expand Up @@ -127,7 +128,7 @@ fun OnboardingScreen(
verticalArrangement = Arrangement.Center
) {
when (currentStep) {
0 -> WelcomeStep(name, viewModel::onNameChange)
0 -> WelcomeStep(name, nameError, viewModel::onNameChange)
1 -> PermissionStep(
title = "Enable Location",
description = "We need your location to show local weather.",
Expand Down Expand Up @@ -167,7 +168,7 @@ fun OnboardingScreen(
}

@Composable
fun WelcomeStep(name: String, onNameChange: (String) -> Unit) {
fun WelcomeStep(name: String, nameError: Int?, onNameChange: (String) -> Unit) {
Text("Welcome to The Loop", style = MaterialTheme.typography.displaySmall)
Spacer(Modifier.height(16.dp))
Text("Your daily dashboard for life.", style = MaterialTheme.typography.bodyLarge)
Expand All @@ -177,7 +178,9 @@ fun WelcomeStep(name: String, onNameChange: (String) -> Unit) {
onValueChange = onNameChange,
label = { Text("What's your name?") },
singleLine = true,
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth(),
isError = nameError != null,
supportingText = nameError?.let { { Text(stringResource(it)) } }
)
}

Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<string name="app_name">The Loop</string>

<!-- UI Strings -->
<string name="error_name_blank">Name cannot be blank</string>
<string name="select_temperature_unit">Select Temperature Unit</string>
<string name="cancel">Cancel</string>
<string name="unknown_location">Unknown Location</string>
Expand Down
58 changes: 58 additions & 0 deletions app/src/test/java/com/example/theloop/OnboardingViewModelTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.example.theloop

import androidx.test.core.app.ApplicationProvider
import com.example.theloop.data.repository.UserPreferencesRepository
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

@RunWith(RobolectricTestRunner::class)
@Config(sdk = [33])
class OnboardingViewModelTest {

private lateinit var viewModel: OnboardingViewModel
private lateinit var userPreferencesRepository: UserPreferencesRepository

@Before
fun setup() {
val context = ApplicationProvider.getApplicationContext<android.content.Context>()
userPreferencesRepository = UserPreferencesRepository(context)
viewModel = OnboardingViewModel(userPreferencesRepository)
}

@Test
fun saveName_withBlankName_setsErrorAndReturnsFalse() {
viewModel.onNameChange(" ")
val result = viewModel.saveName()

assertFalse(result)
assertEquals(R.string.error_name_blank, viewModel.nameError.value)
}

@Test
fun saveName_withValidName_clearsErrorAndReturnsTrue() {
viewModel.onNameChange("Valid Name")
val result = viewModel.saveName()

assertTrue(result)
assertNull(viewModel.nameError.value)
}

@Test
fun onNameChange_clearsError() {
// Set error first
viewModel.onNameChange("")
viewModel.saveName()
assertEquals(R.string.error_name_blank, viewModel.nameError.value)

// Change name
viewModel.onNameChange("New Name")
assertNull(viewModel.nameError.value)
}
}