From a1012b322851a7702c7a09328b5601fbee0e7ea4 Mon Sep 17 00:00:00 2001 From: Harrishan Date: Wed, 18 Dec 2024 19:50:10 +0100 Subject: [PATCH 01/15] fix: remove notification and theme from settings - Removed notification and theme containers. - Removed notification and theme tags. - Updated unit tests for the UI. --- .../com/android/periodpals/resources/C.kt | 14 -- .../periodpals/ui/settings/SettingsScreen.kt | 196 ------------------ .../ui/settings/SettingsScreenTest.kt | 48 ----- 3 files changed, 258 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/resources/C.kt b/app/src/main/java/com/android/periodpals/resources/C.kt index 90c285407..04610f76d 100644 --- a/app/src/main/java/com/android/periodpals/resources/C.kt +++ b/app/src/main/java/com/android/periodpals/resources/C.kt @@ -173,21 +173,7 @@ object C { object SettingsScreen { const val SCREEN = "settingsScreen" - const val NOTIFICATIONS_CONTAINER = "notificationsContainer" - const val THEME_CONTAINER = "themeContainer" const val ACCOUNT_MANAGEMENT_CONTAINER = "accountManagementContainer" - const val NOTIFICATIONS_DESCRIPTION = "notificationDescription" - const val ORGANIC_DESCRIPTION = "organicDescription" - const val PALS_TEXT = "palsText" - const val PALS_SWITCH = "palsSwitch" - const val HORIZONTAL_DIVIDER = "horizontalDivider" - const val PADS_TEXT = "padsText" - const val PADS_SWITCH = "padsSwitch" - const val TAMPONS_TEXT = "tamponsText" - const val TAMPONS_SWITCH = "tamponsSwitch" - const val ORGANIC_TEXT = "organicText" - const val ORGANIC_SWITCH = "organicSwitch" - const val THEME_DROP_DOWN_MENU_BOX = "themeDropdownMenuBox" const val THEME_DROP_DOWN_MENU = "themeDropdownMenu" const val PASSWORD_TEXT = "passwordText" const val PASSWORD_ICON = "passwordIcon" diff --git a/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt b/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt index 3d7a11344..6207ffb94 100644 --- a/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt @@ -8,7 +8,6 @@ import android.widget.Toast import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize @@ -22,27 +21,17 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.Logout -import androidx.compose.material.icons.outlined.DarkMode import androidx.compose.material.icons.outlined.Delete import androidx.compose.material.icons.outlined.Key -import androidx.compose.material.icons.outlined.LightMode -import androidx.compose.material.icons.outlined.PhoneAndroid import androidx.compose.material.icons.outlined.SentimentVeryDissatisfied import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExposedDropdownMenuBox -import androidx.compose.material3.ExposedDropdownMenuDefaults -import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold -import androidx.compose.material3.Switch import androidx.compose.material3.Text -import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -60,9 +49,6 @@ import androidx.compose.ui.window.DialogProperties import com.android.periodpals.model.authentication.AuthenticationViewModel import com.android.periodpals.model.user.UserViewModel import com.android.periodpals.resources.C.Tag.SettingsScreen -import com.android.periodpals.resources.ComponentColor.getMenuItemColors -import com.android.periodpals.resources.ComponentColor.getMenuTextFieldColors -import com.android.periodpals.resources.ComponentColor.getSwitchColors import com.android.periodpals.resources.ComponentColor.getTertiaryCardColors import com.android.periodpals.ui.navigation.NavigationActions import com.android.periodpals.ui.navigation.Screen @@ -71,22 +57,6 @@ import com.android.periodpals.ui.theme.dimens private const val SCREEN_TITLE = "My Settings" -// Comments -private const val COMMENT_NOTIFICATIONS = "Notify me when a pal needs ..." -private const val COMMENT_ORGANIC = "Which are ..." - -// Notifications -private const val NOTIF_PALS = "Pals’ Notifications" -private const val NOTIF_PADS = "Pads" -private const val NOTIF_TAMPONS = "Tampons" -private const val NOTIF_ORGANIC = "Organic" - -// Themes -private const val THEME_LABEL = "Theme" -private const val THEME_SYSTEM = "System" -private const val THEME_LIGHT = "Light Mode" -private const val THEME_DARK = "Dark Mode" - // account management private const val ACCOUNT_PASSWORD = "Change Password" private const val ACCOUNT_SIGN_OUT = "Sign Out" @@ -95,13 +65,6 @@ private const val ACCOUNT_DELETE = "Delete Account" // Dialog private const val DIALOG_TEXT = "Are you sure you want to delete your account?" -// Dropdown choices -private val THEME_DROPDOWN_CHOICES = - listOf( - listOf(THEME_SYSTEM, Icons.Outlined.PhoneAndroid), - listOf(THEME_LIGHT, Icons.Outlined.LightMode), - listOf(THEME_DARK, Icons.Outlined.DarkMode)) - // Log messages private const val LOG_SETTINGS_TAG = "SettingsScreen" @@ -138,7 +101,6 @@ private const val TOAST_LOAD_DATA_FAILURE = "Failed loading user authentication * @param authenticationViewModel The ViewModel that handles authentication logic. * @param navigationActions The navigation actions that can be performed in the app. */ -@OptIn(ExperimentalMaterial3Api::class) @Composable fun SettingsScreen( userViewModel: UserViewModel, @@ -146,17 +108,6 @@ fun SettingsScreen( navigationActions: NavigationActions ) { - // notifications states - var receiveNotifications by remember { mutableStateOf(true) } - var padsNotifications by remember { mutableStateOf(true) } - var tamponsNotifications by remember { mutableStateOf(true) } - var organicNotifications by remember { mutableStateOf(true) } - - // theme states - var expanded by remember { mutableStateOf(false) } - var theme by remember { mutableStateOf(THEME_SYSTEM) } - var icon by remember { mutableStateOf(Icons.Outlined.PhoneAndroid) } - // delete account dialog state var showDialog by remember { mutableStateOf(false) } @@ -198,97 +149,6 @@ fun SettingsScreen( Arrangement.spacedBy(MaterialTheme.dimens.small2, Alignment.CenterVertically), ) { - // notification section - SettingsContainer(testTag = SettingsScreen.NOTIFICATIONS_CONTAINER) { - SettingsSwitchRow( - text = NOTIF_PALS, - isChecked = receiveNotifications, - onCheckedChange = { receiveNotifications = it }, - textTestTag = SettingsScreen.PALS_TEXT, - switchTestTag = SettingsScreen.PALS_SWITCH, - ) - HorizontalDivider( - color = MaterialTheme.colorScheme.outlineVariant, - modifier = Modifier.testTag(SettingsScreen.HORIZONTAL_DIVIDER)) - SettingsDescription( - text = COMMENT_NOTIFICATIONS, testTag = SettingsScreen.NOTIFICATIONS_DESCRIPTION) - SettingsSwitchRow( - text = NOTIF_PADS, - isChecked = receiveNotifications && padsNotifications, - onCheckedChange = { padsNotifications = it }, - textTestTag = SettingsScreen.PADS_TEXT, - switchTestTag = SettingsScreen.PADS_SWITCH) - SettingsSwitchRow( - text = NOTIF_TAMPONS, - isChecked = receiveNotifications && tamponsNotifications, - onCheckedChange = { tamponsNotifications = it }, - textTestTag = SettingsScreen.TAMPONS_TEXT, - switchTestTag = SettingsScreen.TAMPONS_SWITCH) - SettingsDescription(COMMENT_ORGANIC, SettingsScreen.ORGANIC_DESCRIPTION) - SettingsSwitchRow( - text = NOTIF_ORGANIC, - isChecked = receiveNotifications && organicNotifications, - onCheckedChange = { organicNotifications = it }, - textTestTag = SettingsScreen.ORGANIC_TEXT, - switchTestTag = SettingsScreen.ORGANIC_SWITCH) - } - - // theme section - SettingsContainer(testTag = SettingsScreen.THEME_CONTAINER) { - ExposedDropdownMenuBox( - modifier = Modifier.testTag(SettingsScreen.THEME_DROP_DOWN_MENU_BOX), - expanded = expanded, - onExpandedChange = { expanded = it }, - ) { - TextField( - modifier = Modifier.menuAnchor().fillMaxWidth().wrapContentHeight(), - textStyle = MaterialTheme.typography.labelLarge, - value = theme, - onValueChange = {}, - label = { Text(THEME_LABEL, style = MaterialTheme.typography.labelMedium) }, - singleLine = true, - readOnly = true, - leadingIcon = { Icon(icon, contentDescription = null) }, - trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, - colors = getMenuTextFieldColors(), - ) - ExposedDropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false }, - modifier = Modifier.wrapContentSize().testTag(SettingsScreen.THEME_DROP_DOWN_MENU), - containerColor = MaterialTheme.colorScheme.primaryContainer, - ) { - THEME_DROPDOWN_CHOICES.forEach { option -> - DropdownMenuItem( - modifier = Modifier.fillMaxWidth().wrapContentHeight(), - text = { - Text( - text = option[0] as String, - style = MaterialTheme.typography.labelLarge, - modifier = - Modifier.padding(top = MaterialTheme.dimens.small2).wrapContentHeight(), - color = MaterialTheme.colorScheme.onSurface, - ) - }, - onClick = { - theme = option[0] as String - icon = option[1] as ImageVector - expanded = false - }, - leadingIcon = { - Icon( - option[1] as ImageVector, - contentDescription = null, - ) - }, - colors = getMenuItemColors(), - contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding, - ) - } - } - } - } - // account management section SettingsContainer(testTag = SettingsScreen.ACCOUNT_MANAGEMENT_CONTAINER) { SettingsIconRow( @@ -364,62 +224,6 @@ private fun SettingsContainer(testTag: String, content: @Composable () -> Unit) } } -/** - * A composable function that displays a description in the settings screen. - * - * @param text the text to be displayed in the description. - * @param testTag the test tag for the description. - */ -@Composable -private fun SettingsDescription(text: String, testTag: String) { - Box(modifier = Modifier.fillMaxWidth().wrapContentHeight()) { - Text( - text, - textAlign = TextAlign.Start, - style = MaterialTheme.typography.labelMedium, - modifier = Modifier.fillMaxWidth().wrapContentHeight().testTag(testTag), - color = MaterialTheme.colorScheme.onSurface, - ) - } -} - -/** - * A composable function that displays a row with a switch in the settings screen. - * - * @param text The text to be displayed in the row. - * @param isChecked The state of the switch. - * @param onCheckedChange The function to be called when the switch is toggled. - * @param textTestTag The test tag for the text. - * @param switchTestTag The test tag for the switch. - */ -@Composable -private fun SettingsSwitchRow( - text: String, - isChecked: Boolean, - onCheckedChange: (Boolean) -> Unit, - textTestTag: String, - switchTestTag: String -) { - Row( - modifier = Modifier.fillMaxWidth().wrapContentHeight(), - horizontalArrangement = Arrangement.SpaceBetween) { - Text( - text, - modifier = - Modifier.padding(top = MaterialTheme.dimens.small2) - .wrapContentHeight() - .testTag(textTestTag), - style = MaterialTheme.typography.labelLarge, - color = MaterialTheme.colorScheme.onSurface) - Switch( - checked = isChecked, - onCheckedChange = onCheckedChange, - colors = getSwitchColors(), - modifier = Modifier.testTag(switchTestTag), - ) - } -} - /** * A composable function that displays a row with an icon in the settings screen. * diff --git a/app/src/test/java/com/android/periodpals/ui/settings/SettingsScreenTest.kt b/app/src/test/java/com/android/periodpals/ui/settings/SettingsScreenTest.kt index 56163cf8e..78718b716 100644 --- a/app/src/test/java/com/android/periodpals/ui/settings/SettingsScreenTest.kt +++ b/app/src/test/java/com/android/periodpals/ui/settings/SettingsScreenTest.kt @@ -68,44 +68,10 @@ class SettingsScreenTest { composeTestRule.onNodeWithTag(TopAppBar.EDIT_BUTTON).assertIsNotDisplayed() composeTestRule.onNodeWithTag(BottomNavigationMenu.BOTTOM_NAVIGATION_MENU).assertDoesNotExist() - composeTestRule - .onNodeWithTag(SettingsScreen.NOTIFICATIONS_CONTAINER) - .performScrollTo() - .assertIsDisplayed() - composeTestRule - .onNodeWithTag(SettingsScreen.THEME_CONTAINER) - .performScrollTo() - .assertIsDisplayed() composeTestRule .onNodeWithTag(SettingsScreen.ACCOUNT_MANAGEMENT_CONTAINER) .performScrollTo() .assertIsDisplayed() - composeTestRule - .onNodeWithTag(SettingsScreen.NOTIFICATIONS_DESCRIPTION) - .performScrollTo() - .assertIsDisplayed() - composeTestRule.onNodeWithTag(SettingsScreen.PALS_TEXT).performScrollTo().assertIsDisplayed() - composeTestRule.onNodeWithTag(SettingsScreen.PALS_SWITCH).performScrollTo().assertIsDisplayed() - composeTestRule - .onNodeWithTag(SettingsScreen.HORIZONTAL_DIVIDER) - .performScrollTo() - .assertIsDisplayed() - composeTestRule.onNodeWithTag(SettingsScreen.PADS_TEXT).performScrollTo().assertIsDisplayed() - composeTestRule.onNodeWithTag(SettingsScreen.PADS_SWITCH).performScrollTo().assertIsDisplayed() - composeTestRule.onNodeWithTag(SettingsScreen.TAMPONS_TEXT).performScrollTo().assertIsDisplayed() - composeTestRule - .onNodeWithTag(SettingsScreen.TAMPONS_SWITCH) - .performScrollTo() - .assertIsDisplayed() - composeTestRule.onNodeWithTag(SettingsScreen.ORGANIC_TEXT).performScrollTo().assertIsDisplayed() - composeTestRule - .onNodeWithTag(SettingsScreen.ORGANIC_SWITCH) - .performScrollTo() - .assertIsDisplayed() - composeTestRule - .onNodeWithTag(SettingsScreen.THEME_DROP_DOWN_MENU_BOX) - .performScrollTo() - .assertIsDisplayed() composeTestRule .onNodeWithTag(SettingsScreen.PASSWORD_TEXT) .performScrollTo() @@ -168,20 +134,6 @@ class SettingsScreenTest { verify(navigationActions).goBack() } - @Test - fun performClickOnDropDownMenu() { - composeTestRule.setContent { - SettingsScreen(userViewModel, authenticationViewModel, navigationActions) - } - - composeTestRule - .onNodeWithTag(SettingsScreen.THEME_DROP_DOWN_MENU_BOX) - .performScrollTo() - .performClick() - - composeTestRule.onNodeWithTag(SettingsScreen.THEME_DROP_DOWN_MENU).performClick() - } - @Test fun notDeleteAccountButtonDismissDialog() { composeTestRule.setContent { From d95b555281eeea9e0a8f2f9f52b651fc0fa41179 Mon Sep 17 00:00:00 2001 From: Harrishan Date: Wed, 18 Dec 2024 20:59:21 +0100 Subject: [PATCH 02/15] feat: add preferred distance option --- .../android/periodpals/ui/settings/SettingsScreen.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt b/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt index 6207ffb94..c2b0ca64c 100644 --- a/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt @@ -34,6 +34,7 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -50,10 +51,12 @@ import com.android.periodpals.model.authentication.AuthenticationViewModel import com.android.periodpals.model.user.UserViewModel import com.android.periodpals.resources.C.Tag.SettingsScreen import com.android.periodpals.resources.ComponentColor.getTertiaryCardColors +import com.android.periodpals.ui.components.SliderMenu import com.android.periodpals.ui.navigation.NavigationActions import com.android.periodpals.ui.navigation.Screen import com.android.periodpals.ui.navigation.TopAppBar import com.android.periodpals.ui.theme.dimens +import kotlin.math.roundToInt private const val SCREEN_TITLE = "My Settings" @@ -79,7 +82,6 @@ private const val LOG_SETTINGS_SUCCESS_LOAD_DATA = private const val LOG_SETTINGS_FAILURE_LOAD_DATA = "failed to load user data, can't delete the user" // Toast messages - private const val TOAST_SETTINGS_SUCCESS_SIGN_OUT = "Sign out successful" private const val TOAST_SETTINGS_FAILURE_SIGN_OUT = "Failed to sign out" @@ -88,6 +90,8 @@ private const val TOAST_SETTINGS_FAILURE_DELETE = "Failed to delete account" private const val TOAST_LOAD_DATA_FAILURE = "Failed loading user authentication data" +private const val DEFAULT_RADIUS = 500F + /** * A composable function that displays the Settings screen, where users can manage their * notifications, themes, and account settings. @@ -111,6 +115,8 @@ fun SettingsScreen( // delete account dialog state var showDialog by remember { mutableStateOf(false) } + var sliderPosition by remember { mutableFloatStateOf(DEFAULT_RADIUS) } + val context = LocalContext.current // delete account dialog logic @@ -195,6 +201,8 @@ fun SettingsScreen( color = MaterialTheme.colorScheme.error, ) } + + SliderMenu(sliderPosition) { sliderPosition = (it / 100).roundToInt() * 100f } } } } From f207ef675456cc6d6424b4c1297c52356c8afad0 Mon Sep 17 00:00:00 2001 From: Harrishan Date: Thu, 19 Dec 2024 16:56:46 +0100 Subject: [PATCH 03/15] style: put preferred distance option above account management container with an explanatory text --- .../periodpals/ui/settings/SettingsScreen.kt | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt b/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt index 538a77506..cad7e8ea8 100644 --- a/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt @@ -50,6 +50,7 @@ import androidx.compose.ui.window.DialogProperties import com.android.periodpals.R import com.android.periodpals.model.authentication.AuthenticationViewModel import com.android.periodpals.model.user.UserViewModel +import com.android.periodpals.resources.C.Tag.ProfileScreens.CreateProfileScreen import com.android.periodpals.resources.C.Tag.SettingsScreen import com.android.periodpals.resources.ComponentColor.getTertiaryCardColors import com.android.periodpals.ui.components.SliderMenu @@ -86,9 +87,9 @@ fun SettingsScreen( // delete account dialog state var showDialog by remember { mutableStateOf(false) } - val context = LocalContext.current + val context = LocalContext.current - var sliderPosition by remember { mutableFloatStateOf(DEFAULT_RADIUS) } + var sliderPosition by remember { mutableFloatStateOf(DEFAULT_RADIUS) } // delete account dialog logic if (showDialog) { @@ -125,6 +126,18 @@ fun SettingsScreen( verticalArrangement = Arrangement.spacedBy(MaterialTheme.dimens.small2, Alignment.CenterVertically), ) { + SliderMenu(sliderPosition) { sliderPosition = (it / 100).roundToInt() * 100f } + + Text( + text = context.getString(R.string.create_profile_radius_explanation_text), + style = MaterialTheme.typography.labelMedium, + modifier = + Modifier.wrapContentHeight() + .fillMaxWidth() + .testTag(CreateProfileScreen.FILTER_RADIUS_EXPLANATION_TEXT) + .padding(top = MaterialTheme.dimens.small2), + textAlign = TextAlign.Center, + ) // account management section SettingsContainer(testTag = SettingsScreen.ACCOUNT_MANAGEMENT_CONTAINER) { @@ -176,8 +189,6 @@ fun SettingsScreen( color = MaterialTheme.colorScheme.error, ) } - - SliderMenu(sliderPosition) { sliderPosition = (it / 100).roundToInt() * 100f } } } } From 2a4ec4db1d8c2310a52a45e8bd46bdbd2e312a17 Mon Sep 17 00:00:00 2001 From: Harrishan Date: Thu, 19 Dec 2024 17:48:23 +0100 Subject: [PATCH 04/15] fix: save preferred distance option in user data --- .../periodpals/ui/settings/SettingsScreen.kt | 124 ++++++++++++------ 1 file changed, 83 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt b/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt index cad7e8ea8..f0833f830 100644 --- a/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt @@ -49,6 +49,7 @@ import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import com.android.periodpals.R import com.android.periodpals.model.authentication.AuthenticationViewModel +import com.android.periodpals.model.user.User import com.android.periodpals.model.user.UserViewModel import com.android.periodpals.resources.C.Tag.ProfileScreens.CreateProfileScreen import com.android.periodpals.resources.C.Tag.SettingsScreen @@ -63,7 +64,6 @@ import kotlin.math.roundToInt // Log messages private const val LOG_SETTINGS_TAG = "SettingsScreen" -private const val DEFAULT_RADIUS = 500F /** * A composable function that displays the Settings screen, where users can manage their * notifications, themes, and account settings. @@ -89,7 +89,7 @@ fun SettingsScreen( val context = LocalContext.current - var sliderPosition by remember { mutableFloatStateOf(DEFAULT_RADIUS) } + var sliderPosition by remember { mutableFloatStateOf(userViewModel.user.value!!.preferredDistance.toFloat()) } // delete account dialog logic if (showDialog) { @@ -102,7 +102,9 @@ fun SettingsScreen( } Scaffold( - modifier = Modifier.fillMaxSize().testTag(SettingsScreen.SCREEN), + modifier = Modifier + .fillMaxSize() + .testTag(SettingsScreen.SCREEN), topBar = { TopAppBar( title = context.getString(R.string.settings_screen_title), @@ -115,27 +117,51 @@ fun SettingsScreen( ) { paddingValues -> Column( modifier = - Modifier.fillMaxSize() - .padding(paddingValues) - .padding( - horizontal = MaterialTheme.dimens.medium3, - vertical = MaterialTheme.dimens.small3, - ) - .verticalScroll(rememberScrollState()), + Modifier + .fillMaxSize() + .padding(paddingValues) + .padding( + horizontal = MaterialTheme.dimens.medium3, + vertical = MaterialTheme.dimens.small3, + ) + .verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(MaterialTheme.dimens.small2, Alignment.CenterVertically), ) { - SliderMenu(sliderPosition) { sliderPosition = (it / 100).roundToInt() * 100f } + SliderMenu(sliderPosition) { + sliderPosition = (it / 100).roundToInt() * 100f + + userViewModel.user.value?.let { user -> + val newUser = User( + name = user.name, + dob = user.dob, + description = user.description, + imageUrl = user.imageUrl, + preferredDistance = sliderPosition.toInt(), + ) + + userViewModel.saveUser( + newUser, + onSuccess = { + Log.d(LOG_SETTINGS_TAG, "User updated successfully") + }, + onFailure = { + Log.d(LOG_SETTINGS_TAG, "Failed to update user") + } + ) + } + } Text( text = context.getString(R.string.create_profile_radius_explanation_text), style = MaterialTheme.typography.labelMedium, modifier = - Modifier.wrapContentHeight() - .fillMaxWidth() - .testTag(CreateProfileScreen.FILTER_RADIUS_EXPLANATION_TEXT) - .padding(top = MaterialTheme.dimens.small2), + Modifier + .wrapContentHeight() + .fillMaxWidth() + .testTag(CreateProfileScreen.FILTER_RADIUS_EXPLANATION_TEXT) + .padding(top = MaterialTheme.dimens.small2), textAlign = TextAlign.Center, ) @@ -203,14 +229,16 @@ fun SettingsScreen( private fun SettingsContainer(testTag: String, content: @Composable () -> Unit) { Column( modifier = - Modifier.background( - MaterialTheme.colorScheme.surfaceContainerLow, MaterialTheme.shapes.medium) - .padding( - horizontal = MaterialTheme.dimens.medium1, - vertical = MaterialTheme.dimens.small2, - ) - .fillMaxSize() - .testTag(testTag), + Modifier + .background( + MaterialTheme.colorScheme.surfaceContainerLow, MaterialTheme.shapes.medium + ) + .padding( + horizontal = MaterialTheme.dimens.medium1, + vertical = MaterialTheme.dimens.small2, + ) + .fillMaxSize() + .testTag(testTag), verticalArrangement = Arrangement.spacedBy(MaterialTheme.dimens.small2, Alignment.CenterVertically), ) { @@ -238,17 +266,23 @@ private fun SettingsIconRow( color: Color = MaterialTheme.colorScheme.onSurface ) { Row( - modifier = Modifier.fillMaxWidth().wrapContentHeight(), + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight(), horizontalArrangement = Arrangement.SpaceBetween) { Text( text, style = MaterialTheme.typography.labelLarge, - modifier = Modifier.wrapContentHeight().testTag(textTestTag), + modifier = Modifier + .wrapContentHeight() + .testTag(textTestTag), color = color) Icon( icon, contentDescription = null, - modifier = Modifier.clickable { onClick() }.testTag(iconTestTag), + modifier = Modifier + .clickable { onClick() } + .testTag(iconTestTag), tint = color) } } @@ -273,33 +307,39 @@ private fun DeleteAccountDialog( properties = DialogProperties(usePlatformDefaultWidth = false)) { Card( modifier = - Modifier.fillMaxWidth() - .padding( - horizontal = MaterialTheme.dimens.medium3, - vertical = MaterialTheme.dimens.small3, - ) - .testTag(SettingsScreen.DELETE_ACCOUNT_CARD) - .wrapContentHeight(), + Modifier + .fillMaxWidth() + .padding( + horizontal = MaterialTheme.dimens.medium3, + vertical = MaterialTheme.dimens.small3, + ) + .testTag(SettingsScreen.DELETE_ACCOUNT_CARD) + .wrapContentHeight(), shape = RoundedCornerShape(size = MaterialTheme.dimens.cardRoundedSize), colors = getTertiaryCardColors(), elevation = CardDefaults.cardElevation(defaultElevation = MaterialTheme.dimens.cardElevation), ) { Column( - modifier = Modifier.wrapContentSize().padding(MaterialTheme.dimens.small2), + modifier = Modifier + .wrapContentSize() + .padding(MaterialTheme.dimens.small2), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(MaterialTheme.dimens.small2, Alignment.CenterVertically), ) { Icon( modifier = - Modifier.size(MaterialTheme.dimens.iconSize) - .testTag(SettingsScreen.CARD_EMOJI_ICON), + Modifier + .size(MaterialTheme.dimens.iconSize) + .testTag(SettingsScreen.CARD_EMOJI_ICON), imageVector = Icons.Outlined.SentimentVeryDissatisfied, contentDescription = "Account Deletion Emoji", ) Text( - modifier = Modifier.wrapContentSize().testTag(SettingsScreen.CARD_TEXT), + modifier = Modifier + .wrapContentSize() + .testTag(SettingsScreen.CARD_TEXT), text = context.getString(R.string.settings_dialog_text), style = MaterialTheme.typography.bodyMedium, textAlign = TextAlign.Center, @@ -357,8 +397,9 @@ private fun DeleteAccountDialog( containerColor = MaterialTheme.colorScheme.error, contentColor = MaterialTheme.colorScheme.onError), modifier = - Modifier.padding(MaterialTheme.dimens.small2) - .testTag(SettingsScreen.DELETE_BUTTON)) { + Modifier + .padding(MaterialTheme.dimens.small2) + .testTag(SettingsScreen.DELETE_BUTTON)) { Text( "Yes", style = MaterialTheme.typography.labelLarge, @@ -371,8 +412,9 @@ private fun DeleteAccountDialog( containerColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.onPrimary), modifier = - Modifier.padding(MaterialTheme.dimens.small2) - .testTag(SettingsScreen.NOT_DELETE_BUTTON)) { + Modifier + .padding(MaterialTheme.dimens.small2) + .testTag(SettingsScreen.NOT_DELETE_BUTTON)) { Text( "No", style = MaterialTheme.typography.labelLarge, From 90c7a400508370642b04082f71c4b5a67baf93ac Mon Sep 17 00:00:00 2001 From: Harrishan Date: Thu, 19 Dec 2024 17:54:45 +0100 Subject: [PATCH 05/15] fix: ensure that `sliderPosition` does not receive a null value --- .../periodpals/ui/settings/SettingsScreen.kt | 140 ++++++++---------- 1 file changed, 61 insertions(+), 79 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt b/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt index f0833f830..90c4b653c 100644 --- a/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt @@ -89,7 +89,13 @@ fun SettingsScreen( val context = LocalContext.current - var sliderPosition by remember { mutableFloatStateOf(userViewModel.user.value!!.preferredDistance.toFloat()) } + var sliderPosition by remember { + if (userViewModel.user.value == null) { + mutableFloatStateOf(500f) + } else { + mutableFloatStateOf(userViewModel.user.value!!.preferredDistance.toFloat()) + } + } // delete account dialog logic if (showDialog) { @@ -102,9 +108,7 @@ fun SettingsScreen( } Scaffold( - modifier = Modifier - .fillMaxSize() - .testTag(SettingsScreen.SCREEN), + modifier = Modifier.fillMaxSize().testTag(SettingsScreen.SCREEN), topBar = { TopAppBar( title = context.getString(R.string.settings_screen_title), @@ -117,51 +121,45 @@ fun SettingsScreen( ) { paddingValues -> Column( modifier = - Modifier - .fillMaxSize() - .padding(paddingValues) - .padding( - horizontal = MaterialTheme.dimens.medium3, - vertical = MaterialTheme.dimens.small3, - ) - .verticalScroll(rememberScrollState()), + Modifier.fillMaxSize() + .padding(paddingValues) + .padding( + horizontal = MaterialTheme.dimens.medium3, + vertical = MaterialTheme.dimens.small3, + ) + .verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(MaterialTheme.dimens.small2, Alignment.CenterVertically), ) { - SliderMenu(sliderPosition) { - sliderPosition = (it / 100).roundToInt() * 100f + SliderMenu(sliderPosition) { + sliderPosition = (it / 100).roundToInt() * 100f - userViewModel.user.value?.let { user -> - val newUser = User( - name = user.name, - dob = user.dob, - description = user.description, - imageUrl = user.imageUrl, - preferredDistance = sliderPosition.toInt(), - ) + userViewModel.user.value?.let { user -> + val newUser = + User( + name = user.name, + dob = user.dob, + description = user.description, + imageUrl = user.imageUrl, + preferredDistance = sliderPosition.toInt(), + ) - userViewModel.saveUser( - newUser, - onSuccess = { - Log.d(LOG_SETTINGS_TAG, "User updated successfully") - }, - onFailure = { - Log.d(LOG_SETTINGS_TAG, "Failed to update user") - } - ) - } + userViewModel.saveUser( + newUser, + onSuccess = { Log.d(LOG_SETTINGS_TAG, "User updated successfully") }, + onFailure = { Log.d(LOG_SETTINGS_TAG, "Failed to update user") }) } + } Text( text = context.getString(R.string.create_profile_radius_explanation_text), style = MaterialTheme.typography.labelMedium, modifier = - Modifier - .wrapContentHeight() - .fillMaxWidth() - .testTag(CreateProfileScreen.FILTER_RADIUS_EXPLANATION_TEXT) - .padding(top = MaterialTheme.dimens.small2), + Modifier.wrapContentHeight() + .fillMaxWidth() + .testTag(CreateProfileScreen.FILTER_RADIUS_EXPLANATION_TEXT) + .padding(top = MaterialTheme.dimens.small2), textAlign = TextAlign.Center, ) @@ -229,16 +227,14 @@ fun SettingsScreen( private fun SettingsContainer(testTag: String, content: @Composable () -> Unit) { Column( modifier = - Modifier - .background( - MaterialTheme.colorScheme.surfaceContainerLow, MaterialTheme.shapes.medium - ) - .padding( - horizontal = MaterialTheme.dimens.medium1, - vertical = MaterialTheme.dimens.small2, - ) - .fillMaxSize() - .testTag(testTag), + Modifier.background( + MaterialTheme.colorScheme.surfaceContainerLow, MaterialTheme.shapes.medium) + .padding( + horizontal = MaterialTheme.dimens.medium1, + vertical = MaterialTheme.dimens.small2, + ) + .fillMaxSize() + .testTag(testTag), verticalArrangement = Arrangement.spacedBy(MaterialTheme.dimens.small2, Alignment.CenterVertically), ) { @@ -266,23 +262,17 @@ private fun SettingsIconRow( color: Color = MaterialTheme.colorScheme.onSurface ) { Row( - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight(), + modifier = Modifier.fillMaxWidth().wrapContentHeight(), horizontalArrangement = Arrangement.SpaceBetween) { Text( text, style = MaterialTheme.typography.labelLarge, - modifier = Modifier - .wrapContentHeight() - .testTag(textTestTag), + modifier = Modifier.wrapContentHeight().testTag(textTestTag), color = color) Icon( icon, contentDescription = null, - modifier = Modifier - .clickable { onClick() } - .testTag(iconTestTag), + modifier = Modifier.clickable { onClick() }.testTag(iconTestTag), tint = color) } } @@ -307,39 +297,33 @@ private fun DeleteAccountDialog( properties = DialogProperties(usePlatformDefaultWidth = false)) { Card( modifier = - Modifier - .fillMaxWidth() - .padding( - horizontal = MaterialTheme.dimens.medium3, - vertical = MaterialTheme.dimens.small3, - ) - .testTag(SettingsScreen.DELETE_ACCOUNT_CARD) - .wrapContentHeight(), + Modifier.fillMaxWidth() + .padding( + horizontal = MaterialTheme.dimens.medium3, + vertical = MaterialTheme.dimens.small3, + ) + .testTag(SettingsScreen.DELETE_ACCOUNT_CARD) + .wrapContentHeight(), shape = RoundedCornerShape(size = MaterialTheme.dimens.cardRoundedSize), colors = getTertiaryCardColors(), elevation = CardDefaults.cardElevation(defaultElevation = MaterialTheme.dimens.cardElevation), ) { Column( - modifier = Modifier - .wrapContentSize() - .padding(MaterialTheme.dimens.small2), + modifier = Modifier.wrapContentSize().padding(MaterialTheme.dimens.small2), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(MaterialTheme.dimens.small2, Alignment.CenterVertically), ) { Icon( modifier = - Modifier - .size(MaterialTheme.dimens.iconSize) - .testTag(SettingsScreen.CARD_EMOJI_ICON), + Modifier.size(MaterialTheme.dimens.iconSize) + .testTag(SettingsScreen.CARD_EMOJI_ICON), imageVector = Icons.Outlined.SentimentVeryDissatisfied, contentDescription = "Account Deletion Emoji", ) Text( - modifier = Modifier - .wrapContentSize() - .testTag(SettingsScreen.CARD_TEXT), + modifier = Modifier.wrapContentSize().testTag(SettingsScreen.CARD_TEXT), text = context.getString(R.string.settings_dialog_text), style = MaterialTheme.typography.bodyMedium, textAlign = TextAlign.Center, @@ -397,9 +381,8 @@ private fun DeleteAccountDialog( containerColor = MaterialTheme.colorScheme.error, contentColor = MaterialTheme.colorScheme.onError), modifier = - Modifier - .padding(MaterialTheme.dimens.small2) - .testTag(SettingsScreen.DELETE_BUTTON)) { + Modifier.padding(MaterialTheme.dimens.small2) + .testTag(SettingsScreen.DELETE_BUTTON)) { Text( "Yes", style = MaterialTheme.typography.labelLarge, @@ -412,9 +395,8 @@ private fun DeleteAccountDialog( containerColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.onPrimary), modifier = - Modifier - .padding(MaterialTheme.dimens.small2) - .testTag(SettingsScreen.NOT_DELETE_BUTTON)) { + Modifier.padding(MaterialTheme.dimens.small2) + .testTag(SettingsScreen.NOT_DELETE_BUTTON)) { Text( "No", style = MaterialTheme.typography.labelLarge, From e98e705920b0119d0a6c822ca73221268b92bbe8 Mon Sep 17 00:00:00 2001 From: Harrishan Date: Thu, 19 Dec 2024 18:19:33 +0100 Subject: [PATCH 06/15] style : add padding to UI components --- .../periodpals/ui/settings/SettingsScreen.kt | 73 ++++++++++++------- 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt b/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt index 90c4b653c..a643796ba 100644 --- a/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt @@ -130,38 +130,57 @@ fun SettingsScreen( .verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = - Arrangement.spacedBy(MaterialTheme.dimens.small2, Alignment.CenterVertically), + Arrangement.spacedBy(MaterialTheme.dimens.small3, Alignment.CenterVertically), ) { - SliderMenu(sliderPosition) { - sliderPosition = (it / 100).roundToInt() * 100f - userViewModel.user.value?.let { user -> - val newUser = - User( - name = user.name, - dob = user.dob, - description = user.description, - imageUrl = user.imageUrl, - preferredDistance = sliderPosition.toInt(), - ) + // Remark Section + SettingsContainer(testTag = "Remark Section") { + Text( + text = + "To enable/disable notifications or location, please go to your phone's settings", + style = MaterialTheme.typography.labelMedium, + modifier = + Modifier.wrapContentHeight() + .fillMaxWidth() + .testTag(CreateProfileScreen.FILTER_RADIUS_EXPLANATION_TEXT) + .padding(top = MaterialTheme.dimens.small2), + textAlign = TextAlign.Center, + ) + } + + // Slider Section + SettingsContainer(testTag = "Settings Container") { + SliderMenu(sliderPosition) { + sliderPosition = (it / 100).roundToInt() * 100f + + userViewModel.user.value?.let { user -> + val newUser = + User( + name = user.name, + dob = user.dob, + description = user.description, + imageUrl = user.imageUrl, + preferredDistance = sliderPosition.toInt(), + ) - userViewModel.saveUser( - newUser, - onSuccess = { Log.d(LOG_SETTINGS_TAG, "User updated successfully") }, - onFailure = { Log.d(LOG_SETTINGS_TAG, "Failed to update user") }) + userViewModel.saveUser( + newUser, + onSuccess = { Log.d(LOG_SETTINGS_TAG, "User updated successfully") }, + onFailure = { Log.d(LOG_SETTINGS_TAG, "Failed to update user") }) + } } - } - Text( - text = context.getString(R.string.create_profile_radius_explanation_text), - style = MaterialTheme.typography.labelMedium, - modifier = - Modifier.wrapContentHeight() - .fillMaxWidth() - .testTag(CreateProfileScreen.FILTER_RADIUS_EXPLANATION_TEXT) - .padding(top = MaterialTheme.dimens.small2), - textAlign = TextAlign.Center, - ) + Text( + text = context.getString(R.string.create_profile_radius_explanation_text), + style = MaterialTheme.typography.labelMedium, + modifier = + Modifier.wrapContentHeight() + .fillMaxWidth() + .testTag(CreateProfileScreen.FILTER_RADIUS_EXPLANATION_TEXT) + .padding(top = MaterialTheme.dimens.small2), + textAlign = TextAlign.Center, + ) + } // account management section SettingsContainer(testTag = SettingsScreen.ACCOUNT_MANAGEMENT_CONTAINER) { From 406e831aea10745938f2321d38f9edd1d2b62b1c Mon Sep 17 00:00:00 2001 From: Harrishan Date: Thu, 19 Dec 2024 18:30:45 +0100 Subject: [PATCH 07/15] style: add C tags to new settings components --- app/src/main/java/com/android/periodpals/resources/C.kt | 3 +++ .../com/android/periodpals/ui/settings/SettingsScreen.kt | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/resources/C.kt b/app/src/main/java/com/android/periodpals/resources/C.kt index 5fb06a3f1..6468fdf48 100644 --- a/app/src/main/java/com/android/periodpals/resources/C.kt +++ b/app/src/main/java/com/android/periodpals/resources/C.kt @@ -173,6 +173,9 @@ object C { object SettingsScreen { const val SCREEN = "settingsScreen" + const val REMARK_CONTAINER = "remarkContainer" + const val REMARK_TEXT = "remarkText" + const val SETTINGS_CONTAINER = "settingsContainer" const val ACCOUNT_MANAGEMENT_CONTAINER = "accountManagementContainer" const val THEME_DROP_DOWN_MENU = "themeDropdownMenu" const val PASSWORD_TEXT = "passwordText" diff --git a/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt b/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt index a643796ba..470959eda 100644 --- a/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt @@ -134,7 +134,7 @@ fun SettingsScreen( ) { // Remark Section - SettingsContainer(testTag = "Remark Section") { + SettingsContainer(testTag = SettingsScreen.REMARK_CONTAINER) { Text( text = "To enable/disable notifications or location, please go to your phone's settings", @@ -142,14 +142,14 @@ fun SettingsScreen( modifier = Modifier.wrapContentHeight() .fillMaxWidth() - .testTag(CreateProfileScreen.FILTER_RADIUS_EXPLANATION_TEXT) + .testTag(SettingsScreen.REMARK_TEXT) .padding(top = MaterialTheme.dimens.small2), textAlign = TextAlign.Center, ) } // Slider Section - SettingsContainer(testTag = "Settings Container") { + SettingsContainer(testTag = SettingsScreen.SETTINGS_CONTAINER) { SliderMenu(sliderPosition) { sliderPosition = (it / 100).roundToInt() * 100f From 1ac42a58c92f03245b226c7584dbf8032afc2ea4 Mon Sep 17 00:00:00 2001 From: Harrishan Date: Thu, 19 Dec 2024 18:38:27 +0100 Subject: [PATCH 08/15] test: fix test by returning value for `userViewModel.user` --- .../ui/settings/SettingsScreenTest.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/app/src/test/java/com/android/periodpals/ui/settings/SettingsScreenTest.kt b/app/src/test/java/com/android/periodpals/ui/settings/SettingsScreenTest.kt index d6805d7aa..d76237aaf 100644 --- a/app/src/test/java/com/android/periodpals/ui/settings/SettingsScreenTest.kt +++ b/app/src/test/java/com/android/periodpals/ui/settings/SettingsScreenTest.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.test.performScrollTo import com.android.periodpals.R import com.android.periodpals.model.authentication.AuthenticationViewModel import com.android.periodpals.model.user.AuthenticationUserData +import com.android.periodpals.model.user.User import com.android.periodpals.model.user.UserViewModel import com.android.periodpals.resources.C.Tag.BottomNavigationMenu import com.android.periodpals.resources.C.Tag.SettingsScreen @@ -41,6 +42,21 @@ class SettingsScreenTest { companion object { private val userData = mutableStateOf(AuthenticationUserData("uid", "email@epfl.com")) + + private val name = "John Doe" + private val imageUrl = "https://example.com" + private val description = "A short description" + private val dob = "01/01/2000" + private val preferredDistance = 500 + private val userState = + mutableStateOf( + User( + name = name, + imageUrl = imageUrl, + description = description, + dob = dob, + preferredDistance = preferredDistance, + )) } @Before @@ -50,6 +66,7 @@ class SettingsScreenTest { userViewModel = mock(UserViewModel::class.java) `when`(navigationActions.currentRoute()).thenReturn(Screen.SETTINGS) + `when`(userViewModel.user).thenReturn(userState) } @Test From 8b729851ec6d7043bffc169b4681e0c1b0977198 Mon Sep 17 00:00:00 2001 From: Harrishan Date: Thu, 19 Dec 2024 18:53:27 +0100 Subject: [PATCH 09/15] fix: log out the user before switching screen --- .../periodpals/ui/settings/SettingsScreen.kt | 55 ++++++++++--------- .../ui/settings/SettingsScreenTest.kt | 8 +++ 2 files changed, 38 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt b/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt index 470959eda..770c14417 100644 --- a/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt @@ -354,33 +354,38 @@ private fun DeleteAccountDialog( onSuccess = { Log.d( LOG_SETTINGS_TAG, "user data loaded successfully, deleting the user") - userViewModel.deleteUser( - authenticationViewModel.authUserData.value!!.uid, + authenticationViewModel.logOut( onSuccess = { - Handler(Looper.getMainLooper()) - .post { // used to show the Toast on the main thread - Toast.makeText( - context, - context.getString( - R.string.settings_toast_success_delete), - Toast.LENGTH_SHORT) - .show() - } - Log.d(LOG_SETTINGS_TAG, "Account deleted successfully") - navigationActions.navigateTo(Screen.SIGN_IN) + Log.d(LOG_SETTINGS_TAG, "Sign out successful") + userViewModel.deleteUser( + authenticationViewModel.authUserData.value!!.uid, + onSuccess = { + Handler(Looper.getMainLooper()) + .post { // used to show the Toast on the main thread + Toast.makeText( + context, + context.getString( + R.string.settings_toast_success_delete), + Toast.LENGTH_SHORT) + .show() + } + Log.d(LOG_SETTINGS_TAG, "Account deleted successfully") + navigationActions.navigateTo(Screen.SIGN_IN) + }, + onFailure = { + Handler(Looper.getMainLooper()) + .post { // used to show the Toast on the main thread + Toast.makeText( + context, + context.getString( + R.string.settings_toast_failure_delete), + Toast.LENGTH_SHORT) + .show() + } + Log.d(LOG_SETTINGS_TAG, "Failed to delete account") + }) }, - onFailure = { - Handler(Looper.getMainLooper()) - .post { // used to show the Toast on the main thread - Toast.makeText( - context, - context.getString( - R.string.settings_toast_failure_delete), - Toast.LENGTH_SHORT) - .show() - } - Log.d(LOG_SETTINGS_TAG, "Failed to delete account") - }) + onFailure = { Log.d(LOG_SETTINGS_TAG, "Failed to sign out") }) }, onFailure = { Handler(Looper.getMainLooper()) diff --git a/app/src/test/java/com/android/periodpals/ui/settings/SettingsScreenTest.kt b/app/src/test/java/com/android/periodpals/ui/settings/SettingsScreenTest.kt index d76237aaf..3e3769de0 100644 --- a/app/src/test/java/com/android/periodpals/ui/settings/SettingsScreenTest.kt +++ b/app/src/test/java/com/android/periodpals/ui/settings/SettingsScreenTest.kt @@ -208,6 +208,10 @@ class SettingsScreenTest { val onSuccess = it.arguments[0] as () -> Unit onSuccess() } + `when`(authenticationViewModel.logOut(any(), any())).thenAnswer { + val onSuccess = it.arguments[0] as () -> Unit + onSuccess() + } `when`(userViewModel.deleteUser(any(), any(), any())).thenAnswer { val onFailure = it.arguments[2] as (Exception) -> Unit onFailure(Exception("Error deleting user account")) @@ -236,6 +240,10 @@ class SettingsScreenTest { val onSuccess = it.arguments[0] as () -> Unit onSuccess() } + `when`(authenticationViewModel.logOut(any(), any())).thenAnswer { + val onSuccess = it.arguments[0] as () -> Unit + onSuccess() + } `when`(userViewModel.deleteUser(any(), any(), any())).thenAnswer { val onSuccess = it.arguments[1] as () -> Unit onSuccess() From 5d15a7473f3d07e931a021ac34a5e8287bf147d2 Mon Sep 17 00:00:00 2001 From: Harrishan Date: Thu, 19 Dec 2024 19:08:55 +0100 Subject: [PATCH 10/15] style: put remark for notifications and location in `string.xml` --- .../android/periodpals/ui/settings/SettingsScreen.kt | 3 +-- app/src/main/res/values/strings.xml | 10 +--------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt b/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt index 770c14417..16026e84f 100644 --- a/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt @@ -136,8 +136,7 @@ fun SettingsScreen( // Remark Section SettingsContainer(testTag = SettingsScreen.REMARK_CONTAINER) { Text( - text = - "To enable/disable notifications or location, please go to your phone's settings", + text = context.getString(R.string.notifications_and_location_text), style = MaterialTheme.typography.labelMedium, modifier = Modifier.wrapContentHeight() diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 35b21adba..b9a4b9a75 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -48,15 +48,7 @@ My Settings - Notify me when a pal needs ... - Which are ... - - Pals’ Notifications - Pads - Tampons - Organic - - Theme + To enable/disable notifications or location, please go to your phone\'s settings Change Password Sign Out From ddfabaf0463289e5494a95477d8b47b763ded1a7 Mon Sep 17 00:00:00 2001 From: Harrishan Date: Thu, 19 Dec 2024 19:20:46 +0100 Subject: [PATCH 11/15] test: complete `allComponentsAreDisplayed` with the new UI components --- .../com/android/periodpals/resources/C.kt | 2 +- .../periodpals/ui/settings/SettingsScreen.kt | 2 +- .../ui/settings/SettingsScreenTest.kt | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/resources/C.kt b/app/src/main/java/com/android/periodpals/resources/C.kt index 6468fdf48..86426a62a 100644 --- a/app/src/main/java/com/android/periodpals/resources/C.kt +++ b/app/src/main/java/com/android/periodpals/resources/C.kt @@ -175,7 +175,7 @@ object C { const val SCREEN = "settingsScreen" const val REMARK_CONTAINER = "remarkContainer" const val REMARK_TEXT = "remarkText" - const val SETTINGS_CONTAINER = "settingsContainer" + const val SLIDER_CONTAINER = "sliderContainer" const val ACCOUNT_MANAGEMENT_CONTAINER = "accountManagementContainer" const val THEME_DROP_DOWN_MENU = "themeDropdownMenu" const val PASSWORD_TEXT = "passwordText" diff --git a/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt b/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt index 16026e84f..aeca957a7 100644 --- a/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt @@ -148,7 +148,7 @@ fun SettingsScreen( } // Slider Section - SettingsContainer(testTag = SettingsScreen.SETTINGS_CONTAINER) { + SettingsContainer(testTag = SettingsScreen.SLIDER_CONTAINER) { SliderMenu(sliderPosition) { sliderPosition = (it / 100).roundToInt() * 100f diff --git a/app/src/test/java/com/android/periodpals/ui/settings/SettingsScreenTest.kt b/app/src/test/java/com/android/periodpals/ui/settings/SettingsScreenTest.kt index 3e3769de0..0c3848625 100644 --- a/app/src/test/java/com/android/periodpals/ui/settings/SettingsScreenTest.kt +++ b/app/src/test/java/com/android/periodpals/ui/settings/SettingsScreenTest.kt @@ -14,6 +14,7 @@ import com.android.periodpals.model.user.AuthenticationUserData import com.android.periodpals.model.user.User import com.android.periodpals.model.user.UserViewModel import com.android.periodpals.resources.C.Tag.BottomNavigationMenu +import com.android.periodpals.resources.C.Tag.ProfileScreens.CreateProfileScreen import com.android.periodpals.resources.C.Tag.SettingsScreen import com.android.periodpals.resources.C.Tag.TopAppBar import com.android.periodpals.ui.navigation.NavigationActions @@ -87,6 +88,24 @@ class SettingsScreenTest { composeTestRule.onNodeWithTag(TopAppBar.EDIT_BUTTON).assertIsNotDisplayed() composeTestRule.onNodeWithTag(BottomNavigationMenu.BOTTOM_NAVIGATION_MENU).assertDoesNotExist() + composeTestRule + .onNodeWithTag(SettingsScreen.REMARK_CONTAINER) + .performScrollTo() + .assertIsDisplayed() + composeTestRule + .onNodeWithTag(SettingsScreen.REMARK_TEXT) + .performScrollTo() + .assertIsDisplayed() + .assertTextEquals(getResourceString(R.string.notifications_and_location_text)) + composeTestRule + .onNodeWithTag(SettingsScreen.SLIDER_CONTAINER) + .performScrollTo() + .assertIsDisplayed() + composeTestRule + .onNodeWithTag(CreateProfileScreen.FILTER_RADIUS_EXPLANATION_TEXT) + .performScrollTo() + .assertIsDisplayed() + .assertTextEquals(getResourceString(R.string.create_profile_radius_explanation_text)) composeTestRule .onNodeWithTag(SettingsScreen.ACCOUNT_MANAGEMENT_CONTAINER) .performScrollTo() From 1f03af99313a2fdbc2a108058c8b78a71ac4f287 Mon Sep 17 00:00:00 2001 From: Harrishan Date: Thu, 19 Dec 2024 19:56:42 +0100 Subject: [PATCH 12/15] test: verify that the user is not deleted when the they cannot log out --- .../ui/settings/SettingsScreenTest.kt | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/app/src/test/java/com/android/periodpals/ui/settings/SettingsScreenTest.kt b/app/src/test/java/com/android/periodpals/ui/settings/SettingsScreenTest.kt index 0c3848625..3e969b8a8 100644 --- a/app/src/test/java/com/android/periodpals/ui/settings/SettingsScreenTest.kt +++ b/app/src/test/java/com/android/periodpals/ui/settings/SettingsScreenTest.kt @@ -282,4 +282,32 @@ class SettingsScreenTest { verify(navigationActions).navigateTo(Screen.SIGN_IN) } + + @Test + fun deleteAccountVMLogOutFailure() { + `when`(authenticationViewModel.authUserData).thenReturn(userData) + `when`(authenticationViewModel.loadAuthenticationUserData(any(), any())).thenAnswer { + val onSuccess = it.arguments[0] as () -> Unit + onSuccess() + } + `when`(authenticationViewModel.logOut(any(), any())).thenAnswer { + val onFailure = it.arguments[1] as (Exception) -> Unit + onFailure(Exception("Error logging out user")) + } + + composeTestRule.setContent { + SettingsScreen(userViewModel, authenticationViewModel, navigationActions) + } + + composeTestRule + .onNodeWithTag(SettingsScreen.DELETE_ACCOUNT_ICON) + .performScrollTo() + .performClick() + composeTestRule.onNodeWithTag(SettingsScreen.DELETE_BUTTON).performClick() + + verify(userViewModel, never()).deleteUser(eq(userData.value.uid), any(), any()) + + verify(navigationActions, never()).navigateTo(any()) + verify(navigationActions, never()).navigateTo(any()) + } } From 58e17d88f03637d129cabdc7d4a2e54b595310ee Mon Sep 17 00:00:00 2001 From: Harrishan Date: Thu, 19 Dec 2024 20:09:16 +0100 Subject: [PATCH 13/15] test: add test for the slider value on change logic --- .../periodpals/ui/settings/SettingsScreen.kt | 45 ++++++++++++------- .../ui/settings/SettingsScreenTest.kt | 10 +++++ 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt b/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt index aeca957a7..5ea309d2c 100644 --- a/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt @@ -151,22 +151,7 @@ fun SettingsScreen( SettingsContainer(testTag = SettingsScreen.SLIDER_CONTAINER) { SliderMenu(sliderPosition) { sliderPosition = (it / 100).roundToInt() * 100f - - userViewModel.user.value?.let { user -> - val newUser = - User( - name = user.name, - dob = user.dob, - description = user.description, - imageUrl = user.imageUrl, - preferredDistance = sliderPosition.toInt(), - ) - - userViewModel.saveUser( - newUser, - onSuccess = { Log.d(LOG_SETTINGS_TAG, "User updated successfully") }, - onFailure = { Log.d(LOG_SETTINGS_TAG, "Failed to update user") }) - } + sliderLogic(sliderPosition, userViewModel) } Text( @@ -430,3 +415,31 @@ private fun DeleteAccountDialog( } } } + +/** + * Function that updates the user's preferred distance when the slider is moved. + * + * @param sliderPosition The position of the slider. + * @param userViewModel The ViewModel that handles user data. + */ +public fun sliderLogic( + sliderPosition: Float, + userViewModel: UserViewModel, +) { + + userViewModel.user.value?.let { user -> + val newUser = + User( + name = user.name, + dob = user.dob, + description = user.description, + imageUrl = user.imageUrl, + preferredDistance = sliderPosition.toInt(), + ) + + userViewModel.saveUser( + newUser, + onSuccess = { Log.d(LOG_SETTINGS_TAG, "User updated successfully") }, + onFailure = { Log.d(LOG_SETTINGS_TAG, "Failed to update user") }) + } +} diff --git a/app/src/test/java/com/android/periodpals/ui/settings/SettingsScreenTest.kt b/app/src/test/java/com/android/periodpals/ui/settings/SettingsScreenTest.kt index 3e969b8a8..8c212952d 100644 --- a/app/src/test/java/com/android/periodpals/ui/settings/SettingsScreenTest.kt +++ b/app/src/test/java/com/android/periodpals/ui/settings/SettingsScreenTest.kt @@ -310,4 +310,14 @@ class SettingsScreenTest { verify(navigationActions, never()).navigateTo(any()) verify(navigationActions, never()).navigateTo(any()) } + + @Test + fun sliderLogicTest() { + `when`(userViewModel.saveUser(any(), any(), any())).thenAnswer { + val onSuccess = it.arguments[1] as () -> Unit + onSuccess() + } + + sliderLogic(preferredDistance.toFloat(), userViewModel) + } } From 463be172d7ddde6cd7b3a343b7d4aec0a501a6ec Mon Sep 17 00:00:00 2001 From: Harrishan Date: Thu, 19 Dec 2024 20:13:21 +0100 Subject: [PATCH 14/15] test: make sure that a load data failure does not delete the user account --- .../ui/settings/SettingsScreenTest.kt | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/app/src/test/java/com/android/periodpals/ui/settings/SettingsScreenTest.kt b/app/src/test/java/com/android/periodpals/ui/settings/SettingsScreenTest.kt index 8c212952d..cd12906fd 100644 --- a/app/src/test/java/com/android/periodpals/ui/settings/SettingsScreenTest.kt +++ b/app/src/test/java/com/android/periodpals/ui/settings/SettingsScreenTest.kt @@ -311,6 +311,30 @@ class SettingsScreenTest { verify(navigationActions, never()).navigateTo(any()) } + @Test + fun deleteAccountVMLoadDataFailure() { + `when`(authenticationViewModel.authUserData).thenReturn(userData) + `when`(authenticationViewModel.loadAuthenticationUserData(any(), any())).thenAnswer { + val onFailure = it.arguments[1] as (Exception) -> Unit + onFailure(Exception("Error loading user data")) + } + + composeTestRule.setContent { + SettingsScreen(userViewModel, authenticationViewModel, navigationActions) + } + + composeTestRule + .onNodeWithTag(SettingsScreen.DELETE_ACCOUNT_ICON) + .performScrollTo() + .performClick() + composeTestRule.onNodeWithTag(SettingsScreen.DELETE_BUTTON).performClick() + + verify(userViewModel, never()).deleteUser(eq(userData.value.uid), any(), any()) + + verify(navigationActions, never()).navigateTo(any()) + verify(navigationActions, never()).navigateTo(any()) + } + @Test fun sliderLogicTest() { `when`(userViewModel.saveUser(any(), any(), any())).thenAnswer { From 20828ad7627fdb5de4f5eeb78ca3ce632e10dd7b Mon Sep 17 00:00:00 2001 From: Harrishan Date: Thu, 19 Dec 2024 23:03:58 +0100 Subject: [PATCH 15/15] fix: remove unnecessary public for `sliderLogic` method --- .../java/com/android/periodpals/ui/settings/SettingsScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt b/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt index 5ea309d2c..5107d02b6 100644 --- a/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt @@ -422,7 +422,7 @@ private fun DeleteAccountDialog( * @param sliderPosition The position of the slider. * @param userViewModel The ViewModel that handles user data. */ -public fun sliderLogic( +fun sliderLogic( sliderPosition: Float, userViewModel: UserViewModel, ) {