Skip to content

Commit

Permalink
180 improve overall visual consistency for final release (#183)
Browse files Browse the repository at this point in the history
* Make sortPeriodHistory public and adjust angle calculation

* Add padding to end of page

* Adjust layout for even sized length box and extract text from UpcomingPeriodBox

* Resize average length box

* Fine tune cycle box visuals

* Make system navigation color same as app

* Fix tests

* Fix a problem where pages re-render infinitely
  • Loading branch information
leowrites authored Jul 27, 2023
1 parent 2f017a4 commit 4d03df8
Show file tree
Hide file tree
Showing 15 changed files with 307 additions and 253 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -195,15 +195,15 @@ class PeriodPredictionTest {
@Test
fun arcAngleCalculation() {
assertEquals(
360f * 14 / 31,
340f * 14 / 31,
calculateArcAngle(periodHistoryHalfMonth),
)
}

@Test
fun arcAngleCalculation_MaxDays() {
assertEquals(
360f,
340f,
calculateArcAngle(periodHistoryOneCycle),
)
}
Expand Down
230 changes: 103 additions & 127 deletions app/src/main/java/com/tpp/theperiodpurse/AppScreen.kt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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.*
Expand All @@ -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
Expand Down Expand Up @@ -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<GoogleSignInAccount> = GoogleSignIn.getSignedInAccountFromIntent(data)
handleSignInResult(task)
}
}
private fun signIn() {
private fun signIn(launcher: ActivityResultLauncher<Intent>) {
val signInIntent = googleSignInClient.signInIntent
signInLauncher.launch(signInIntent)
}
private fun handleSignInResult(completedTask: Task<GoogleSignInAccount>) {
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 {
Expand All @@ -141,12 +116,12 @@ class MainActivity : ComponentActivity() {
@Composable
fun Application(
context: Context,
signIn: () -> Unit,
signIn: (launcher: ActivityResultLauncher<Intent>) -> Unit,
signout: () -> Unit = {},
hasNotificationsPermission: Boolean = false,
) {
AppScreen(
signIn = signIn,
signIn = { signInLauncher -> signIn(signInLauncher) },
context = context,
signout = signout,
hasNotificationsPermissions = hasNotificationsPermission,
Expand Down Expand Up @@ -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<Intent>) -> 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)
}
}
}
}
Expand Down
8 changes: 5 additions & 3 deletions app/src/main/java/com/tpp/theperiodpurse/NavigationGraph.kt
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -78,8 +80,8 @@ fun NavigationGraph(
appViewModel: AppViewModel,
modifier: Modifier = Modifier,
context: Context,
signIn: () -> Unit,
signout: () -> Unit = {},
signIn: (launcher: ActivityResultLauncher<Intent>) -> Unit,
) {
val onboardUIState by onboardViewModel.uiState.collectAsState()
val appUiState by appViewModel.uiState.collectAsState()
Expand Down Expand Up @@ -133,7 +135,7 @@ fun NavigationGraph(
onboardViewModel = onboardViewModel,
appUiState = appUiState,
calUiState = calUiState,
signIn = signIn,
signIn = { signInLauncher -> signIn(signInLauncher) },
signout = signout,
)
}
Expand Down Expand Up @@ -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,
Expand Down
Loading

0 comments on commit 4d03df8

Please sign in to comment.