diff --git a/app/src/androidTest/java/com/tpp/theperiodpurse/PeriodPredictionTest.kt b/app/src/androidTest/java/com/tpp/theperiodpurse/PeriodPredictionTest.kt index 0caed0c9..019a1066 100644 --- a/app/src/androidTest/java/com/tpp/theperiodpurse/PeriodPredictionTest.kt +++ b/app/src/androidTest/java/com/tpp/theperiodpurse/PeriodPredictionTest.kt @@ -195,7 +195,7 @@ class PeriodPredictionTest { @Test fun arcAngleCalculation() { assertEquals( - 360f * 14 / 31, + 340f * 14 / 31, calculateArcAngle(periodHistoryHalfMonth), ) } @@ -203,7 +203,7 @@ class PeriodPredictionTest { @Test fun arcAngleCalculation_MaxDays() { assertEquals( - 360f, + 340f, calculateArcAngle(periodHistoryOneCycle), ) } diff --git a/app/src/main/java/com/tpp/theperiodpurse/AppScreen.kt b/app/src/main/java/com/tpp/theperiodpurse/AppScreen.kt index 53420e3d..cb914527 100644 --- a/app/src/main/java/com/tpp/theperiodpurse/AppScreen.kt +++ b/app/src/main/java/com/tpp/theperiodpurse/AppScreen.kt @@ -1,7 +1,6 @@ package com.tpp.theperiodpurse import android.Manifest.permission.POST_NOTIFICATIONS -import android.app.Activity import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context @@ -14,6 +13,7 @@ import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.setContent +import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi import androidx.compose.foundation.* @@ -32,12 +32,9 @@ import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavHostController import androidx.navigation.compose.rememberNavController import com.google.android.gms.auth.api.signin.GoogleSignIn -import com.google.android.gms.auth.api.signin.GoogleSignInAccount import com.google.android.gms.auth.api.signin.GoogleSignInClient import com.google.android.gms.auth.api.signin.GoogleSignInOptions -import com.google.android.gms.common.api.ApiException import com.google.android.gms.common.api.Scope -import com.google.android.gms.tasks.Task import com.google.api.services.drive.DriveScopes import com.tpp.theperiodpurse.ui.component.BottomNavigation import com.tpp.theperiodpurse.ui.component.FloatingActionButton @@ -69,64 +66,42 @@ class MainActivity : ComponentActivity() { googleSignInClient = GoogleSignIn.getClient(this, gso) setContent { - ThePeriodPurseTheme { - val context = LocalContext.current.applicationContext - var hasNotificationPermission by remember { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - mutableStateOf( - ContextCompat.checkSelfPermission( - context, - POST_NOTIFICATIONS, - ) == PackageManager.PERMISSION_GRANTED, - ) - } else { - mutableStateOf(true) - } + val context = LocalContext.current.applicationContext + var hasNotificationPermission by remember { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + mutableStateOf( + ContextCompat.checkSelfPermission( + context, + POST_NOTIFICATIONS, + ) == PackageManager.PERMISSION_GRANTED, + ) + } else { + mutableStateOf(true) } - val launcher = rememberLauncherForActivityResult( - contract = ActivityResultContracts.RequestPermission(), - onResult = { isGranted -> - hasNotificationPermission = isGranted - if (!isGranted) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - shouldShowRequestPermissionRationale(POST_NOTIFICATIONS) - } + } + val launcher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.RequestPermission(), + onResult = { isGranted -> + hasNotificationPermission = isGranted + if (!isGranted) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + shouldShowRequestPermissionRationale(POST_NOTIFICATIONS) } - }, - ) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - SideEffect { - launcher.launch(POST_NOTIFICATIONS) } + }, + ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + SideEffect { + launcher.launch(POST_NOTIFICATIONS) } - Application(context = context, signIn = { signIn() }, signout = { signOut() }) } + Application(context = context, signIn = { signInLauncher -> signIn(signInLauncher) }, + signout = { signOut() }) } } - private val signInLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode == Activity.RESULT_OK) { - val data: Intent? = result.data - val task: Task = GoogleSignIn.getSignedInAccountFromIntent(data) - handleSignInResult(task) - } - } - private fun signIn() { + private fun signIn(launcher: ActivityResultLauncher) { val signInIntent = googleSignInClient.signInIntent - signInLauncher.launch(signInIntent) - } - private fun handleSignInResult(completedTask: Task) { - try { - if (completedTask.isSuccessful) { - Toast.makeText(this, "SignIn Successful", Toast.LENGTH_SHORT).show() - } else { - Toast.makeText(this, "SignIn Failed", Toast.LENGTH_SHORT).show() - } - } catch (e: ApiException) { - // The ApiException status code indicates the detailed failure reason. - // Please refer to the GoogleSignInStatusCodes class reference for more information. - Log.d("Sign In", e.toString()) - Toast.makeText(this, "SignIn Failed", Toast.LENGTH_SHORT).show() - } + launcher.launch(signInIntent) } private fun signOut() { googleSignInClient.revokeAccess().addOnCompleteListener { @@ -141,12 +116,12 @@ class MainActivity : ComponentActivity() { @Composable fun Application( context: Context, - signIn: () -> Unit, + signIn: (launcher: ActivityResultLauncher) -> Unit, signout: () -> Unit = {}, hasNotificationsPermission: Boolean = false, ) { AppScreen( - signIn = signIn, + signIn = { signInLauncher -> signIn(signInLauncher) }, context = context, signout = signout, hasNotificationsPermissions = hasNotificationsPermission, @@ -177,92 +152,93 @@ fun AppScreen( onboardViewModel: OnboardViewModel = viewModel(), calendarViewModel: CalendarViewModel = viewModel(), navController: NavHostController = rememberNavController(), - signIn: () -> Unit, skipWelcome: Boolean = false, skipDatabase: Boolean = false, skipOnboarding: Boolean = false, context: Context, signout: () -> Unit = {}, + signIn: (launcher: ActivityResultLauncher) -> Unit, hasNotificationsPermissions: Boolean = false, - ) { - var loggingOptionsVisible by remember { mutableStateOf(false) } - var skipOnboarding = skipOnboarding - val isOnboarded by onboardViewModel.isOnboarded.observeAsState(initial = null) - - if (!skipDatabase) { - LaunchedEffect(Unit) { - onboardViewModel.checkOnboardedStatus(context) - } - } - val startdestination: String + ThePeriodPurseTheme(appViewModel) { + var loggingOptionsVisible by remember { mutableStateOf(false) } + var skipOnboarding = skipOnboarding + val isOnboarded by onboardViewModel.isOnboarded.observeAsState(initial = null) - if (isOnboarded == null && !skipDatabase) { - LoadingScreen(appViewModel) - } else { if (!skipDatabase) { - skipOnboarding = (isOnboarded as Boolean) + LaunchedEffect(Unit) { + onboardViewModel.checkOnboardedStatus(context) + } } - if (skipOnboarding) { - startdestination = OnboardingScreen.LoadDatabase.name - } else if (skipWelcome) { - startdestination = OnboardingScreen.QuestionOne.name + val startdestination: String + + if (isOnboarded == null && !skipDatabase) { + LoadingScreen(appViewModel) } else { - startdestination = OnboardingScreen.Welcome.name - } - Scaffold( - floatingActionButton = { - if (currentRoute(navController) in screensWithNavigationBar) { - FloatingActionButton( + if (!skipDatabase) { + skipOnboarding = (isOnboarded as Boolean) + } + if (skipOnboarding) { + startdestination = OnboardingScreen.LoadDatabase.name + } else if (skipWelcome) { + startdestination = OnboardingScreen.QuestionOne.name + } else { + startdestination = OnboardingScreen.Welcome.name + } + Scaffold( + floatingActionButton = { + if (currentRoute(navController) in screensWithNavigationBar) { + FloatingActionButton( + navController = navController, + onClickInCalendar = { loggingOptionsVisible = true }, + ) + } + }, + floatingActionButtonPosition = FabPosition.Center, + isFloatingActionButtonDocked = true, + ) { innerPadding -> + Image( + painter = painterResource(id = appViewModel.colorPalette.background), + contentDescription = null, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.FillBounds, + ) + Box { + NavigationGraph( navController = navController, - onClickInCalendar = { loggingOptionsVisible = true }, + startDestination = startdestination, + onboardViewModel = onboardViewModel, + appViewModel = appViewModel, + calendarViewModel = calendarViewModel, + modifier = modifier.padding(innerPadding), + context = context, + signout = signout, + signIn = { signInLauncher -> signIn(signInLauncher) } ) - } - }, - floatingActionButtonPosition = FabPosition.Center, - isFloatingActionButtonDocked = true, - ) { innerPadding -> - Image( - painter = painterResource(id = appViewModel.colorPalette.background), - contentDescription = null, - modifier = Modifier.fillMaxSize(), - contentScale = ContentScale.FillBounds, - ) - Box { - NavigationGraph( - navController = navController, - startDestination = startdestination, - onboardViewModel = onboardViewModel, - appViewModel = appViewModel, - calendarViewModel = calendarViewModel, - modifier = modifier.padding(innerPadding), - signIn = signIn, - context = context, - signout = signout, - ) - if (loggingOptionsVisible) { - Log.d("AppScreen", "Rendering logging options") - LoggingOptionsPopup( - onLogDailySymptomsClick = { - navigateToLogScreenWithDate( - LocalDate.now(), - navController, - ) - }, - onLogMultiplePeriodDates = { navController.navigate(Screen.LogMultipleDates.name) }, - onExit = { loggingOptionsVisible = false }, - modifier = modifier.padding(bottom = 64.dp), - appViewModel = appViewModel - ) + if (loggingOptionsVisible) { + Log.d("AppScreen", "Rendering logging options") + LoggingOptionsPopup( + onLogDailySymptomsClick = { + navigateToLogScreenWithDate( + LocalDate.now(), + navController, + ) + }, + onLogMultiplePeriodDates = { navController.navigate(Screen.LogMultipleDates.name) }, + onExit = { loggingOptionsVisible = false }, + modifier = modifier.padding(bottom = 64.dp), + appViewModel = appViewModel + ) + } } - } - Box( - contentAlignment = Alignment.BottomCenter, - modifier = Modifier.fillMaxSize(), - ) { - if (currentRoute(navController) in screensWithNavigationBar) { - BottomNavigation(navController = navController, appViewModel = appViewModel) + Box( + contentAlignment = Alignment.BottomCenter, + modifier = Modifier.fillMaxSize(), + ) { + if (currentRoute(navController) in screensWithNavigationBar) { + BottomNavigation(navController = navController, appViewModel = appViewModel) + } } } } diff --git a/app/src/main/java/com/tpp/theperiodpurse/NavigationGraph.kt b/app/src/main/java/com/tpp/theperiodpurse/NavigationGraph.kt index e42f62da..f35f5715 100644 --- a/app/src/main/java/com/tpp/theperiodpurse/NavigationGraph.kt +++ b/app/src/main/java/com/tpp/theperiodpurse/NavigationGraph.kt @@ -1,8 +1,10 @@ package com.tpp.theperiodpurse import android.content.Context +import android.content.Intent import android.os.Build import android.util.Log +import androidx.activity.result.ActivityResultLauncher import androidx.annotation.RequiresApi import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState @@ -78,8 +80,8 @@ fun NavigationGraph( appViewModel: AppViewModel, modifier: Modifier = Modifier, context: Context, - signIn: () -> Unit, signout: () -> Unit = {}, + signIn: (launcher: ActivityResultLauncher) -> Unit, ) { val onboardUIState by onboardViewModel.uiState.collectAsState() val appUiState by appViewModel.uiState.collectAsState() @@ -133,7 +135,7 @@ fun NavigationGraph( onboardViewModel = onboardViewModel, appUiState = appUiState, calUiState = calUiState, - signIn = signIn, + signIn = { signInLauncher -> signIn(signInLauncher) }, signout = signout, ) } @@ -171,7 +173,7 @@ fun NavigationGraph( WelcomeScreen( onNextButtonClicked = { navController.navigate(OnboardingScreen.QuestionOne.name) }, - signIn = signIn, + signIn = { signInLauncher -> signIn(signInLauncher) }, signout = signout, navController = navController, context = context, diff --git a/app/src/main/java/com/tpp/theperiodpurse/ui/calendar/CalendarScreen.kt b/app/src/main/java/com/tpp/theperiodpurse/ui/calendar/CalendarScreen.kt index 9813c5a0..72d19baa 100644 --- a/app/src/main/java/com/tpp/theperiodpurse/ui/calendar/CalendarScreen.kt +++ b/app/src/main/java/com/tpp/theperiodpurse/ui/calendar/CalendarScreen.kt @@ -15,7 +15,6 @@ import androidx.navigation.compose.rememberNavController import com.google.accompanist.pager.* import com.kizitonwose.calendar.core.* import com.tpp.theperiodpurse.ui.calendar.components.* -import com.tpp.theperiodpurse.ui.theme.ThePeriodPurseTheme import com.tpp.theperiodpurse.ui.viewmodel.AppViewModel import com.tpp.theperiodpurse.ui.viewmodel.CalendarViewModel import java.util.* @@ -47,21 +46,19 @@ fun CalendarScreen( CalendarTabItem.CycleTab, ) val pagerState = rememberPagerState() - ThePeriodPurseTheme { - Scaffold(topBar = {}) { padding -> - Column( - verticalArrangement = Arrangement.Bottom, - modifier = Modifier.padding(padding), - ) { - Tabs(tabs = tabs, pagerState = pagerState, appViewModel = appViewModel) - TabsContent( - tabs = tabs, - pagerState = pagerState, - calendarViewModel = calendarViewModel, - navController = navController, - appViewModel = appViewModel, - ) - } + Scaffold(topBar = {}) { padding -> + Column( + verticalArrangement = Arrangement.Bottom, + modifier = Modifier.padding(padding), + ) { + Tabs(tabs = tabs, pagerState = pagerState, appViewModel = appViewModel) + TabsContent( + tabs = tabs, + pagerState = pagerState, + calendarViewModel = calendarViewModel, + navController = navController, + appViewModel = appViewModel, + ) } } } @@ -69,7 +66,5 @@ fun CalendarScreen( @Preview @Composable fun CalendarScreenPreview() { - ThePeriodPurseTheme { - CalendarScreen(rememberNavController(), viewModel(), viewModel()) - } + CalendarScreen(rememberNavController(), viewModel(), viewModel()) } diff --git a/app/src/main/java/com/tpp/theperiodpurse/ui/cycle/CycleScreen.kt b/app/src/main/java/com/tpp/theperiodpurse/ui/cycle/CycleScreen.kt index 1fe14cc0..324c87ae 100644 --- a/app/src/main/java/com/tpp/theperiodpurse/ui/cycle/CycleScreen.kt +++ b/app/src/main/java/com/tpp/theperiodpurse/ui/cycle/CycleScreen.kt @@ -57,9 +57,16 @@ fun CycleScreenLayout( .fillMaxHeight() .padding(horizontal = 20.dp, vertical = 25.dp), ) { - // show if next predicted < 7 days + // show if next predicted <= 7 days if (daysUntilNextPeriod <= 7) { - UpcomingPeriodBox(daysUntilNextPeriod, appViewModel = appViewModel) + if (daysUntilNextPeriod <= 0) { + UpcomingPeriodBox("You period will likely come any day now!", + appViewModel = appViewModel) + } else { + UpcomingPeriodBox("You period might be coming in the next " + + "$daysUntilNextPeriod days", + appViewModel = appViewModel) + } } Spacer(modifier.height(15.dp)) CurrentCycleBox(dates = dates, appViewModel = appViewModel) @@ -70,6 +77,7 @@ fun CycleScreenLayout( color = appViewModel.colorPalette.cyclePink, length = periodLength, image = painterResource(R.drawable.flow_with_heart), + modifier = Modifier.weight(1f) ) Spacer(modifier.width(16.dp)) AverageLengthBox( @@ -77,6 +85,7 @@ fun CycleScreenLayout( color = appViewModel.colorPalette.cycleBlue, length = cycleLength, image = painterResource(R.drawable.menstruation_calendar__1_), + modifier = Modifier.weight(1f) ) } Spacer(modifier.height(30.dp)) diff --git a/app/src/main/java/com/tpp/theperiodpurse/ui/cycle/components/AverageLengthBox.kt b/app/src/main/java/com/tpp/theperiodpurse/ui/cycle/components/AverageLengthBox.kt index 4b13b294..a7094be9 100644 --- a/app/src/main/java/com/tpp/theperiodpurse/ui/cycle/components/AverageLengthBox.kt +++ b/app/src/main/java/com/tpp/theperiodpurse/ui/cycle/components/AverageLengthBox.kt @@ -13,13 +13,14 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.tpp.theperiodpurse.R import com.tpp.theperiodpurse.ui.onboarding.scaledSp -import com.tpp.theperiodpurse.ui.viewmodel.AppViewModel -import java.lang.Appendable +import com.tpp.theperiodpurse.ui.theme.DarkColorPaletteImpl @Composable fun AverageLengthBox( @@ -30,20 +31,24 @@ fun AverageLengthBox( color: Color, ) { Card( - modifier.width(177.dp), + modifier = modifier.height(110.dp), elevation = 2.dp, backgroundColor = color, shape = RoundedCornerShape(10), ) { - Column(modifier.padding(18.dp)) { + Column( + modifier = Modifier.padding(vertical = 10.dp, horizontal = 15.dp) + ) { Text( text = title, - fontSize = 12.scaledSp(), + fontSize = 13.scaledSp(), fontWeight = FontWeight(700), color = Color.Black, ) - Spacer(modifier.height(20.dp)) - AverageLengthRow(length, modifier, image) + AverageLengthRow( + length = length, + image = image, + ) } } } @@ -51,41 +56,49 @@ fun AverageLengthBox( @Composable private fun AverageLengthRow( length: Float, - modifier: Modifier, + modifier: Modifier = Modifier, image: Painter, ) { - Row { + Row (modifier = modifier.padding(top = 10.dp)){ Text( text = when (length) { (-1).toFloat() -> stringResource(R.string.log_to_learn) (-2).toFloat() -> stringResource(R.string.log_to_learn) - else -> "%.2f Days".format(length) + else -> "${length.toInt()} Days" }, fontSize = when (length) { - (-1).toFloat() -> 10.scaledSp() - (-2).toFloat() -> 10.scaledSp() + (-1).toFloat() -> 13.scaledSp() + (-2).toFloat() -> 13.scaledSp() else -> 20.scaledSp() }, fontWeight = FontWeight(500), - modifier = modifier.width(55.dp), + modifier = modifier + .weight(1f), ) - Spacer(modifier.width(29.dp)) + Spacer(modifier = Modifier.width(10.dp)) Box( modifier - .size(50.dp) .clip(RoundedCornerShape(50)) - .background(Color.White), + .background(color = Color.White) + .aspectRatio(1f) // Maintain a 1:1 aspect ratio + .weight(0.5f) ) { Image( painter = image, contentDescription = null, modifier = Modifier .matchParentSize() - .aspectRatio(1f) // Maintain a 1:1 aspect ratio - .padding(8.dp), // Add padding to shrink the image inside the box + .padding(10.dp), // Add padding to shrink the image inside the box contentScale = ContentScale.Fit, alignment = Alignment.Center, ) } } } + +@Preview +@Composable +fun AverageLengthBoxPreview(){ + AverageLengthBox(title = "Average Period Length", image = painterResource(R.drawable + .flow_with_heart), length = 5.00f, color = DarkColorPaletteImpl().secondary3,) +} \ No newline at end of file diff --git a/app/src/main/java/com/tpp/theperiodpurse/ui/cycle/components/CurrentCycleBox.kt b/app/src/main/java/com/tpp/theperiodpurse/ui/cycle/components/CurrentCycleBox.kt index b9d0d40e..1ac49cb4 100644 --- a/app/src/main/java/com/tpp/theperiodpurse/ui/cycle/components/CurrentCycleBox.kt +++ b/app/src/main/java/com/tpp/theperiodpurse/ui/cycle/components/CurrentCycleBox.kt @@ -1,7 +1,6 @@ package com.tpp.theperiodpurse.ui.cycle.components import androidx.compose.foundation.Canvas -import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Card @@ -11,6 +10,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.drawscope.Stroke @@ -31,10 +31,12 @@ fun CurrentCycleBox(modifier: Modifier = Modifier, dates: ArrayList, appVi shape = RoundedCornerShape(5), modifier = modifier .fillMaxWidth() - .height(300.dp) + .height(300.dp), + backgroundColor = if (appViewModel.getColorMode()) appViewModel.colorPalette + .HeaderColor1.copy(0.75f) else Color.White, ) { - Column(modifier = modifier.background(color = appViewModel.colorPalette.HeaderColor1)) { + Column { Text( text = stringResource(R.string.current_cycle), fontSize = 20.scaledSp(), @@ -42,10 +44,7 @@ fun CurrentCycleBox(modifier: Modifier = Modifier, dates: ArrayList, appVi color = Color(0xFFB12126), modifier = modifier.padding(start = 20.dp, top = 20.dp), ) - Spacer( - modifier = Modifier.height(16.dp), - ) - CycleInfo(dates = dates, modifier = modifier) + CycleInfo(dates = dates, modifier = modifier, appViewModel = appViewModel) } } @@ -55,7 +54,9 @@ fun CurrentCycleBox(modifier: Modifier = Modifier, dates: ArrayList, appVi private fun CycleInfo( dates: ArrayList, modifier: Modifier, + appViewModel: AppViewModel ) { + val gradientColor = listOf(appViewModel.colorPalette.primary2, appViewModel.colorPalette.primary1) Box( modifier = Modifier .padding(20.dp) @@ -67,24 +68,24 @@ private fun CycleInfo( .size(200.dp), ) { Canvas(modifier = Modifier.matchParentSize()) { - val strokeWidth = 25.dp.toPx() + val strokeWidth = 23.dp.toPx() val ringColor = Color(0xFFB12126) val radius = (size.minDimension - strokeWidth) / 2 drawArc( color = ringColor.copy(alpha = 0.2f), - startAngle = 0f, - sweepAngle = 360f, + startAngle = -80f, + sweepAngle = 340f, useCenter = false, topLeft = center - Offset(radius, radius), size = Size(radius * 2, radius * 2), - style = Stroke(width = strokeWidth), + style = Stroke(width = strokeWidth, cap = StrokeCap.Round), ) // Draw the progress ring drawArc( - color = ringColor, - startAngle = -90f, + brush = Brush.linearGradient(gradientColor), + startAngle = -80f, sweepAngle = calculateArcAngle(dates), useCenter = false, topLeft = center - Offset(radius, radius), @@ -98,21 +99,22 @@ private fun CycleInfo( ) { Text( text = calculateDaysSinceLastPeriod(dates).toString(), - fontSize = 50.scaledSp(), + fontSize = 40.scaledSp(), + modifier = Modifier.padding(0.dp), fontWeight = FontWeight(900), color = Color(0xFFB12126), ) Text( text = stringResource(R.string.days_since), - fontSize = 16.scaledSp(), + fontSize = 15.scaledSp(), fontWeight = FontWeight(500), - color = Color(0xFF868083), + color = appViewModel.colorPalette.MainFontColor ) Text( text = stringResource(R.string.last_period), - fontSize = 16.scaledSp(), + fontSize = 15.scaledSp(), fontWeight = FontWeight(500), - color = Color(0xFF868083), + color = appViewModel.colorPalette.MainFontColor ) } } diff --git a/app/src/main/java/com/tpp/theperiodpurse/ui/cycle/components/UpcomingPeriodBox.kt b/app/src/main/java/com/tpp/theperiodpurse/ui/cycle/components/UpcomingPeriodBox.kt index 11db6813..07392aa3 100644 --- a/app/src/main/java/com/tpp/theperiodpurse/ui/cycle/components/UpcomingPeriodBox.kt +++ b/app/src/main/java/com/tpp/theperiodpurse/ui/cycle/components/UpcomingPeriodBox.kt @@ -20,8 +20,9 @@ import androidx.compose.ui.unit.dp import com.tpp.theperiodpurse.R import com.tpp.theperiodpurse.ui.viewmodel.AppViewModel + @Composable -fun UpcomingPeriodBox(daysUntilPeriod: Int, appViewModel: AppViewModel? = null) { +fun UpcomingPeriodBox(text: String, appViewModel: AppViewModel? = null) { val color = appViewModel?.colorPalette?.secondary2 ?: Color.White Card( shape = RoundedCornerShape(12.dp), @@ -37,7 +38,7 @@ fun UpcomingPeriodBox(daysUntilPeriod: Int, appViewModel: AppViewModel? = null) verticalAlignment = Alignment.CenterVertically ) { Text( - text = "You period might be coming in the next $daysUntilPeriod days", + text = text, modifier = Modifier.weight(2f) ) Image( @@ -55,5 +56,5 @@ fun UpcomingPeriodBox(daysUntilPeriod: Int, appViewModel: AppViewModel? = null) @Preview @Composable fun UpcomingPeriodBoxPreview(){ - UpcomingPeriodBox(10) + UpcomingPeriodBox("Your period will likely come any day now!") } diff --git a/app/src/main/java/com/tpp/theperiodpurse/ui/education/EducationInfoScreen.kt b/app/src/main/java/com/tpp/theperiodpurse/ui/education/EducationInfoScreen.kt index 7506f04f..f0610867 100644 --- a/app/src/main/java/com/tpp/theperiodpurse/ui/education/EducationInfoScreen.kt +++ b/app/src/main/java/com/tpp/theperiodpurse/ui/education/EducationInfoScreen.kt @@ -16,10 +16,8 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight.Companion.Bold import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController -import androidx.navigation.compose.rememberNavController import com.tpp.theperiodpurse.R import com.tpp.theperiodpurse.ui.datasource.Product import com.tpp.theperiodpurse.ui.datasource.ProductsList @@ -85,6 +83,6 @@ fun EducationInfoScreen( fontSize = 18.scaledSp(), color = appViewModel.colorPalette.MainFontColor ) - Spacer(modifier = Modifier.weight(45f)) + Spacer(modifier = Modifier.height(50.dp)) } } diff --git a/app/src/main/java/com/tpp/theperiodpurse/ui/onboarding/RestoreFromGoogleDrivePrompt.kt b/app/src/main/java/com/tpp/theperiodpurse/ui/onboarding/RestoreFromGoogleDrivePrompt.kt index ff8159f0..15580502 100644 --- a/app/src/main/java/com/tpp/theperiodpurse/ui/onboarding/RestoreFromGoogleDrivePrompt.kt +++ b/app/src/main/java/com/tpp/theperiodpurse/ui/onboarding/RestoreFromGoogleDrivePrompt.kt @@ -32,6 +32,7 @@ fun RestoreFromGoogleDrivePrompt( signout: () -> Unit = {}, context: Context, ) { + Log.d("RestoreFromGoogleDrive", "Rendering") val googleDriveFolder by onboardViewModel.googleDriveFolder.observeAsState() val drivePermissionHasError by onboardViewModel.drivePermissionHasError.observeAsState() if (googleAccount != null) { diff --git a/app/src/main/java/com/tpp/theperiodpurse/ui/onboarding/WelcomeScreen.kt b/app/src/main/java/com/tpp/theperiodpurse/ui/onboarding/WelcomeScreen.kt index 3c5f963e..eeae1b35 100644 --- a/app/src/main/java/com/tpp/theperiodpurse/ui/onboarding/WelcomeScreen.kt +++ b/app/src/main/java/com/tpp/theperiodpurse/ui/onboarding/WelcomeScreen.kt @@ -1,7 +1,14 @@ package com.tpp.theperiodpurse.ui.onboarding +import android.app.Activity import android.content.Context +import android.content.Intent import android.os.Build +import android.util.Log +import android.widget.Toast +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image @@ -10,8 +17,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -30,8 +35,8 @@ import androidx.compose.ui.unit.sp import androidx.navigation.NavHostController import com.google.android.gms.auth.api.signin.GoogleSignIn import com.google.android.gms.auth.api.signin.GoogleSignInAccount -import com.google.android.gms.auth.api.signin.GoogleSignInResult -import com.google.android.gms.common.api.Status +import com.google.android.gms.common.api.ApiException +import com.google.android.gms.tasks.Task import com.tpp.theperiodpurse.OnboardingScreen import com.tpp.theperiodpurse.R import com.tpp.theperiodpurse.ui.component.handleError @@ -44,7 +49,7 @@ import com.tpp.theperiodpurse.utility.validateUserAuthenticationAndAuthorization @RequiresApi(Build.VERSION_CODES.O) @Composable fun WelcomeScreen( - signIn: () -> Unit, + signIn: (launcher: ActivityResultLauncher) -> Unit, signout: () -> Unit = {}, onNextButtonClicked: () -> Unit, navController: NavHostController, @@ -56,24 +61,16 @@ fun WelcomeScreen( val screenheight = configuration.screenHeightDp val screenwidth = configuration.screenWidthDp val account = GoogleSignIn.getLastSignedInAccount(context) - // use a value through view model which appscreen can post to - - val signInResult = remember { - mutableStateOf( - GoogleSignInResult( - GoogleSignInAccount.createDefault(), - Status.RESULT_CANCELED, - ), - ) - } - LaunchedEffect(signInResult.value) { - if (!signInResult.value.isSuccess) { - signInResult.value = GoogleSignInResult( - GoogleSignInAccount.createDefault(), - Status.RESULT_CANCELED, - ) + val launcher = rememberLauncherForActivityResult(contract = ActivityResultContracts + .StartActivityForResult()){ result -> + if (result.resultCode == Activity.RESULT_OK) { + val data: Intent? = result.data + val task: Task = GoogleSignIn.getSignedInAccountFromIntent(data) + handleSignInResult(task, context, onboardUIState, signout, navController) } } + Log.d("Welcome", "Re-rendering") + if (account != null) { onboardUIState.googleAccount = account.account val hasGoogleDrivePermission = validateUserAuthenticationAndAuthorization(account) @@ -87,6 +84,7 @@ fun WelcomeScreen( ) } else { LaunchedEffect(Unit) { + Log.d("Welcome", "Navigating Second Time") navController.navigate(OnboardingScreen.RestoreFromGoogleDrivePrompt.name) } } @@ -126,9 +124,7 @@ fun WelcomeScreen( ) Spacer(modifier = Modifier.height(5.dp)) // Sign in with Google Button - GoogleSignInButton { - signIn() - } + GoogleSignInButton { signIn(launcher) } Spacer(modifier = Modifier.height((screenheight * 0.006).dp)) @@ -223,3 +219,32 @@ fun handleSecurityError( navController.navigate(OnboardingScreen.Welcome.name) } } + +private fun handleSignInResult(completedTask: Task, context: Context, + onboardUIState: OnboardUIState, signout: () -> Unit, navController: NavHostController) { + try { + if (completedTask.isSuccessful) { + val account = GoogleSignIn.getLastSignedInAccount(context) + if (account != null) { onboardUIState.googleAccount = account.account } + val hasGoogleDrivePermission = validateUserAuthenticationAndAuthorization(account) + if (!hasGoogleDrivePermission) { + handleSecurityError( + context, + signout, + "ERROR - Please grant all the required " + + "permissions", + navController, + ) + } else { + Log.d("Welcome", "Navigating First Time") + Toast.makeText(context, "SignIn Successful", Toast.LENGTH_SHORT).show() + navController.navigate(OnboardingScreen.RestoreFromGoogleDrivePrompt.name) + } + } else { + Toast.makeText(context, "SignIn Failed", Toast.LENGTH_SHORT).show() + } + } catch (e: ApiException) { + Log.d("Sign In", e.toString()) + Toast.makeText(context, "SignIn Failed", Toast.LENGTH_SHORT).show() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tpp/theperiodpurse/ui/setting/BackUpAccountScreen.kt b/app/src/main/java/com/tpp/theperiodpurse/ui/setting/BackUpAccountScreen.kt index f5750597..a43648eb 100644 --- a/app/src/main/java/com/tpp/theperiodpurse/ui/setting/BackUpAccountScreen.kt +++ b/app/src/main/java/com/tpp/theperiodpurse/ui/setting/BackUpAccountScreen.kt @@ -1,14 +1,18 @@ package com.tpp.theperiodpurse.ui.setting +import android.app.Activity import android.content.Context +import android.content.Intent +import android.util.Log import android.widget.Toast +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -18,8 +22,9 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.rememberNavController import com.google.android.gms.auth.api.signin.GoogleSignIn import com.google.android.gms.auth.api.signin.GoogleSignInAccount -import com.google.android.gms.auth.api.signin.GoogleSignInResult +import com.google.android.gms.common.api.ApiException import com.google.android.gms.common.api.Status +import com.google.android.gms.tasks.Task import com.tpp.theperiodpurse.ui.onboarding.GoogleSignInButton import com.tpp.theperiodpurse.ui.onboarding.scaledSp import com.tpp.theperiodpurse.ui.viewmodel.AppViewModel @@ -39,7 +44,7 @@ fun BackUpAccountScreen( appbar: Unit, navController: NavHostController = rememberNavController(), appViewModel: AppViewModel, - signIn: () -> Unit, + signIn: (launcher: ActivityResultLauncher) -> Unit, signOut: () -> Unit = {}, context: Context, ) { @@ -48,27 +53,11 @@ fun BackUpAccountScreen( val account = GoogleSignIn.getLastSignedInAccount(context) val authorized = validateUserAuthenticationAndAuthorization(account) - - val signInResult = remember { - mutableStateOf( - GoogleSignInResult( - GoogleSignInAccount.createDefault(), - Status.RESULT_CANCELED, - ), - ) - } - LaunchedEffect(signInResult.value) { - if (!signInResult.value.isSuccess) { - signInResult.value = GoogleSignInResult( - GoogleSignInAccount.createDefault(), - Status.RESULT_CANCELED, - ) - } - } + Log.d("Backup account", "Re-rendering") appbar if (account == null) { - SignInView(screenheight, signIn, appViewModel) + SignInView(screenheight, signIn, appViewModel, context, signOut, navController) } else if (authorized) { Box( modifier = Modifier @@ -124,7 +113,16 @@ fun BackUpAccountScreen( } @Composable -private fun SignInView(screenheight: Int, signIn: () -> Unit, appViewModel: AppViewModel) { +private fun SignInView(screenheight: Int, signIn: (launcher: ActivityResultLauncher) -> +Unit, appViewModel: AppViewModel, context: Context, signOut: () -> Unit, navController: NavHostController) { + val launcher = rememberLauncherForActivityResult(contract = ActivityResultContracts + .StartActivityForResult()){ result -> + if (result.resultCode == Activity.RESULT_OK) { + val data: Intent? = result.data + val task: Task = GoogleSignIn.getSignedInAccountFromIntent(data) + handleSignInResult(task, context, signOut, navController) + } + } Box( modifier = Modifier .fillMaxSize() @@ -153,9 +151,31 @@ private fun SignInView(screenheight: Int, signIn: () -> Unit, appViewModel: AppV Spacer(modifier = Modifier.height((screenheight * (0.02)).dp)) GoogleSignInButton { - signIn() + signIn(launcher) + } + } + } +} +private fun handleSignInResult(completedTask: Task, context: Context, + signout: () -> Unit, navController: NavHostController) { + try { + if (completedTask.isSuccessful) { + val account = GoogleSignIn.getLastSignedInAccount(context) + val hasGoogleDrivePermission = validateUserAuthenticationAndAuthorization(account) + if (!hasGoogleDrivePermission) { + handleSecurityError(context, signout, "ERROR - Please grant all the required " + + "permissions", navController) + } else { + Log.d("Welcome", "Navigating First Time") + Toast.makeText(context, "SignIn Successful", Toast.LENGTH_SHORT).show() + navController.navigate(SettingScreenNavigation.BackUpAccount.name) } + } else { + Toast.makeText(context, "SignIn Failed", Toast.LENGTH_SHORT).show() } + } catch (e: ApiException) { + Log.d("Sign In", e.toString()) + Toast.makeText(context, "SignIn Failed", Toast.LENGTH_SHORT).show() } } diff --git a/app/src/main/java/com/tpp/theperiodpurse/ui/setting/SettingScreenNavigation.kt b/app/src/main/java/com/tpp/theperiodpurse/ui/setting/SettingScreenNavigation.kt index 1a5e01cf..5862c65e 100644 --- a/app/src/main/java/com/tpp/theperiodpurse/ui/setting/SettingScreenNavigation.kt +++ b/app/src/main/java/com/tpp/theperiodpurse/ui/setting/SettingScreenNavigation.kt @@ -2,8 +2,10 @@ package com.tpp.theperiodpurse.ui.setting import android.Manifest import android.content.Context +import android.content.Intent import android.content.pm.PackageManager import android.os.Build +import androidx.activity.result.ActivityResultLauncher import androidx.annotation.RequiresApi import androidx.annotation.StringRes import androidx.compose.foundation.Image @@ -14,7 +16,6 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.runtime.* import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource @@ -85,7 +86,7 @@ fun SettingsScreen( onboardViewModel: OnboardViewModel?, appUiState: AppUiState?, calUiState: CalendarUIState?, - signIn: () -> Unit, + signIn: (launcher: ActivityResultLauncher) -> Unit, signout: () -> Unit = {}, ) { val backStackEntry by navController.currentBackStackEntryAsState() @@ -169,7 +170,7 @@ fun SettingsScreen( appViewModel = appViewModel ), navController = navController, - signIn = signIn, + signIn = { signInLauncher -> signIn(signInLauncher) }, signOut = signout, context = context, appViewModel = appViewModel diff --git a/app/src/main/java/com/tpp/theperiodpurse/ui/theme/Theme.kt b/app/src/main/java/com/tpp/theperiodpurse/ui/theme/Theme.kt index 8336ffd4..089c7aad 100644 --- a/app/src/main/java/com/tpp/theperiodpurse/ui/theme/Theme.kt +++ b/app/src/main/java/com/tpp/theperiodpurse/ui/theme/Theme.kt @@ -3,8 +3,11 @@ package com.tpp.theperiodpurse.ui.theme import androidx.compose.material.MaterialTheme import androidx.compose.material.lightColors import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.ui.graphics.Color import com.google.accompanist.systemuicontroller.rememberSystemUiController +import com.tpp.theperiodpurse.ui.viewmodel.AppViewModel val LightColorPalette = lightColors( primary = Purple500, @@ -23,17 +26,25 @@ val LightColorPalette = lightColors( @Composable fun ThePeriodPurseTheme( + appViewModel: AppViewModel, content: @Composable () -> Unit, ) { val colors = LightColorPalette val systemUiController = rememberSystemUiController() - systemUiController.setSystemBarsColor( + systemUiController.setStatusBarColor( color = Color.Black, ) - systemUiController.setNavigationBarColor( - color = BottomBarColor1, - ) + val darkMode = appViewModel.uiState.collectAsState().value.darkMode + LaunchedEffect(darkMode) { + if (darkMode) { + systemUiController.setNavigationBarColor(appViewModel.colorPalette.BottomBarColor1, + false) + } else { + systemUiController.setNavigationBarColor(appViewModel.colorPalette.BottomBarColor1, + true) + } + } MaterialTheme( colors = colors, diff --git a/app/src/main/java/com/tpp/theperiodpurse/utility/PeriodPrediction.kt b/app/src/main/java/com/tpp/theperiodpurse/utility/PeriodPrediction.kt index e2f7ac25..e53b7f38 100644 --- a/app/src/main/java/com/tpp/theperiodpurse/utility/PeriodPrediction.kt +++ b/app/src/main/java/com/tpp/theperiodpurse/utility/PeriodPrediction.kt @@ -65,7 +65,7 @@ fun parseDatesIntoPeriods(periodHistory: ArrayList): ArrayList) { +fun sortPeriodHistory(processedDates: ArrayList) { // Removes all dates with no flow or spotting. processedDates.removeAll { it.flow == FlowSeverity.None || it.flow == FlowSeverity.Spotting } @@ -197,10 +197,10 @@ fun calculateArcAngle(periodHistory: ArrayList): Float { val processedDates = processDates(periodHistory) val averageCycleLength = calculateAverageCycleLength(processedDates) if (averageCycleLength <= 0f) { - return 360f * min(1f, calculateDaysSinceLastPeriod(processedDates) / 31f) + return 340f * min(1f, calculateDaysSinceLastPeriod(processedDates) / 31f) } val resultedAngle = calculateDaysSinceLastPeriod(processedDates) / averageCycleLength - return 360f * min(1f, resultedAngle) + return 340f * min(1f, resultedAngle) } /**