Skip to content

Commit

Permalink
Create calendar page layout and set up navigation (#25)
Browse files Browse the repository at this point in the history
* Added Basic Calendar Cycle Top Bar

* added dependencies

* Styled Calendar Cycle Top Bar

* Changed TopBar to use tabs, added navigation

* setup basic navigation bar and graph

* added calender navgiation to FAB

* modularized BottomNav

* modularized FAB

* moved Screen enum to NavigationGraph

* Added Swiping to tabs, comments

* cleanup

* removed unused icon

* Updated Indicator Color

* removed unused colors

* added dependencies and tests

* Added activity to manifest - temp fix so tests run

Was seeing the following issues when I run tests in NavigationTest:
- android.content.ActivityNotFoundException: Unable to find explicit activity class {com.example.theperiodpurse.test/androidx.test.core.app.InstrumentationActivityInvoker$EmptyActivity}; have you declared this activity in your AndroidManifest.xml, or does your intent not match its declared <intent-filter>?
- android.content.ActivityNotFoundException: Unable to find explicit activity class {com.example.theperiodpurse.test/androidx.test.core.app.InstrumentationActivityInvoker$BootstrapActivity}; have you declared this activity in your AndroidManifest.xml, or does your intent not match its declared <intent-filter>?

Referred to android/android-test#196 for the fix.
Looks like this is fixed in androidx.test:core:1.5.0-alpha02.

* removed changes to manifest

* added androidTestResultsUserPreferences to gitignore

* added useful contentDescription to FAB

* added instrumental tests to CI

* added emulator use to workflow

* fixed typo in ci

* test with macOS

* specify system image

* Added content description for testing

* Added content description for testing

* Added testing for tabs

* added check to hide navigation bar during onboarding

* better implementation for hiding nav bar

* bandaid fix for tests

* fix tests

* create different screen to start in Calendar page

* added skipOnboarding option to AppScreen

* popback stack after onboarding

* #3 Created the Calendar Layout (#33)

* Added basic calendar setup to the screen

* Changed the minSDK in Project Configs

* Updated Visuals to look like original app

* Added Click Functionality

* Revert "#3 Created the Calendar Layout (#33)" (#38)

This reverts commit 18909c4.

Co-authored-by: Pierre-William Lessard <lessardpw@gmail.com>
Co-authored-by: Madeline <93456777+madelahn@users.noreply.github.com>
  • Loading branch information
3 people authored Dec 5, 2022
1 parent 9cabca7 commit 4a8f501
Show file tree
Hide file tree
Showing 17 changed files with 694 additions and 95 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
/.idea/assetWizardSettings.xml
/.idea/inspectionProfiles/
/.idea/deploymentTargetDropDown.xml
/.idea/androidTestResultsUserPreferences.xml
.DS_Store
/build
/captures
Expand Down
5 changes: 4 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,13 @@ dependencies {
implementation "androidx.compose.ui:ui:$compose_ui_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_ui_version"
implementation 'androidx.compose.material:material:1.1.1'
implementation 'androidx.navigation:navigation-testing:2.5.3'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.ext:junit:1.1.4'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_ui_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_ui_version"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_ui_version"
implementation "com.google.accompanist:accompanist-pager:0.25.0"
implementation "com.google.accompanist:accompanist-pager-indicators:0.25.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package com.example.theperiodpurse

import androidx.activity.ComponentActivity
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.test.*
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.navigation.compose.ComposeNavigator
import androidx.navigation.testing.TestNavHostController
import com.example.theperiodpurse.ui.calendar.CalendarTabItem
import org.junit.Before
import org.junit.Rule
import org.junit.Test

class CalendarCycleTabTest {
@get:Rule
val composeTestRule = createAndroidComposeRule<ComponentActivity>()
private lateinit var navController: TestNavHostController

@Before
fun setupNavHost() {
composeTestRule.setContent {
navController =
TestNavHostController(LocalContext.current)
navController.navigatorProvider.addNavigator(
ComposeNavigator()
)
ScreenApp(navController = navController, skipOnboarding = true)
}
}


private fun navigateToCycleScreen() {
composeTestRule.onNodeWithText(CalendarTabItem.CycleTab.title).performClick()
}

@Test
fun appTabs_clickCalendar_CycleNotDisplayed() {
composeTestRule.onNodeWithContentDescription("Cycle Page").assertIsNotDisplayed()
}

@Test
fun appTabs_clickCalendar_CalendarDisplayed() {
composeTestRule.onNodeWithContentDescription("Calendar Page").assertIsDisplayed()
}

@Test
fun appTabs_clickCalendar_CalendarSelected() {
composeTestRule.onNodeWithText(CalendarTabItem.CalendarTab.title).assertIsSelected()
}

@Test
fun appTabs_clickCycleTab_DisplaysCycle() {
composeTestRule.onNodeWithText(CalendarTabItem.CycleTab.title).performClick()

composeTestRule.onNodeWithContentDescription("Cycle Page").assertIsDisplayed()
}

@Test
fun appTabs_clickCycleTab_SelectsCycle() {
composeTestRule.onNodeWithText(CalendarTabItem.CycleTab.title).performClick()

composeTestRule.onNodeWithText(CalendarTabItem.CycleTab.title).assertIsSelected()
}

@Test
fun appTabs_swipeCalendarPage_DisplaysCycle() {
composeTestRule.onRoot().performTouchInput {
swipeLeft()
}

composeTestRule.onNodeWithContentDescription("Cycle Page").assertIsDisplayed()
}

@Test
fun appTabs_swipeCalendarPage_SelectsCycle() {
composeTestRule.onRoot().performTouchInput {
swipeLeft()
}

composeTestRule.onNodeWithText(CalendarTabItem.CycleTab.title).assertIsSelected()
}

@Test
fun appTabs_swipeCyclePage_SelectsCalendar() {
composeTestRule.onRoot().performTouchInput {
swipeLeft()
swipeRight()
}

composeTestRule.onNodeWithText(CalendarTabItem.CalendarTab.title).assertIsSelected()
}

@Test
fun appTabs_swipeCyclePage_DisplaysCalendar() {
composeTestRule.onRoot().performTouchInput {
swipeLeft()
swipeRight()
}

composeTestRule.onNodeWithContentDescription("Calendar Page").assertIsDisplayed()
}

@Test
fun appTabs_clickCalendarTab_SelectsCalendar() {
navigateToCycleScreen()
composeTestRule.onNodeWithText(CalendarTabItem.CalendarTab.title).performClick()

composeTestRule.onNodeWithText(CalendarTabItem.CalendarTab.title).assertIsSelected()
}

@Test
fun appTabs_clickCalendarTab_DisplaysCalendar() {
navigateToCycleScreen()
composeTestRule.onNodeWithText(CalendarTabItem.CalendarTab.title).performClick()

composeTestRule.onNodeWithContentDescription("Calendar Page").assertIsDisplayed()
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.example.theperiodpurse

import androidx.activity.ComponentActivity
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.navigation.compose.ComposeNavigator
import androidx.navigation.testing.TestNavHostController
import org.junit.Before
import org.junit.Rule
import org.junit.Test

class NavigationTest {
@get:Rule
val composeTestRule = createAndroidComposeRule<ComponentActivity>()
private lateinit var navController: TestNavHostController

@Before
fun setupNavHost() {
composeTestRule.setContent {
navController =
TestNavHostController(LocalContext.current)
navController.navigatorProvider.addNavigator(
ComposeNavigator()
)
ScreenApp(navController = navController, skipOnboarding = true)
}
}

@Test
fun appNavHost_clickSettings_navigatesToSettingsScreen() {
composeTestRule.onNodeWithText(Screen.Settings.name)
.performClick()
navController.assertCurrentRouteName(Screen.Settings.name)
}

private fun navigateToSettingsScreen() {
composeTestRule.onNodeWithText(Screen.Settings.name).performClick()
}

@Test
fun appNavHost_clickSettings_navigatesToInfoScreen() {
composeTestRule.onNodeWithText(Screen.Learn.name)
.performClick()
navController.assertCurrentRouteName(Screen.Learn.name)
}

private fun navigateToInfoScreen() {
composeTestRule.onNodeWithText(Screen.Learn.name).performClick()
}

@Test
fun appNavHost_clickCalendarFABOnSettingsScreen_navigatesToCalendarScreen() {
navigateToSettingsScreen()
composeTestRule.onNodeWithContentDescription("Navigate to Calendar page")
.performClick()
navController.assertCurrentRouteName(Screen.Calendar.name)
}

@Test
fun appNavHost_clickCalendarFABOnInfoScreen_navigatesToCalendarScreen() {
navigateToInfoScreen()
composeTestRule.onNodeWithContentDescription("Navigate to Calendar page")
.performClick()
navController.assertCurrentRouteName(Screen.Calendar.name)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.example.theperiodpurse

import androidx.navigation.NavController
import org.junit.Assert.assertEquals

fun NavController.assertCurrentRouteName(expectedRouteName: String) {
assertEquals(expectedRouteName, currentBackStackEntry?.destination?.route)
}
102 changes: 13 additions & 89 deletions app/src/main/java/com/example/theperiodpurse/AppScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,23 @@ package com.example.theperiodpurse
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.annotation.StringRes
import androidx.compose.ui.Modifier
import androidx.navigation.compose.rememberNavController
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.example.theperiodpurse.data.DataSource
import com.example.theperiodpurse.ui.QuestionThreeScreen
import com.example.theperiodpurse.ui.SummaryScreen
import com.example.theperiodpurse.ui.cycle.CycleScreenLayout
import com.example.theperiodpurse.ui.onboarding.*
import com.example.theperiodpurse.ui.theme.ThePeriodPurseTheme
import com.example.theperiodpurse.ui.component.BottomNavigation

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ThePeriodPurseTheme {
Application()

}
}
}
Expand All @@ -40,92 +30,26 @@ fun Application() {
ScreenApp()
}

enum class OnboardingScreen() {
Welcome,
QuestionOne,
QuestionTwo,
QuestionThree,
Summary,
Calendar


}

@Composable
fun ScreenApp(
modifier: Modifier = Modifier,
viewModel: OnboardViewModel = viewModel(),
skipOnboarding: Boolean = false,
navController: NavHostController = rememberNavController()

) {
Scaffold(
bottomBar = {
if (currentRoute(navController) in Screen.values().map{ it.name }) {
BottomNavigation(navController = navController)
}
}
) { innerPadding ->
val uiState by viewModel.uiState.collectAsState()

NavHost(
NavigationGraph(
navController = navController,
startDestination = OnboardingScreen.Welcome.name,
startDestination = if (skipOnboarding) Screen.Calendar.name else OnboardingScreen.Welcome.name,
viewModel,
modifier = modifier.padding(innerPadding)
) {
composable(route = OnboardingScreen.Welcome.name) {
WelcomeScreen(
onNextButtonClicked = {
navController.navigate(OnboardingScreen.QuestionOne.name)
}
)
}
composable(route = OnboardingScreen.QuestionOne.name) {
QuestionOneScreen(
onNextButtonClicked = { navController.navigate(OnboardingScreen.QuestionTwo.name) },
onSelectionChanged = { viewModel.setQuantity(it.toInt()) }
)
}
composable(route = OnboardingScreen.QuestionTwo.name) {
QuestionTwoScreen(
onNextButtonClicked = { navController.navigate(OnboardingScreen.QuestionThree.name) },
options = uiState.dateOptions,
onSelectionChanged = { viewModel.setDate(it) }
)
}

composable(route = OnboardingScreen.QuestionThree.name) {
val context = LocalContext.current
QuestionThreeScreen(
onNextButtonClicked = { navController.navigate(OnboardingScreen.Summary.name) },
onSelectionChanged = { viewModel.setSymptoms(it) },
options = DataSource.symptoms.map { id -> context.resources.getString(id) },
)
}
composable(route = OnboardingScreen.Summary.name) {
SummaryScreen(
onboardUiState = uiState,
onSendButtonClicked = {
navController.navigate(OnboardingScreen.Calendar.name)
},
onCancelButtonClicked = {
cancelOrderAndNavigateToStart(viewModel, navController)
},
)
}
composable(route = OnboardingScreen.Calendar.name) {
CycleScreenLayout(
)
}


}
)

}

}

/**
* Resets the [OnboardUIState] and pops up to [OnboardingScreen.Start]
*/
private fun cancelOrderAndNavigateToStart(
viewModel: OnboardViewModel,
navController: NavHostController
) {
viewModel.resetOrder()
navController.popBackStack(OnboardingScreen.Welcome.name, inclusive = false)
}
Loading

0 comments on commit 4a8f501

Please sign in to comment.