diff --git a/app/src/main/java/com/android/periodpals/MainActivity.kt b/app/src/main/java/com/android/periodpals/MainActivity.kt index 3d32988d5..f2cb858ac 100644 --- a/app/src/main/java/com/android/periodpals/MainActivity.kt +++ b/app/src/main/java/com/android/periodpals/MainActivity.kt @@ -31,6 +31,7 @@ import com.android.periodpals.ui.navigation.Screen import com.android.periodpals.ui.profile.CreateProfileScreen import com.android.periodpals.ui.profile.EditProfileScreen import com.android.periodpals.ui.profile.ProfileScreen +import com.android.periodpals.ui.settings.SettingsScreen import com.android.periodpals.ui.theme.PeriodPalsAppTheme import com.android.periodpals.ui.timer.TimerScreen import io.github.jan.supabase.auth.Auth @@ -134,6 +135,7 @@ fun PeriodPalsApp( navigation(startDestination = Screen.PROFILE, route = Route.PROFILE) { composable(Screen.PROFILE) { ProfileScreen(userViewModel, navigationActions) } composable(Screen.EDIT_PROFILE) { EditProfileScreen(userViewModel, navigationActions) } + composable(Screen.SETTINGS) { SettingsScreen(navigationActions) } } } } 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 b50926257..b514b1827 100644 --- a/app/src/main/java/com/android/periodpals/resources/C.kt +++ b/app/src/main/java/com/android/periodpals/resources/C.kt @@ -110,6 +110,7 @@ object C { object TopAppBar { const val TOP_BAR = "topBar" const val GO_BACK_BUTTON = "goBackButton" + const val SETTINGS_BUTTON = "settingsButton" const val EDIT_BUTTON = "editButton" const val TITLE_TEXT = "titleText" } diff --git a/app/src/main/java/com/android/periodpals/ui/navigation/NavigationActions.kt b/app/src/main/java/com/android/periodpals/ui/navigation/NavigationActions.kt index 638810329..90ff5f374 100644 --- a/app/src/main/java/com/android/periodpals/ui/navigation/NavigationActions.kt +++ b/app/src/main/java/com/android/periodpals/ui/navigation/NavigationActions.kt @@ -29,6 +29,7 @@ object Screen { const val SIGN_UP = "Register Screen" const val CREATE_PROFILE = "CreateProfile Screen" const val EDIT_PROFILE = "EditProfile Screen" + const val SETTINGS = "Settings Screen" // TODO: Add as app is being built } diff --git a/app/src/main/java/com/android/periodpals/ui/navigation/TopAppBar.kt b/app/src/main/java/com/android/periodpals/ui/navigation/TopAppBar.kt index c0d323252..20add1ada 100644 --- a/app/src/main/java/com/android/periodpals/ui/navigation/TopAppBar.kt +++ b/app/src/main/java/com/android/periodpals/ui/navigation/TopAppBar.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.ArrowBack import androidx.compose.material.icons.outlined.Edit +import androidx.compose.material.icons.outlined.Settings import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -26,8 +27,10 @@ import com.android.periodpals.ui.theme.dimens * * @param title The title text to be displayed in the app bar. * @param backButton Whether to show a back button. Default is false. - * @param editButton Whether to show an edit button. Default is false. * @param onBackButtonClick Called when the back button is clicked. Default is null. + * @param settingsButton Whether to show a settings button. Default is false. + * @param onSettingsButtonClick Called when the settings button is clicked. Default is null. + * @param editButton Whether to show an edit button. Default is false. * @param onEditButtonClick Called when the edit button is clicked. Default is null. * * ### Usage: @@ -48,7 +51,7 @@ import com.android.periodpals.ui.theme.dimens * ### Testing: * - Use the testTag "topBar" to verify the app bar is displayed. * - If the back button is shown, check for the "goBackButton" tag to confirm its presence and - * functionality. + * functionality. - * - If the edit button is shown, check for the "editButton" tag to confirm its presence and * functionality. * - The title can be checked using the "screenTitle" testTag. @@ -59,12 +62,20 @@ fun TopAppBar( title: String, backButton: Boolean = false, onBackButtonClick: (() -> Unit)? = null, + settingsButton: Boolean = false, + onSettingsButtonClick: (() -> Unit)? = null, editButton: Boolean = false, onEditButtonClick: (() -> Unit)? = null, ) { + require(!(backButton && settingsButton)) { + "Either backButton or settingsButton must be true, but not both" + } require(!(backButton && onBackButtonClick == null)) { "onBackButtonClick must be provided when backButton is true" } + require(!(settingsButton && onSettingsButtonClick == null)) { + "onSettingsButtonClick must be provided when settingsButton is true" + } require(!(editButton && onEditButtonClick == null)) { "onEditButtonClick must be provided when editButton is true" } @@ -91,6 +102,18 @@ fun TopAppBar( contentDescription = "Back", ) } + } else if (settingsButton) { + IconButton( + modifier = Modifier.wrapContentSize().testTag(TopAppBar.SETTINGS_BUTTON), + onClick = onSettingsButtonClick!!, + colors = getTopAppBarIconButtonColors(), + ) { + Icon( + modifier = Modifier.size(MaterialTheme.dimens.iconSize), + imageVector = Icons.Outlined.Settings, + contentDescription = "Settings", + ) + } } }, actions = { diff --git a/app/src/main/java/com/android/periodpals/ui/profile/ProfileScreen.kt b/app/src/main/java/com/android/periodpals/ui/profile/ProfileScreen.kt index 0e87a411a..413c29939 100644 --- a/app/src/main/java/com/android/periodpals/ui/profile/ProfileScreen.kt +++ b/app/src/main/java/com/android/periodpals/ui/profile/ProfileScreen.kt @@ -88,6 +88,8 @@ fun ProfileScreen(userViewModel: UserViewModel, navigationActions: NavigationAct topBar = { TopAppBar( title = SCREEN_TITLE, + settingsButton = true, + onSettingsButtonClick = { navigationActions.navigateTo(Screen.SETTINGS) }, editButton = true, onEditButtonClick = { navigationActions.navigateTo(Screen.EDIT_PROFILE) }, ) 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 new file mode 100644 index 000000000..0da72cecb --- /dev/null +++ b/app/src/main/java/com/android/periodpals/ui/settings/SettingsScreen.kt @@ -0,0 +1,50 @@ +package com.android.periodpals.ui.settings + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import com.android.periodpals.ui.navigation.NavigationActions +import com.android.periodpals.ui.navigation.TopAppBar +import com.android.periodpals.ui.theme.dimens + +private const val SCREEN_TITLE = "My Settings" + +/** TODO: Placeholder Screen, waiting for implementation */ +@Composable +fun SettingsScreen(navigationActions: NavigationActions) { + Scaffold( + modifier = Modifier.fillMaxSize(), + topBar = { + TopAppBar( + title = SCREEN_TITLE, + backButton = true, + onBackButtonClick = { navigationActions.goBack() }) + }, + ) { paddingValues -> + Column( + modifier = + 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), + ) { + // TODO: delete when implementing the screen + Text("Settings Screen", modifier = Modifier.fillMaxSize()) + } + } +} diff --git a/app/src/test/java/com/android/periodpals/ui/alert/AlertListsScreenTest.kt b/app/src/test/java/com/android/periodpals/ui/alert/AlertListsScreenTest.kt index c7231229c..0e37a3bd2 100644 --- a/app/src/test/java/com/android/periodpals/ui/alert/AlertListsScreenTest.kt +++ b/app/src/test/java/com/android/periodpals/ui/alert/AlertListsScreenTest.kt @@ -113,6 +113,7 @@ class AlertListsScreenTest { .assertIsDisplayed() .assertTextEquals("Alert Lists") composeTestRule.onNodeWithTag(TopAppBar.GO_BACK_BUTTON).assertIsNotDisplayed() + composeTestRule.onNodeWithTag(TopAppBar.SETTINGS_BUTTON).assertIsNotDisplayed() composeTestRule.onNodeWithTag(TopAppBar.EDIT_BUTTON).assertIsNotDisplayed() composeTestRule.onNodeWithTag(BottomNavigationMenu.BOTTOM_NAVIGATION_MENU).assertIsDisplayed() } diff --git a/app/src/test/java/com/android/periodpals/ui/alert/CreateAlertScreenTest.kt b/app/src/test/java/com/android/periodpals/ui/alert/CreateAlertScreenTest.kt index 747e626ad..473a46538 100644 --- a/app/src/test/java/com/android/periodpals/ui/alert/CreateAlertScreenTest.kt +++ b/app/src/test/java/com/android/periodpals/ui/alert/CreateAlertScreenTest.kt @@ -77,6 +77,7 @@ class CreateAlertScreenTest { .assertIsDisplayed() .assertTextEquals("Create Alert") composeTestRule.onNodeWithTag(TopAppBar.GO_BACK_BUTTON).assertIsNotDisplayed() + composeTestRule.onNodeWithTag(TopAppBar.SETTINGS_BUTTON).assertIsNotDisplayed() composeTestRule.onNodeWithTag(TopAppBar.EDIT_BUTTON).assertIsNotDisplayed() composeTestRule.onNodeWithTag(BottomNavigationMenu.BOTTOM_NAVIGATION_MENU).assertIsDisplayed() diff --git a/app/src/test/java/com/android/periodpals/ui/alert/EditAlertScreenTest.kt b/app/src/test/java/com/android/periodpals/ui/alert/EditAlertScreenTest.kt index 3900f2312..e83a045ff 100644 --- a/app/src/test/java/com/android/periodpals/ui/alert/EditAlertScreenTest.kt +++ b/app/src/test/java/com/android/periodpals/ui/alert/EditAlertScreenTest.kt @@ -87,6 +87,7 @@ class EditAlertScreenTest { .assertIsDisplayed() .assertTextEquals("Edit Your Alert") composeTestRule.onNodeWithTag(TopAppBar.GO_BACK_BUTTON).assertIsDisplayed() + composeTestRule.onNodeWithTag(TopAppBar.SETTINGS_BUTTON).assertIsNotDisplayed() composeTestRule.onNodeWithTag(TopAppBar.EDIT_BUTTON).assertIsNotDisplayed() composeTestRule .onNodeWithTag(BottomNavigationMenu.BOTTOM_NAVIGATION_MENU) diff --git a/app/src/test/java/com/android/periodpals/ui/map/MapScreenTest.kt b/app/src/test/java/com/android/periodpals/ui/map/MapScreenTest.kt index 0d6911313..e500f896a 100644 --- a/app/src/test/java/com/android/periodpals/ui/map/MapScreenTest.kt +++ b/app/src/test/java/com/android/periodpals/ui/map/MapScreenTest.kt @@ -48,6 +48,7 @@ class MapScreenTest { composeTestRule.onNodeWithTag(TopAppBar.TOP_BAR).assertIsDisplayed() composeTestRule.onNodeWithTag(TopAppBar.TITLE_TEXT).assertIsDisplayed().assertTextEquals("Map") composeTestRule.onNodeWithTag(TopAppBar.GO_BACK_BUTTON).assertIsNotDisplayed() + composeTestRule.onNodeWithTag(TopAppBar.SETTINGS_BUTTON).assertIsNotDisplayed() composeTestRule.onNodeWithTag(TopAppBar.EDIT_BUTTON).assertIsNotDisplayed() composeTestRule.onNodeWithTag(MapScreen.MAP_VIEW_CONTAINER).assertIsDisplayed() diff --git a/app/src/test/java/com/android/periodpals/ui/navigation/TopAppBarTest.kt b/app/src/test/java/com/android/periodpals/ui/navigation/TopAppBarTest.kt index 0e2a428f1..59ced52a0 100644 --- a/app/src/test/java/com/android/periodpals/ui/navigation/TopAppBarTest.kt +++ b/app/src/test/java/com/android/periodpals/ui/navigation/TopAppBarTest.kt @@ -51,7 +51,24 @@ class TopAppBarTest { } @Test - fun backAndEditButtonsAreDisplayed() { + fun settingsButtonIsDisplayed() { + composeTestRule.setContent { + TopAppBar( + title = "Tampon Timer", + settingsButton = true, + onSettingsButtonClick = { /* Do nothing */}, + ) + } + + composeTestRule.onNodeWithTag(TopAppBar.TOP_BAR).assertIsDisplayed() + composeTestRule.onNodeWithTag(TopAppBar.TITLE_TEXT).assertIsDisplayed() + composeTestRule.onNodeWithTag(TopAppBar.SETTINGS_BUTTON).assertIsDisplayed() + composeTestRule.onNodeWithTag(TopAppBar.GO_BACK_BUTTON).assertDoesNotExist() + composeTestRule.onNodeWithTag(TopAppBar.EDIT_BUTTON).assertDoesNotExist() + } + + @Test + fun allButtonsAreDisplayed() { composeTestRule.setContent { TopAppBar( title = "Tampon Timer", @@ -100,6 +117,22 @@ class TopAppBarTest { assert(editButtonClicked) } + @Test + fun settingsButtonClickWorks() { + var settingsButtonClicked = false + + composeTestRule.setContent { + TopAppBar( + title = "Tampon Timer", + settingsButton = true, + onSettingsButtonClick = { settingsButtonClicked = true }, + ) + } + + composeTestRule.onNodeWithTag(TopAppBar.SETTINGS_BUTTON).performClick() + assert(settingsButtonClicked) + } + @Test fun backButtonInvalidFunction() { val exception = @@ -119,4 +152,33 @@ class TopAppBarTest { } assert(exception.message == "onEditButtonClick must be provided when editButton is true") } + + @Test + fun settingsButtonInvalidFunction() { + val exception = + assertThrows(IllegalArgumentException::class.java) { + composeTestRule.setContent { + TopAppBar(title = "Test Title", settingsButton = true, onSettingsButtonClick = null) + } + } + assert( + exception.message == "onSettingsButtonClick must be provided when settingsButton is true") + } + + @Test + fun cannotHaveBothBackAndSettingsButtons() { + val exception = + assertThrows(IllegalArgumentException::class.java) { + composeTestRule.setContent { + TopAppBar( + title = "Test Title", + backButton = true, + onBackButtonClick = { /* Do nothing */}, + settingsButton = true, + onSettingsButtonClick = { /* Do nothing */}, + ) + } + } + assert(exception.message == "Either backButton or settingsButton must be true, but not both") + } } diff --git a/app/src/test/java/com/android/periodpals/ui/profile/CreateProfileTest.kt b/app/src/test/java/com/android/periodpals/ui/profile/CreateProfileTest.kt index a2e96be42..232fb5457 100644 --- a/app/src/test/java/com/android/periodpals/ui/profile/CreateProfileTest.kt +++ b/app/src/test/java/com/android/periodpals/ui/profile/CreateProfileTest.kt @@ -69,6 +69,7 @@ class CreateProfileTest { .assertIsDisplayed() .assertTextEquals("Create Your Account") composeTestRule.onNodeWithTag(TopAppBar.GO_BACK_BUTTON).assertIsNotDisplayed() + composeTestRule.onNodeWithTag(TopAppBar.SETTINGS_BUTTON).assertIsNotDisplayed() composeTestRule.onNodeWithTag(TopAppBar.EDIT_BUTTON).assertIsNotDisplayed() composeTestRule.onNodeWithTag(BottomNavigationMenu.BOTTOM_NAVIGATION_MENU).assertDoesNotExist() diff --git a/app/src/test/java/com/android/periodpals/ui/profile/EditProfileTest.kt b/app/src/test/java/com/android/periodpals/ui/profile/EditProfileTest.kt index b4b1277cc..c921b0a71 100644 --- a/app/src/test/java/com/android/periodpals/ui/profile/EditProfileTest.kt +++ b/app/src/test/java/com/android/periodpals/ui/profile/EditProfileTest.kt @@ -68,6 +68,7 @@ class EditProfileTest { .assertIsDisplayed() .assertTextEquals("Edit Your Profile") composeTestRule.onNodeWithTag(TopAppBar.GO_BACK_BUTTON).assertIsDisplayed() + composeTestRule.onNodeWithTag(TopAppBar.SETTINGS_BUTTON).assertIsNotDisplayed() composeTestRule.onNodeWithTag(TopAppBar.EDIT_BUTTON).assertIsNotDisplayed() composeTestRule.onNodeWithTag(BottomNavigationMenu.BOTTOM_NAVIGATION_MENU).assertDoesNotExist() diff --git a/app/src/test/java/com/android/periodpals/ui/profile/ProfileScreenTest.kt b/app/src/test/java/com/android/periodpals/ui/profile/ProfileScreenTest.kt index 0608c60db..179878955 100644 --- a/app/src/test/java/com/android/periodpals/ui/profile/ProfileScreenTest.kt +++ b/app/src/test/java/com/android/periodpals/ui/profile/ProfileScreenTest.kt @@ -62,6 +62,7 @@ class ProfileScreenTest { .assertIsDisplayed() .assertTextEquals("Your Profile") composeTestRule.onNodeWithTag(TopAppBar.GO_BACK_BUTTON).assertIsNotDisplayed() + composeTestRule.onNodeWithTag(TopAppBar.SETTINGS_BUTTON).assertIsDisplayed() composeTestRule.onNodeWithTag(TopAppBar.EDIT_BUTTON).assertIsDisplayed() composeTestRule.onNodeWithTag(BottomNavigationMenu.BOTTOM_NAVIGATION_MENU).assertIsDisplayed() @@ -97,6 +98,16 @@ class ProfileScreenTest { .assertIsDisplayed() } + @Test + fun settingsButtonNavigatesToSettingsScreen() { + `when`(userViewModel.user).thenReturn(userState) + composeTestRule.setContent { ProfileScreen(userViewModel, navigationActions) } + + composeTestRule.onNodeWithTag(TopAppBar.SETTINGS_BUTTON).performClick() + + verify(navigationActions).navigateTo(Screen.SETTINGS) + } + @Test fun editButtonNavigatesToEditProfileScreen() { `when`(userViewModel.user).thenReturn(userState) diff --git a/app/src/test/java/com/android/periodpals/ui/timer/TimerScreenTest.kt b/app/src/test/java/com/android/periodpals/ui/timer/TimerScreenTest.kt index 2e44b256e..af7d8d529 100644 --- a/app/src/test/java/com/android/periodpals/ui/timer/TimerScreenTest.kt +++ b/app/src/test/java/com/android/periodpals/ui/timer/TimerScreenTest.kt @@ -41,6 +41,7 @@ class TimerScreenTest { .assertIsDisplayed() .assertTextEquals("Tampon Timer") composeTestRule.onNodeWithTag(TopAppBar.GO_BACK_BUTTON).assertIsNotDisplayed() + composeTestRule.onNodeWithTag(TopAppBar.SETTINGS_BUTTON).assertIsNotDisplayed() composeTestRule.onNodeWithTag(TopAppBar.EDIT_BUTTON).assertIsNotDisplayed() composeTestRule.onNodeWithTag(TimerScreen.DISPLAYED_TEXT).performScrollTo().assertIsDisplayed()