diff --git a/app/src/main/java/org/permanent/permanent/Validator.kt b/app/src/main/java/org/permanent/permanent/Validator.kt index 73493826..87fc508f 100644 --- a/app/src/main/java/org/permanent/permanent/Validator.kt +++ b/app/src/main/java/org/permanent/permanent/Validator.kt @@ -46,18 +46,18 @@ class Validator { } } - fun isValidPassword(password: String?, passwordError: MutableLiveData): Boolean { + fun isValidPassword(password: String?, passwordError: MutableLiveData?): Boolean { return when { password.isNullOrEmpty() -> { - passwordError.value = R.string.password_empty_error + passwordError?.value = R.string.password_empty_error false } password.length < MIN_PASSWORD_LENGTH -> { - passwordError.value = R.string.password_too_short_error + passwordError?.value = R.string.password_too_short_error false } else -> { - passwordError.value = null + passwordError?.value = null true } } diff --git a/app/src/main/java/org/permanent/permanent/ui/archiveOnboarding/compose/ArchiveNamePage.kt b/app/src/main/java/org/permanent/permanent/ui/archiveOnboarding/compose/ArchiveNamePage.kt index 10f75b0e..ae7a5044 100644 --- a/app/src/main/java/org/permanent/permanent/ui/archiveOnboarding/compose/ArchiveNamePage.kt +++ b/app/src/main/java/org/permanent/permanent/ui/archiveOnboarding/compose/ArchiveNamePage.kt @@ -45,7 +45,7 @@ import kotlinx.coroutines.launch import org.permanent.permanent.R import org.permanent.permanent.ui.composeComponents.ButtonColor import org.permanent.permanent.ui.composeComponents.ButtonIconAlignment -import org.permanent.permanent.ui.composeComponents.SmallTextAndIconButton +import org.permanent.permanent.ui.composeComponents.CenteredTextAndIconButton @Composable @@ -200,7 +200,7 @@ private fun TabletBody( Box( modifier = Modifier.weight(1f) ) { - SmallTextAndIconButton( + CenteredTextAndIconButton( buttonColor = ButtonColor.TRANSPARENT, text = stringResource(id = R.string.back), icon = painterResource(id = R.drawable.ic_arrow_back_rounded_white), @@ -215,7 +215,7 @@ private fun TabletBody( Box( modifier = Modifier.weight(1f) ) { - SmallTextAndIconButton( + CenteredTextAndIconButton( buttonColor = ButtonColor.LIGHT, text = stringResource(id = R.string.next), showButtonEnabled = textFieldValueState.text.isNotEmpty() @@ -353,7 +353,7 @@ private fun PhoneBody( .fillMaxWidth() .weight(1f) ) { - SmallTextAndIconButton( + CenteredTextAndIconButton( buttonColor = ButtonColor.TRANSPARENT, text = stringResource(id = R.string.back), icon = painterResource(id = R.drawable.ic_arrow_back_rounded_white), @@ -370,7 +370,7 @@ private fun PhoneBody( .fillMaxWidth() .weight(1f) ) { - SmallTextAndIconButton( + CenteredTextAndIconButton( buttonColor = ButtonColor.LIGHT, text = stringResource(id = R.string.next), showButtonEnabled = textFieldValueState.text.isNotEmpty() diff --git a/app/src/main/java/org/permanent/permanent/ui/archiveOnboarding/compose/ArchiveOnboardingScreen.kt b/app/src/main/java/org/permanent/permanent/ui/archiveOnboarding/compose/ArchiveOnboardingScreen.kt index 1f62ab5f..14e86ac8 100644 --- a/app/src/main/java/org/permanent/permanent/ui/archiveOnboarding/compose/ArchiveOnboardingScreen.kt +++ b/app/src/main/java/org/permanent/permanent/ui/archiveOnboarding/compose/ArchiveOnboardingScreen.kt @@ -2,16 +2,9 @@ package org.permanent.permanent.ui.archiveOnboarding.compose -import androidx.compose.animation.core.LinearEasing -import androidx.compose.animation.core.RepeatMode -import androidx.compose.animation.core.animateFloat -import androidx.compose.animation.core.infiniteRepeatable -import androidx.compose.animation.core.rememberInfiniteTransition -import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image 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 @@ -37,12 +30,10 @@ import androidx.compose.runtime.saveable.listSaver import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.runtime.toMutableStateList -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource @@ -53,7 +44,8 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch import org.permanent.permanent.R import org.permanent.permanent.models.ArchiveType -import org.permanent.permanent.ui.composeComponents.CustomProgressIndicator +import org.permanent.permanent.ui.composeComponents.CircularProgressIndicator +import org.permanent.permanent.ui.composeComponents.CustomLinearProgressIndicator import org.permanent.permanent.viewmodels.ArchiveOnboardingViewModel @Composable @@ -282,29 +274,7 @@ fun ArchiveOnboardingScreen( // Overlay with spinning images if (isBusyState) { - Box( - modifier = Modifier - .fillMaxSize() - .background(Color.Black.copy(alpha = 0.5f)) - .clickable(enabled = false) {}, contentAlignment = Alignment.Center - ) { - val infiniteTransition = rememberInfiniteTransition(label = "") - - val rotation by infiniteTransition.animateFloat( - initialValue = 0f, targetValue = 360f, animationSpec = infiniteRepeatable( - animation = tween(1000, easing = LinearEasing), - repeatMode = RepeatMode.Restart - ), label = "" - ) - - Image(painter = painterResource(id = R.drawable.ellipse_exterior), - contentDescription = null, - modifier = Modifier.graphicsLayer { rotationZ = -rotation }) - - Image(painter = painterResource(id = R.drawable.ellipse_interior), - contentDescription = null, - modifier = Modifier.graphicsLayer { rotationZ = rotation }) - } + CircularProgressIndicator() } } @@ -337,7 +307,7 @@ fun OnboardingProgressIndicator( val configuration = LocalConfiguration.current val screenWidthDp = configuration.screenWidthDp.dp - CustomProgressIndicator( + CustomLinearProgressIndicator( Modifier .clip(shape = RoundedCornerShape(3.dp)) .height(height), diff --git a/app/src/main/java/org/permanent/permanent/ui/archiveOnboarding/compose/ArchiveTypePage.kt b/app/src/main/java/org/permanent/permanent/ui/archiveOnboarding/compose/ArchiveTypePage.kt index b20be59d..6bef0991 100644 --- a/app/src/main/java/org/permanent/permanent/ui/archiveOnboarding/compose/ArchiveTypePage.kt +++ b/app/src/main/java/org/permanent/permanent/ui/archiveOnboarding/compose/ArchiveTypePage.kt @@ -40,7 +40,7 @@ import org.permanent.permanent.R import org.permanent.permanent.models.ArchiveType import org.permanent.permanent.ui.composeComponents.ButtonColor import org.permanent.permanent.ui.composeComponents.ButtonIconAlignment -import org.permanent.permanent.ui.composeComponents.SmallTextAndIconButton +import org.permanent.permanent.ui.composeComponents.CenteredTextAndIconButton @Composable fun ArchiveTypePage( @@ -131,7 +131,7 @@ private fun TabletBody( Box( modifier = Modifier.weight(1f) ) { - SmallTextAndIconButton( + CenteredTextAndIconButton( buttonColor = ButtonColor.TRANSPARENT, text = stringResource(id = R.string.back), icon = painterResource(id = R.drawable.ic_arrow_back_rounded_white), @@ -146,7 +146,7 @@ private fun TabletBody( Box( modifier = Modifier.weight(1f) ) { - SmallTextAndIconButton( + CenteredTextAndIconButton( buttonColor = ButtonColor.LIGHT, text = stringResource(id = R.string.next) ) { @@ -218,7 +218,7 @@ private fun PhoneBody( .fillMaxWidth() .weight(1f) ) { - SmallTextAndIconButton( + CenteredTextAndIconButton( buttonColor = ButtonColor.TRANSPARENT, text = stringResource(id = R.string.back), icon = painterResource(id = R.drawable.ic_arrow_back_rounded_white), @@ -235,7 +235,7 @@ private fun PhoneBody( .fillMaxWidth() .weight(1f) ) { - SmallTextAndIconButton( + CenteredTextAndIconButton( ButtonColor.LIGHT, text = stringResource(id = R.string.next) ) { coroutineScope.launch { diff --git a/app/src/main/java/org/permanent/permanent/ui/archiveOnboarding/compose/CongratulationsPage.kt b/app/src/main/java/org/permanent/permanent/ui/archiveOnboarding/compose/CongratulationsPage.kt index 9cc02032..0b95753a 100644 --- a/app/src/main/java/org/permanent/permanent/ui/archiveOnboarding/compose/CongratulationsPage.kt +++ b/app/src/main/java/org/permanent/permanent/ui/archiveOnboarding/compose/CongratulationsPage.kt @@ -43,7 +43,7 @@ import org.permanent.permanent.models.Archive import org.permanent.permanent.models.ThumbStatus import org.permanent.permanent.ui.composeComponents.ArchiveItem import org.permanent.permanent.ui.composeComponents.ButtonColor -import org.permanent.permanent.ui.composeComponents.SmallTextAndIconButton +import org.permanent.permanent.ui.composeComponents.CenteredTextAndIconButton import org.permanent.permanent.viewmodels.ArchiveOnboardingViewModel @Composable @@ -176,7 +176,7 @@ private fun PhoneBody( .fillMaxWidth() .weight(1f) ) { - SmallTextAndIconButton( + CenteredTextAndIconButton( buttonColor = ButtonColor.LIGHT, text = stringResource(id = R.string.done), icon = painterResource(id = R.drawable.ic_done_white), @@ -287,7 +287,7 @@ private fun TabletBody( .fillMaxWidth() .weight(1f) ) { - SmallTextAndIconButton( + CenteredTextAndIconButton( buttonColor = ButtonColor.LIGHT, text = stringResource(id = R.string.done), icon = painterResource(id = R.drawable.ic_done_white), diff --git a/app/src/main/java/org/permanent/permanent/ui/archiveOnboarding/compose/GoalsPage.kt b/app/src/main/java/org/permanent/permanent/ui/archiveOnboarding/compose/GoalsPage.kt index bf24be83..ad23454f 100644 --- a/app/src/main/java/org/permanent/permanent/ui/archiveOnboarding/compose/GoalsPage.kt +++ b/app/src/main/java/org/permanent/permanent/ui/archiveOnboarding/compose/GoalsPage.kt @@ -46,8 +46,8 @@ import kotlinx.coroutines.launch import org.permanent.permanent.R import org.permanent.permanent.ui.composeComponents.ButtonColor import org.permanent.permanent.ui.composeComponents.ButtonIconAlignment +import org.permanent.permanent.ui.composeComponents.CenteredTextAndIconButton import org.permanent.permanent.ui.composeComponents.CustomCheckbox -import org.permanent.permanent.ui.composeComponents.SmallTextAndIconButton import org.permanent.permanent.viewmodels.ArchiveOnboardingViewModel @@ -181,7 +181,7 @@ private fun PhoneBody( .fillMaxWidth() .weight(1f) ) { - SmallTextAndIconButton( + CenteredTextAndIconButton( buttonColor = ButtonColor.TRANSPARENT, text = stringResource(id = R.string.back), icon = painterResource(id = R.drawable.ic_arrow_back_rounded_white), @@ -200,7 +200,7 @@ private fun PhoneBody( .fillMaxWidth() .weight(1f) ) { - SmallTextAndIconButton( + CenteredTextAndIconButton( buttonColor = ButtonColor.LIGHT, text = stringResource(id = R.string.next) ) { newArchive.goals = goals @@ -317,7 +317,7 @@ private fun TabletBody( Box( modifier = Modifier.weight(1f) ) { - SmallTextAndIconButton( + CenteredTextAndIconButton( buttonColor = ButtonColor.TRANSPARENT, text = stringResource(id = R.string.back), icon = painterResource(id = R.drawable.ic_arrow_back_rounded_white), @@ -334,7 +334,7 @@ private fun TabletBody( Box( modifier = Modifier.weight(1f) ) { - SmallTextAndIconButton( + CenteredTextAndIconButton( buttonColor = ButtonColor.LIGHT, text = stringResource(id = R.string.next) ) { diff --git a/app/src/main/java/org/permanent/permanent/ui/archiveOnboarding/compose/PrioritiesPage.kt b/app/src/main/java/org/permanent/permanent/ui/archiveOnboarding/compose/PrioritiesPage.kt index bbd5ba9c..20386c0e 100644 --- a/app/src/main/java/org/permanent/permanent/ui/archiveOnboarding/compose/PrioritiesPage.kt +++ b/app/src/main/java/org/permanent/permanent/ui/archiveOnboarding/compose/PrioritiesPage.kt @@ -49,8 +49,8 @@ import kotlinx.coroutines.launch import org.permanent.permanent.R import org.permanent.permanent.ui.composeComponents.ButtonColor import org.permanent.permanent.ui.composeComponents.ButtonIconAlignment +import org.permanent.permanent.ui.composeComponents.CenteredTextAndIconButton import org.permanent.permanent.ui.composeComponents.CustomCheckbox -import org.permanent.permanent.ui.composeComponents.SmallTextAndIconButton import org.permanent.permanent.viewmodels.ArchiveOnboardingViewModel @Composable @@ -199,7 +199,7 @@ private fun PhoneBody( .fillMaxWidth() .weight(1f) ) { - SmallTextAndIconButton( + CenteredTextAndIconButton( buttonColor = ButtonColor.TRANSPARENT, text = stringResource(id = R.string.back), icon = painterResource(id = R.drawable.ic_arrow_back_rounded_white), @@ -216,7 +216,7 @@ private fun PhoneBody( .fillMaxWidth() .weight(1f) ) { - SmallTextAndIconButton( + CenteredTextAndIconButton( buttonColor = ButtonColor.LIGHT, text = stringResource(id = R.string.next) ) { newArchive.priorities = priorities @@ -334,7 +334,7 @@ private fun TabletBody( Box( modifier = Modifier.weight(1f) ) { - SmallTextAndIconButton( + CenteredTextAndIconButton( buttonColor = ButtonColor.TRANSPARENT, text = stringResource(id = R.string.back), icon = painterResource(id = R.drawable.ic_arrow_back_rounded_white), @@ -349,7 +349,7 @@ private fun TabletBody( Box( modifier = Modifier.weight(1f) ) { - SmallTextAndIconButton( + CenteredTextAndIconButton( buttonColor = ButtonColor.LIGHT, text = stringResource(id = R.string.next) ) { diff --git a/app/src/main/java/org/permanent/permanent/ui/archiveOnboarding/compose/WelcomePage.kt b/app/src/main/java/org/permanent/permanent/ui/archiveOnboarding/compose/WelcomePage.kt index 2b72b9f0..2912400d 100644 --- a/app/src/main/java/org/permanent/permanent/ui/archiveOnboarding/compose/WelcomePage.kt +++ b/app/src/main/java/org/permanent/permanent/ui/archiveOnboarding/compose/WelcomePage.kt @@ -44,7 +44,7 @@ import org.permanent.permanent.models.Status import org.permanent.permanent.models.ThumbStatus import org.permanent.permanent.ui.composeComponents.ArchiveItem import org.permanent.permanent.ui.composeComponents.ButtonColor -import org.permanent.permanent.ui.composeComponents.SmallTextAndIconButton +import org.permanent.permanent.ui.composeComponents.CenteredTextAndIconButton import org.permanent.permanent.viewmodels.ArchiveOnboardingViewModel @Composable @@ -169,7 +169,7 @@ private fun TabletBody( modifier = Modifier.weight(1f) ) { if (archives.isNotEmpty()) { - SmallTextAndIconButton( + CenteredTextAndIconButton( buttonColor = ButtonColor.TRANSPARENT, text = stringResource(id = R.string.create_new_archive), icon = painterResource(id = R.drawable.ic_plus_white) @@ -184,7 +184,7 @@ private fun TabletBody( Box( modifier = Modifier.weight(1f) ) { - SmallTextAndIconButton(buttonColor = ButtonColor.LIGHT, + CenteredTextAndIconButton(buttonColor = ButtonColor.LIGHT, text = stringResource(id = if (archives.isEmpty()) R.string.get_started else R.string.next), showButtonEnabled = archives.isEmpty() || archives.any { it.status == Status.OK }) { val nextPage = @@ -300,7 +300,7 @@ private fun PhoneBody( } .padding(horizontal = 32.dp), verticalArrangement = Arrangement.spacedBy(24.dp)) { - SmallTextAndIconButton(buttonColor = ButtonColor.LIGHT, + CenteredTextAndIconButton(buttonColor = ButtonColor.LIGHT, text = stringResource(id = if (archives.isEmpty()) R.string.get_started else R.string.next), showButtonEnabled = archives.isEmpty() || archives.any { it.status == Status.OK } ) { @@ -316,7 +316,7 @@ private fun PhoneBody( } if (archives.isNotEmpty()) { - SmallTextAndIconButton( + CenteredTextAndIconButton( buttonColor = ButtonColor.TRANSPARENT, text = stringResource(id = R.string.new_archive), icon = painterResource(id = R.drawable.ic_plus_white) diff --git a/app/src/main/java/org/permanent/permanent/ui/composeComponents/ArchiveItem.kt b/app/src/main/java/org/permanent/permanent/ui/composeComponents/ArchiveItem.kt index 38712d53..71bf4726 100644 --- a/app/src/main/java/org/permanent/permanent/ui/composeComponents/ArchiveItem.kt +++ b/app/src/main/java/org/permanent/permanent/ui/composeComponents/ArchiveItem.kt @@ -105,7 +105,7 @@ fun ArchiveItem( .width(90.dp) .height(40.dp) ) { - SmallTextAndIconButton( + CenteredTextAndIconButton( buttonColor = ButtonColor.TRANSPARENT, text = stringResource(id = R.string.accept), fontSize = 12.sp, @@ -183,7 +183,7 @@ private fun TabletBodyForWelcomePage( .width(96.dp) .height(48.dp) ) { - SmallTextAndIconButton( + CenteredTextAndIconButton( buttonColor = ButtonColor.TRANSPARENT, text = stringResource(id = R.string.accept), fontSize = 12.sp, diff --git a/app/src/main/java/org/permanent/permanent/ui/composeComponents/SmallTextAndIconButton.kt b/app/src/main/java/org/permanent/permanent/ui/composeComponents/CenteredTextAndIconButton.kt similarity index 62% rename from app/src/main/java/org/permanent/permanent/ui/composeComponents/SmallTextAndIconButton.kt rename to app/src/main/java/org/permanent/permanent/ui/composeComponents/CenteredTextAndIconButton.kt index 1f6ba85e..b119c65a 100644 --- a/app/src/main/java/org/permanent/permanent/ui/composeComponents/SmallTextAndIconButton.kt +++ b/app/src/main/java/org/permanent/permanent/ui/composeComponents/CenteredTextAndIconButton.kt @@ -22,7 +22,6 @@ import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.tooling.preview.Preview @@ -32,7 +31,7 @@ import androidx.compose.ui.unit.sp import org.permanent.permanent.R @Composable -fun SmallTextAndIconButton( +fun CenteredTextAndIconButton( buttonColor: ButtonColor, text: String, fontSize: TextUnit = 16.sp, @@ -41,52 +40,6 @@ fun SmallTextAndIconButton( showButtonEnabled: Boolean = true, onButtonClick: () -> Unit ) { - CustomSmallTextAndIconButton( - buttonColor = buttonColor, - text = text, - fontSize = fontSize, - annotatedText = null, - icon = icon, - iconAlignment = iconAlignment, - showButtonEnabled = showButtonEnabled, - onButtonClick = onButtonClick - ) -} - -@Composable -fun SmallTextAndIconButton( - buttonColor: ButtonColor, - annotatedText: AnnotatedString, - fontSize: TextUnit = 16.sp, - icon: Painter? = painterResource(id = R.drawable.ic_arrow_next_rounded_primary), - iconAlignment: ButtonIconAlignment = ButtonIconAlignment.END, - showButtonEnabled: Boolean = true, - onButtonClick: () -> Unit -) { - CustomSmallTextAndIconButton( - buttonColor = buttonColor, - text = null, - fontSize = fontSize, - annotatedText = annotatedText, - icon = icon, - iconAlignment = iconAlignment, - showButtonEnabled = showButtonEnabled, - onButtonClick = onButtonClick - ) -} - -@Composable -private fun CustomSmallTextAndIconButton( - buttonColor: ButtonColor, - text: String?, - annotatedText: AnnotatedString?, - fontSize: TextUnit = 16.sp, - icon: Painter? = painterResource(id = R.drawable.ic_arrow_next_rounded_primary), - iconAlignment: ButtonIconAlignment = ButtonIconAlignment.END, - showButtonEnabled: Boolean, - onButtonClick: () -> Unit -) { - val semiboldFont = FontFamily(Font(R.font.open_sans_semibold_ttf)) val primaryColor = colorResource(id = R.color.colorPrimary) val whiteTransparentColor = colorResource(id = R.color.whiteSuperTransparent) @@ -117,21 +70,12 @@ private fun CustomSmallTextAndIconButton( Spacer(modifier = Modifier.width(16.dp)) } - if (text != null) { - Text( - text = text, - color = if (buttonColor == ButtonColor.LIGHT) primaryColor else Color.White, - fontSize = fontSize, - fontFamily = semiboldFont, - ) - } else if (annotatedText != null) { - Text( - text = annotatedText, - color = if (buttonColor == ButtonColor.LIGHT) primaryColor else Color.White, - fontSize = fontSize, - fontFamily = semiboldFont, - ) - } + Text( + text = text, + color = if (buttonColor == ButtonColor.LIGHT) primaryColor else Color.White, + fontSize = fontSize, + fontFamily = FontFamily(Font(R.font.open_sans_semibold_ttf)), + ) if (iconAlignment == ButtonIconAlignment.END && icon != null) { Spacer(modifier = Modifier.width(16.dp)) @@ -153,8 +97,8 @@ enum class ButtonIconAlignment { @Preview @Composable -fun SmallTextAndIconButtonPreview() { - SmallTextAndIconButton( +fun CenteredTextAndIconButtonPreview() { + CenteredTextAndIconButton( buttonColor = ButtonColor.TRANSPARENT, text = stringResource(id = R.string.accept), ) { } diff --git a/app/src/main/java/org/permanent/permanent/ui/composeComponents/CircularProgressIndicator.kt b/app/src/main/java/org/permanent/permanent/ui/composeComponents/CircularProgressIndicator.kt new file mode 100644 index 00000000..167f232d --- /dev/null +++ b/app/src/main/java/org/permanent/permanent/ui/composeComponents/CircularProgressIndicator.kt @@ -0,0 +1,48 @@ +package org.permanent.permanent.ui.composeComponents + +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.res.painterResource +import org.permanent.permanent.R + +@Composable +fun CircularProgressIndicator() { + Box( + modifier = Modifier + .fillMaxSize() + .background(Color.Black.copy(alpha = 0.5f)) + .clickable(enabled = false) {}, contentAlignment = Alignment.Center + ) { + val infiniteTransition = rememberInfiniteTransition(label = "") + + val rotation by infiniteTransition.animateFloat( + initialValue = 0f, targetValue = 360f, animationSpec = infiniteRepeatable( + animation = tween(1000, easing = LinearEasing), + repeatMode = RepeatMode.Restart + ), label = "" + ) + + Image(painter = painterResource(id = R.drawable.ellipse_exterior), + contentDescription = null, + modifier = Modifier.graphicsLayer { rotationZ = -rotation }) + + Image(painter = painterResource(id = R.drawable.ellipse_interior), + contentDescription = null, + modifier = Modifier.graphicsLayer { rotationZ = rotation }) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/permanent/permanent/ui/composeComponents/CustomLinearProgressIndicator.kt b/app/src/main/java/org/permanent/permanent/ui/composeComponents/CustomLinearProgressIndicator.kt new file mode 100644 index 00000000..98c78865 --- /dev/null +++ b/app/src/main/java/org/permanent/permanent/ui/composeComponents/CustomLinearProgressIndicator.kt @@ -0,0 +1,27 @@ +package org.permanent.permanent.ui.composeComponents + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp + +@Composable +fun CustomLinearProgressIndicator( + modifier: Modifier, width: Dp, backgroundColor: Color, foregroundColor: Brush, percent: Int +) { + Box( + modifier = modifier + .background(backgroundColor) + .width(width) + ) { + Box( + modifier = modifier + .background(foregroundColor) + .width(width * percent / 100) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/permanent/permanent/ui/composeComponents/CustomSnackbar.kt b/app/src/main/java/org/permanent/permanent/ui/composeComponents/CustomSnackbar.kt new file mode 100644 index 00000000..2a59c5ba --- /dev/null +++ b/app/src/main/java/org/permanent/permanent/ui/composeComponents/CustomSnackbar.kt @@ -0,0 +1,78 @@ +package org.permanent.permanent.ui.composeComponents + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.permanent.permanent.R + +@Composable +fun CustomSnackbar( + message: String, buttonText: String, onButtonClick: () -> Unit, modifier: Modifier = Modifier +) { + Box( + modifier = modifier + .fillMaxWidth() + .background( + color = colorResource(id = R.color.errorLight), + shape = RoundedCornerShape(size = 12.dp) + ) + .padding(24.dp), contentAlignment = Alignment.Center + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.fillMaxWidth() + ) { + Image( + painter = painterResource(id = R.drawable.ic_error_cercle), + contentDescription = "error", + modifier = Modifier.size(16.dp) + ) + + Text( + text = message, + color = colorResource(id = R.color.error500), + modifier = Modifier.weight(1f), + fontFamily = FontFamily(Font(R.font.open_sans_regular_ttf)), + fontSize = 14.sp, + lineHeight = 24.sp + ) + + TextButton(onClick = onButtonClick) { + Text( + text = buttonText, + color = colorResource(id = R.color.blue900), + fontFamily = FontFamily(Font(R.font.open_sans_semibold_ttf)), + fontSize = 14.sp, + lineHeight = 24.sp + ) + } + } + } +} + +@Preview +@Composable +fun CustomSnackbarPreview() { + CustomSnackbar(message = "The entered data is invalid", + buttonText = "OK", + onButtonClick = { /*TODO*/ }) +} diff --git a/app/src/main/java/org/permanent/permanent/ui/composeComponents/CustomTextButton.kt b/app/src/main/java/org/permanent/permanent/ui/composeComponents/CustomTextButton.kt new file mode 100644 index 00000000..c60a444e --- /dev/null +++ b/app/src/main/java/org/permanent/permanent/ui/composeComponents/CustomTextButton.kt @@ -0,0 +1,83 @@ +package org.permanent.permanent.ui.composeComponents + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.permanent.permanent.R + +@Composable +fun CustomTextButton( + style: ButtonColor = ButtonColor.LIGHT, + text: String, + icon: Painter? = null, + onButtonClick: () -> Unit +) { + Button(modifier = Modifier + .fillMaxWidth() + .height(56.dp), + shape = RoundedCornerShape(12.dp), + colors = ButtonDefaults.buttonColors(containerColor = Color.Transparent), + onClick = { onButtonClick() }) { + Box( + modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center + ) { + Row( + modifier = Modifier.fillMaxSize(), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = text, + color = if (style == ButtonColor.LIGHT) Color.White else colorResource(id = R.color.blue900), + fontSize = 14.sp, + lineHeight = 24.sp, + fontFamily = FontFamily(Font(R.font.open_sans_semibold_ttf)) + ) + + if (icon != null) { + Spacer(modifier = Modifier.width(16.dp)) + + Image( + painter = icon, colorFilter = ColorFilter.tint( + if (style == ButtonColor.LIGHT) Color.White else colorResource( + id = R.color.blue900 + ) + ), contentDescription = "Back", modifier = Modifier.size(16.dp) + ) + } + } + } + } +} + +@Preview +@Composable +fun TextButtonPreview() { + CustomTextButton( + text = stringResource(id = R.string.forgot_password), + ) { } +} \ No newline at end of file diff --git a/app/src/main/java/org/permanent/permanent/ui/composeComponents/FeedbackSnackbar.kt b/app/src/main/java/org/permanent/permanent/ui/composeComponents/FeedbackSnackbar.kt index 8d96d1e8..ad8d1fd9 100644 --- a/app/src/main/java/org/permanent/permanent/ui/composeComponents/FeedbackSnackbar.kt +++ b/app/src/main/java/org/permanent/permanent/ui/composeComponents/FeedbackSnackbar.kt @@ -66,7 +66,7 @@ fun FeedbackSnackbar( .padding(top = 36.dp) ) { Image( - painter = painterResource(id = if (isForSuccess) R.drawable.ic_done_green else R.drawable.ic_error), + painter = painterResource(id = if (isForSuccess) R.drawable.ic_done_green else R.drawable.ic_error_square), contentDescription = "Next", modifier = Modifier.size(16.dp) ) diff --git a/app/src/main/java/org/permanent/permanent/ui/composeComponents/StorageCard.kt b/app/src/main/java/org/permanent/permanent/ui/composeComponents/StorageCard.kt index 4795e08d..fc7288ba 100644 --- a/app/src/main/java/org/permanent/permanent/ui/composeComponents/StorageCard.kt +++ b/app/src/main/java/org/permanent/permanent/ui/composeComponents/StorageCard.kt @@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -20,7 +19,6 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.content.ContextCompat @@ -88,7 +86,7 @@ fun StorageCard( Box( modifier = Modifier.padding(16.dp) ) { - CustomProgressIndicator( + CustomLinearProgressIndicator( Modifier .clip(shape = RoundedCornerShape(3.dp)) .height(8.dp), @@ -106,23 +104,6 @@ fun StorageCard( } } -@Composable -fun CustomProgressIndicator( - modifier: Modifier, width: Dp, backgroundColor: Color, foregroundColor: Brush, percent: Int -) { - Box( - modifier = modifier - .background(backgroundColor) - .width(width) - ) { - Box( - modifier = modifier - .background(foregroundColor) - .width(width * percent / 100) - ) - } -} - enum class StorageCardStyle { LIGHT, COLORFUL diff --git a/app/src/main/java/org/permanent/permanent/ui/login/LoginFragment.kt b/app/src/main/java/org/permanent/permanent/ui/login/AuthenticationFragment.kt similarity index 50% rename from app/src/main/java/org/permanent/permanent/ui/login/LoginFragment.kt rename to app/src/main/java/org/permanent/permanent/ui/login/AuthenticationFragment.kt index 8901bd69..d318f64a 100644 --- a/app/src/main/java/org/permanent/permanent/ui/login/LoginFragment.kt +++ b/app/src/main/java/org/permanent/permanent/ui/login/AuthenticationFragment.kt @@ -6,51 +6,39 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.Toast +import androidx.compose.material3.MaterialTheme +import androidx.compose.ui.platform.ComposeView import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider -import androidx.navigation.fragment.findNavController -import org.permanent.permanent.Constants import org.permanent.permanent.EventType import org.permanent.permanent.EventsManager -import org.permanent.permanent.R -import org.permanent.permanent.databinding.FragmentLoginBinding import org.permanent.permanent.ui.PREFS_NAME import org.permanent.permanent.ui.PermanentBaseFragment import org.permanent.permanent.ui.PreferencesHelper import org.permanent.permanent.ui.activities.MainActivity import org.permanent.permanent.ui.archiveOnboarding.ArchiveOnboardingActivity -import org.permanent.permanent.viewmodels.ForgotPasswordViewModel +import org.permanent.permanent.ui.login.compose.AuthenticationContainer import org.permanent.permanent.viewmodels.LoginFragmentViewModel -class LoginFragment : PermanentBaseFragment() { +class AuthenticationFragment : PermanentBaseFragment() { private lateinit var viewModel: LoginFragmentViewModel - private lateinit var dialogViewModel: ForgotPasswordViewModel - private lateinit var binding: FragmentLoginBinding private lateinit var prefsHelper: PreferencesHelper override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { - binding = FragmentLoginBinding.inflate(inflater, container, false) - binding.executePendingBindings() - binding.lifecycleOwner = this viewModel = ViewModelProvider(this)[LoginFragmentViewModel::class.java] - binding.viewModel = viewModel prefsHelper = PreferencesHelper( requireContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) ) - return binding.root - } - - private val onErrorMessage = Observer { errorMessage -> - when (errorMessage) { - Constants.ERROR_MFA_TOKEN -> { - findNavController().navigate(R.id.action_loginFragment_to_codeVerificationFragment) + return ComposeView(requireContext()).apply { + setContent { + MaterialTheme { + AuthenticationContainer(viewModel) + } } - else -> Toast.makeText(context, errorMessage, Toast.LENGTH_LONG).show() } } @@ -65,44 +53,21 @@ class LoginFragment : PermanentBaseFragment() { activity?.finish() } - private val onStartSignUp = Observer { - findNavController().navigate(R.id.action_loginFragment_to_signUpFragment) - } - - private val onPasswordReset = Observer { - Toast.makeText( - context, getString( - R.string.login_screen_password_reset_message, - dialogViewModel.getCurrentEmail().value - ), Toast.LENGTH_LONG - ).show() - } - - private val onForgotPasswordRequest = Observer { - findNavController().navigate(R.id.action_loginFragment_to_forgotPasswordFragment) - } - private fun logEvents() { - EventsManager(requireContext()).setUserProfile(prefsHelper.getAccountId(), prefsHelper.getAccountEmail()) + EventsManager(requireContext()).setUserProfile( + prefsHelper.getAccountId(), prefsHelper.getAccountEmail() + ) EventsManager(requireContext()).sendToMixpanel(EventType.SignIn) } override fun connectViewModelEvents() { viewModel.getOnLoggedIn().observe(this, onLoggedIn) viewModel.getOnUserMissingDefaultArchive().observe(this, userMissingDefaultArchiveObserver) - viewModel.getOnSignUp().observe(this, onStartSignUp) - viewModel.getOnPasswordReset().observe(this, onPasswordReset) - viewModel.getOnForgotPasswordRequest().observe(this, onForgotPasswordRequest) - viewModel.getErrorMessage().observe(this, onErrorMessage) } override fun disconnectViewModelEvents() { viewModel.getOnLoggedIn().removeObserver(onLoggedIn) viewModel.getOnUserMissingDefaultArchive().removeObserver(userMissingDefaultArchiveObserver) - viewModel.getOnSignUp().removeObserver(onStartSignUp) - viewModel.getOnPasswordReset().removeObserver(onPasswordReset) - viewModel.getOnForgotPasswordRequest().removeObserver(onForgotPasswordRequest) - viewModel.getErrorMessage().removeObserver(onErrorMessage) } override fun onResume() { diff --git a/app/src/main/java/org/permanent/permanent/ui/login/ForgotPasswordFragment.kt b/app/src/main/java/org/permanent/permanent/ui/login/ForgotPasswordFragment.kt index 7abb6505..c53d498a 100644 --- a/app/src/main/java/org/permanent/permanent/ui/login/ForgotPasswordFragment.kt +++ b/app/src/main/java/org/permanent/permanent/ui/login/ForgotPasswordFragment.kt @@ -1,19 +1,14 @@ package org.permanent.permanent.ui.login import android.content.Context -import android.graphics.Typeface import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.TextView import android.widget.Toast -import androidx.core.content.ContextCompat import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider import androidx.navigation.fragment.findNavController -import com.google.android.material.snackbar.Snackbar -import org.permanent.permanent.R import org.permanent.permanent.databinding.FragmentForgotPasswordBinding import org.permanent.permanent.ui.PREFS_NAME import org.permanent.permanent.ui.PermanentBaseFragment @@ -42,20 +37,20 @@ class ForgotPasswordFragment : PermanentBaseFragment() { } private val onPasswordReset = Observer { - val message = getString( - R.string.login_screen_password_reset_message, viewModel.getCurrentEmail().value - ) - val snackBar = Snackbar.make(binding.root, message, SNACKBAR_DURATION) - val view: View = snackBar.view - context?.let { - view.setBackgroundColor(ContextCompat.getColor(it, R.color.deepGreen)) - snackBar.setTextColor(ContextCompat.getColor(it, R.color.paleGreen)) - } - val snackbarTextTextView = view.findViewById(R.id.snackbar_text) as TextView - snackbarTextTextView.setTypeface(snackbarTextTextView.typeface, Typeface.BOLD) - snackBar.show() - - onBackToSignIn.onChanged(null) +// val message = getString( +// R.string.login_screen_password_reset_message, viewModel.getCurrentEmail().value +// ) +// val snackBar = Snackbar.make(binding.root, message, SNACKBAR_DURATION) +// val view: View = snackBar.view +// context?.let { +// view.setBackgroundColor(ContextCompat.getColor(it, R.color.deepGreen)) +// snackBar.setTextColor(ContextCompat.getColor(it, R.color.paleGreen)) +// } +// val snackbarTextTextView = view.findViewById(R.id.snackbar_text) as TextView +// snackbarTextTextView.setTypeface(snackbarTextTextView.typeface, Typeface.BOLD) +// snackBar.show() +// +// onBackToSignIn.onChanged(null) } private val onBackToSignIn = Observer { diff --git a/app/src/main/java/org/permanent/permanent/ui/login/compose/AuthenticationContainer.kt b/app/src/main/java/org/permanent/permanent/ui/login/compose/AuthenticationContainer.kt new file mode 100644 index 00000000..b8f39c9e --- /dev/null +++ b/app/src/main/java/org/permanent/permanent/ui/login/compose/AuthenticationContainer.kt @@ -0,0 +1,217 @@ +@file:OptIn(ExperimentalFoundationApi::class) + +package org.permanent.permanent.ui.login.compose + +import android.content.Intent +import android.net.Uri +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.unit.times +import org.permanent.permanent.R +import org.permanent.permanent.ui.composeComponents.ButtonColor +import org.permanent.permanent.ui.composeComponents.CircularProgressIndicator +import org.permanent.permanent.ui.composeComponents.CustomTextButton +import org.permanent.permanent.viewmodels.LoginFragmentViewModel + +@Composable +fun AuthenticationContainer( + viewModel: LoginFragmentViewModel +) { + val pagerState = rememberPagerState( + initialPage = AuthPage.SIGN_IN.value, + pageCount = { AuthPage.values().size }) + val isTablet = viewModel.isTablet() + + val isBusyState by viewModel.isBusyState.collectAsState() + + val horizontalPaddingDp = if (isTablet) 64.dp else 32.dp + + Box( + modifier = Modifier.fillMaxSize() + ) { + Box( + modifier = Modifier + .fillMaxSize() + .background( + Brush.verticalGradient( + listOf( + colorResource(id = R.color.blue900), + colorResource(id = R.color.blueLighter) + ) + ) + ) + ) { + Row( + modifier = Modifier + .fillMaxSize() + .padding(horizontalPaddingDp) + ) { + if (isTablet) { + LeftSideView(horizontalPaddingDp) + + Spacer(modifier = Modifier.width(64.dp)) + } + + HorizontalPager( + state = pagerState, userScrollEnabled = false + ) { page -> + when (page) { + AuthPage.SIGN_IN.value -> { + SignInPage( + viewModel = viewModel, pagerState = pagerState + ) + } + + AuthPage.CODE_VERIFICATION.value -> { +// CodeVerificationPage( +// viewModel = viewModel, +// isTablet = isTablet, +// pagerState = pagerState +// ) + } + + AuthPage.SIGN_UP.value -> { +// SignUpPage( +// viewModel = viewModel, +// isTablet = isTablet, +// pagerState = pagerState +// ) + } + + AuthPage.FORGOT_PASSWORD.value -> { +// ForgotPasswordPage( +// viewModel = viewModel, +// isTablet = isTablet, +// pagerState = pagerState +// ) + } + } + } + } + } + + if (isBusyState) { + CircularProgressIndicator() + } + } +} + +@Composable +private fun LeftSideView( + horizontalPaddingDp: Dp +) { + val context = LocalContext.current + val configuration = LocalConfiguration.current + val oneThirdOfScreenDp = (configuration.screenWidthDp.dp - 2 * horizontalPaddingDp) / 3 + + Box( + modifier = Modifier + .width(2 * oneThirdOfScreenDp + horizontalPaddingDp) + .fillMaxHeight() + ) { + Image( + painter = painterResource(id = R.drawable.img_sign_in), + contentDescription = "Sign In Image", + modifier = Modifier + .fillMaxSize() + .clip(RoundedCornerShape(12.dp)), + contentScale = ContentScale.Crop + ) + + Box( + modifier = Modifier + .fillMaxWidth() + .padding( + start = horizontalPaddingDp, + end = horizontalPaddingDp, + bottom = horizontalPaddingDp + ) + .align(Alignment.BottomCenter) + .background( + colorResource(id = R.color.whiteLightTransparent), + shape = RoundedCornerShape(size = 12.dp) + ) + ) { + Column { + Text( + modifier = Modifier.padding(top = 32.dp, start = 32.dp, end = 32.dp), + text = stringResource(id = R.string.haadsma_dairy_truck_title).uppercase(), + color = colorResource(id = R.color.blue900), + fontSize = 10.sp, + lineHeight = 24.sp, + fontFamily = FontFamily(Font(R.font.open_sans_semibold_ttf)), + letterSpacing = 1.6.sp + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Text( + modifier = Modifier.padding(start = 32.dp, end = 32.dp), + text = stringResource(id = R.string.haadsma_dairy_truck_text), + color = colorResource(id = R.color.blue900), + fontSize = 14.sp, + lineHeight = 24.sp, + fontFamily = FontFamily(Font(R.font.open_sans_regular_ttf)), + ) + + Spacer(modifier = Modifier.height(12.dp)) + + Box( + modifier = Modifier + .width(220.dp) + .padding(start = 8.dp) + ) { + CustomTextButton( + style = ButtonColor.DARK, + text = stringResource(id = R.string.start_exploring_now), + icon = painterResource(id = R.drawable.ic_arrow_next_rounded_primary) + ) { + val intent = Intent( + Intent.ACTION_VIEW, Uri.parse("https://www.permanent.org/gallery") + ) + context.startActivity(intent) + } + } + + Spacer(modifier = Modifier.height(12.dp)) + } + } + } +} + +enum class AuthPage(val value: Int) { + SIGN_IN(0), CODE_VERIFICATION(1), SIGN_UP(2), FORGOT_PASSWORD(3) +} diff --git a/app/src/main/java/org/permanent/permanent/ui/login/compose/ForgotPasswordPage.kt b/app/src/main/java/org/permanent/permanent/ui/login/compose/ForgotPasswordPage.kt new file mode 100644 index 00000000..2518b0a0 --- /dev/null +++ b/app/src/main/java/org/permanent/permanent/ui/login/compose/ForgotPasswordPage.kt @@ -0,0 +1,2 @@ +package org.permanent.permanent.ui.login.compose + diff --git a/app/src/main/java/org/permanent/permanent/ui/login/compose/SignInPage.kt b/app/src/main/java/org/permanent/permanent/ui/login/compose/SignInPage.kt new file mode 100644 index 00000000..e18e7d93 --- /dev/null +++ b/app/src/main/java/org/permanent/permanent/ui/login/compose/SignInPage.kt @@ -0,0 +1,303 @@ +@file:OptIn(ExperimentalFoundationApi::class) + +package org.permanent.permanent.ui.login.compose + +import android.graphics.Rect +import android.view.ViewTreeObserver +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image +import androidx.compose.foundation.border +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.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.pager.PagerState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import kotlinx.coroutines.launch +import org.permanent.permanent.R +import org.permanent.permanent.ui.composeComponents.ButtonColor +import org.permanent.permanent.ui.composeComponents.CenteredTextAndIconButton +import org.permanent.permanent.ui.composeComponents.CustomSnackbar +import org.permanent.permanent.ui.composeComponents.CustomTextButton +import org.permanent.permanent.viewmodels.LoginFragmentViewModel + +@Composable +fun SignInPage( + viewModel: LoginFragmentViewModel, pagerState: PagerState +) { + val coroutineScope = rememberCoroutineScope() + val regularFont = FontFamily(Font(R.font.open_sans_regular_ttf)) + + val errorMessage by viewModel.showError.collectAsState() + + val keyboardController = LocalSoftwareKeyboardController.current + + var emailValueState by remember { + mutableStateOf( + TextFieldValue( + text = "" + ) + ) + } + + var passwordValueState by remember { + mutableStateOf( + TextFieldValue( + text = "" + ) + ) + } + + val keyboardState by keyboardAsState() + + Box( + modifier = Modifier.fillMaxSize() + ) { + Column( + modifier = Modifier.fillMaxSize() + ) { + Image( + painter = painterResource(id = R.drawable.img_logo), + contentDescription = "Logo", + modifier = Modifier.size(64.dp) + ) + + Spacer(modifier = Modifier.height(32.dp)) + + Text( + text = stringResource(id = R.string.sign_in_to_permanent), + fontSize = 32.sp, + lineHeight = 48.sp, + color = Color.White, + fontFamily = regularFont + ) + + Spacer(modifier = Modifier.height(32.dp)) + + if (keyboardState == Keyboard.Closed) { + Spacer(modifier = Modifier.weight(1f)) + } + + Column( + modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.Bottom + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .border(1.dp, Color.White.copy(alpha = 0.29f), RoundedCornerShape(10.dp)), + verticalAlignment = Alignment.CenterVertically + ) { + TextField( + value = emailValueState, + onValueChange = { value -> emailValueState = value }, + modifier = Modifier + .fillMaxWidth() + .height(56.dp) + .weight(1.0f), + singleLine = true, + placeholder = { + Text( + text = stringResource(id = R.string.email_address).uppercase(), + color = Color.White, + fontSize = 10.sp, + lineHeight = 16.sp, + fontFamily = regularFont + ) + }, + textStyle = TextStyle( + fontSize = 16.sp, + lineHeight = 24.sp, + fontFamily = regularFont, + fontWeight = FontWeight(600), + ), + colors = TextFieldDefaults.colors( + focusedTextColor = Color.White, + unfocusedTextColor = Color.White, + focusedContainerColor = colorResource(id = R.color.whiteUltraTransparent), + unfocusedContainerColor = colorResource(id = R.color.whiteUltraTransparent), + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + cursorColor = colorResource(id = R.color.blue400), + ) + ) + } + + Spacer(modifier = Modifier.height(16.dp)) + + Row( + modifier = Modifier + .fillMaxWidth() + .border(1.dp, Color.White.copy(alpha = 0.29f), RoundedCornerShape(10.dp)), + verticalAlignment = Alignment.CenterVertically + ) { + TextField( + value = passwordValueState, + onValueChange = { value -> passwordValueState = value }, + modifier = Modifier + .fillMaxWidth() + .height(56.dp) + .weight(1.0f), + singleLine = true, + placeholder = { + Text( + text = stringResource(id = R.string.password).uppercase(), + color = Color.White, + fontSize = 10.sp, + lineHeight = 16.sp, + fontFamily = regularFont + ) + }, + textStyle = TextStyle( + fontSize = 16.sp, + lineHeight = 24.sp, + fontFamily = regularFont, + fontWeight = FontWeight(600), + ), + colors = TextFieldDefaults.colors( + focusedTextColor = Color.White, + unfocusedTextColor = Color.White, + focusedContainerColor = Color.Transparent, + unfocusedContainerColor = colorResource(id = R.color.whiteUltraTransparent), + focusedIndicatorColor = colorResource(id = R.color.whiteUltraTransparent), + unfocusedIndicatorColor = Color.Transparent, + cursorColor = colorResource(id = R.color.blue400) + ), + visualTransformation = PasswordVisualTransformation() + ) + } + + Spacer(modifier = Modifier.height(16.dp)) + + CenteredTextAndIconButton( + buttonColor = ButtonColor.LIGHT, text = stringResource(id = R.string.sign_in) + ) { + keyboardController?.hide() + viewModel.login(emailValueState.text.trim(), passwordValueState.text.trim()) + } + + Spacer(modifier = Modifier.height(32.dp)) + + CustomTextButton(text = stringResource(id = R.string.forgot_password)) { + coroutineScope.launch { + pagerState.animateScrollToPage(AuthPage.FORGOT_PASSWORD.value) + } + } + + Spacer(modifier = Modifier.height(32.dp)) + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + HorizontalDivider( + color = colorResource(id = R.color.colorPrimary200), + modifier = Modifier.weight(1f) + ) + + Spacer(modifier = Modifier.width(16.dp)) + + Text( + text = stringResource(id = R.string.new_to_permanent).uppercase(), + color = colorResource(id = R.color.colorPrimary200), + fontSize = 10.sp, + lineHeight = 24.sp, + fontFamily = FontFamily(Font(R.font.open_sans_semibold_ttf)) + ) + + Spacer(modifier = Modifier.width(16.dp)) + + HorizontalDivider( + color = colorResource(id = R.color.colorPrimary200), + modifier = Modifier.weight(1f) + ) + } + + Spacer(modifier = Modifier.height(32.dp)) + + CenteredTextAndIconButton( + buttonColor = ButtonColor.TRANSPARENT, + text = stringResource(id = R.string.register), + icon = painterResource(id = R.drawable.ic_account_add_white) + ) { + coroutineScope.launch { + pagerState.animateScrollToPage(AuthPage.SIGN_UP.value) + } + } + } + } + + if (errorMessage.isNotEmpty()) { + CustomSnackbar(message = errorMessage, + buttonText = stringResource(id = R.string.ok), + modifier = Modifier.align(Alignment.BottomCenter), + onButtonClick = { + viewModel.clearError() + }) + } + } +} + +enum class Keyboard { + Opened, Closed +} + +@Composable +fun keyboardAsState(): State { + val keyboardState = remember { mutableStateOf(Keyboard.Closed) } + val view = LocalView.current + DisposableEffect(view) { + val onGlobalListener = ViewTreeObserver.OnGlobalLayoutListener { + val rect = Rect() + view.getWindowVisibleDisplayFrame(rect) + val screenHeight = view.rootView.height + val keypadHeight = screenHeight - rect.bottom + keyboardState.value = if (keypadHeight > screenHeight * 0.15) { + Keyboard.Opened + } else { + Keyboard.Closed + } + } + view.viewTreeObserver.addOnGlobalLayoutListener(onGlobalListener) + + onDispose { + view.viewTreeObserver.removeOnGlobalLayoutListener(onGlobalListener) + } + } + + return keyboardState +} \ No newline at end of file diff --git a/app/src/main/java/org/permanent/permanent/ui/login/compose/SignUpPage.kt b/app/src/main/java/org/permanent/permanent/ui/login/compose/SignUpPage.kt new file mode 100644 index 00000000..2518b0a0 --- /dev/null +++ b/app/src/main/java/org/permanent/permanent/ui/login/compose/SignUpPage.kt @@ -0,0 +1,2 @@ +package org.permanent.permanent.ui.login.compose + diff --git a/app/src/main/java/org/permanent/permanent/ui/storage/AddStorageFragment.kt b/app/src/main/java/org/permanent/permanent/ui/storage/AddStorageFragment.kt index f6573e58..8d8f9b37 100644 --- a/app/src/main/java/org/permanent/permanent/ui/storage/AddStorageFragment.kt +++ b/app/src/main/java/org/permanent/permanent/ui/storage/AddStorageFragment.kt @@ -138,7 +138,7 @@ class AddStorageFragment : PermanentBaseFragment(), TabLayout.OnTabSelectedListe alertDialog.setTitle(title) alertDialog.setMessage(text) alertDialog.setPositiveButton( - R.string.button_ok + R.string.ok ) { _, _ -> } val alert: AlertDialog = alertDialog.create() diff --git a/app/src/main/java/org/permanent/permanent/viewmodels/LoginFragmentViewModel.kt b/app/src/main/java/org/permanent/permanent/viewmodels/LoginFragmentViewModel.kt index 58b5bbd2..57a4a863 100644 --- a/app/src/main/java/org/permanent/permanent/viewmodels/LoginFragmentViewModel.kt +++ b/app/src/main/java/org/permanent/permanent/viewmodels/LoginFragmentViewModel.kt @@ -2,10 +2,9 @@ package org.permanent.permanent.viewmodels import android.app.Application import android.content.Context -import android.text.Editable -import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import org.permanent.permanent.BuildConfig +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import org.permanent.permanent.Constants import org.permanent.permanent.R import org.permanent.permanent.Validator @@ -24,69 +23,72 @@ class LoginFragmentViewModel(application: Application) : ObservableAndroidViewMo private val prefsHelper = PreferencesHelper( application.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) ) - private val errorMessage = MutableLiveData() - private val emailError = MutableLiveData() - private val passwordError = MutableLiveData() - private val isBusy = MutableLiveData() + private var isTablet = false private val onLoggedIn = SingleLiveEvent() private val onUserMissingDefaultArchive = SingleLiveEvent() - private val onSignUp = SingleLiveEvent() - private val onPasswordReset = SingleLiveEvent() - private val onForgotPasswordRequest = SingleLiveEvent() - private val currentEmail = MutableLiveData() - private val currentPassword = MutableLiveData() - val versionName = MutableLiveData( - application.getString( - R.string.version_text, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE.toString() - ) - ) + private val _isBusyState = MutableStateFlow(false) + val isBusyState: StateFlow = _isBusyState + private val _showError = MutableStateFlow("") + val showError: StateFlow = _showError + private var authRepository: IAuthenticationRepository = AuthenticationRepositoryImpl(application) private val archiveRepository: IArchiveRepository = ArchiveRepositoryImpl(application) - fun login() { - if (isBusy.value != null && isBusy.value!!) { + init { + isTablet = prefsHelper.isTablet() + } + + fun login(email: String, password: String) { + if (_isBusyState.value) { return } - currentEmail.value = currentEmail.value?.trim() - currentPassword.value = currentPassword.value?.trim() - - val email = currentEmail.value - val password = currentPassword.value - - if (!Validator.isValidEmail(null, email, emailError, null)) return - if (!Validator.isValidPassword(password, passwordError)) return - - isBusy.value = true - authRepository.login(email!!, - password!!, - object : IAuthenticationRepository.IOnLoginListener { - override fun onSuccess() { - isBusy.value = false - prefsHelper.saveUserLoggedIn(true) - - val defaultArchiveId = prefsHelper.getDefaultArchiveId() - if (defaultArchiveId == 0) { - onUserMissingDefaultArchive.call() - } else { - getArchive(defaultArchiveId) - } + + if (!Validator.isValidEmail(null, email, null, null) || !Validator.isValidPassword( + password, null)) { + _showError.value = appContext.getString(R.string.the_entered_data_is_invalid) + return + } + + _isBusyState.value = true + authRepository.login(email, password, object : IAuthenticationRepository.IOnLoginListener { + override fun onSuccess() { + _isBusyState.value = false + prefsHelper.saveUserLoggedIn(true) + + val defaultArchiveId = prefsHelper.getDefaultArchiveId() + if (defaultArchiveId == 0) { + onUserMissingDefaultArchive.call() + } else { + getArchive(defaultArchiveId) } + } - override fun onFailed(error: String?) { - isBusy.value = false - when (error) { - Constants.ERROR_UNKNOWN_SIGNIN -> errorMessage.value = - appContext.getString(R.string.login_bad_credentials) - Constants.ERROR_SERVER_ERROR -> errorMessage.value = - appContext.getString(R.string.server_error) - else -> { - prefsHelper.saveAccountEmail(email) // We save this here for verifyCode - errorMessage.value = error + override fun onFailed(error: String?) { + _isBusyState.value = false + when (error) { + Constants.ERROR_UNKNOWN_SIGNIN -> _showError.value = + appContext.getString(R.string.login_bad_credentials) + + Constants.ERROR_SERVER_ERROR -> _showError.value = + appContext.getString(R.string.server_error) + + Constants.ERROR_MFA_TOKEN -> { + prefsHelper.saveAccountEmail(email) // We save this here for verifyCode + // TODO: move to verifyCode page + } + else -> { + if (error != null) { + _showError.value = error } } } - }) + } + }) + } + + fun clearError() { + _showError.value = "" } fun getArchive(defaultArchiveId: Int) { @@ -109,54 +111,18 @@ class LoginFragmentViewModel(application: Application) : ObservableAndroidViewMo } } } - errorMessage.value = appContext.getString(R.string.generic_error) + _showError.value = appContext.getString(R.string.generic_error) } override fun onFailed(error: String?) { - error?.let { errorMessage.value = it } + error?.let { _showError.value = it } } }) } - fun signUp() { - onSignUp.call() - } - - fun onForgotPasswordBtnClick() { - if (isBusy.value != null && isBusy.value!!) { - return - } - - onForgotPasswordRequest.call() - } - - fun getCurrentEmail(): MutableLiveData = currentEmail - - fun getCurrentPassword(): MutableLiveData = currentPassword - - fun getEmailError(): LiveData = emailError - - fun getPasswordError(): LiveData = passwordError - - fun onEmailTextChanged(email: Editable) { - currentEmail.value = email.toString() - } - - fun onPasswordTextChanged(password: Editable) { - currentPassword.value = password.toString() - } - - fun getErrorMessage(): LiveData = errorMessage - - fun getIsBusy(): MutableLiveData = isBusy + fun isTablet() = isTablet fun getOnUserMissingDefaultArchive(): MutableLiveData = onUserMissingDefaultArchive fun getOnLoggedIn(): MutableLiveData = onLoggedIn - - fun getOnSignUp(): MutableLiveData = onSignUp - - fun getOnPasswordReset(): MutableLiveData = onPasswordReset - - fun getOnForgotPasswordRequest(): MutableLiveData = onForgotPasswordRequest } diff --git a/app/src/main/res/drawable/ic_account_add_white.xml b/app/src/main/res/drawable/ic_account_add_white.xml new file mode 100644 index 00000000..49fb7969 --- /dev/null +++ b/app/src/main/res/drawable/ic_account_add_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_error_cercle.xml b/app/src/main/res/drawable/ic_error_cercle.xml new file mode 100644 index 00000000..dcb5c928 --- /dev/null +++ b/app/src/main/res/drawable/ic_error_cercle.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_error.xml b/app/src/main/res/drawable/ic_error_square.xml similarity index 100% rename from app/src/main/res/drawable/ic_error.xml rename to app/src/main/res/drawable/ic_error_square.xml diff --git a/app/src/main/res/drawable/img_sign_in.png b/app/src/main/res/drawable/img_sign_in.png new file mode 100644 index 00000000..4ce8f535 Binary files /dev/null and b/app/src/main/res/drawable/img_sign_in.png differ diff --git a/app/src/main/res/layout/activity_update_app.xml b/app/src/main/res/layout/activity_update_app.xml index e4f8a546..1b275abb 100644 --- a/app/src/main/res/layout/activity_update_app.xml +++ b/app/src/main/res/layout/activity_update_app.xml @@ -70,7 +70,7 @@ android:layout_marginEnd="32dp" android:backgroundTint="@color/colorAccent" android:fontFamily="@font/open_sans_bold" - android:text="@string/button_update" + android:text="@string/update" android:textAllCaps="false" android:textColor="@color/white" android:textSize="21sp" diff --git a/app/src/main/res/layout/fragment_container_archives.xml b/app/src/main/res/layout/fragment_container_archives.xml index 2c25644c..6d20991e 100644 --- a/app/src/main/res/layout/fragment_container_archives.xml +++ b/app/src/main/res/layout/fragment_container_archives.xml @@ -49,7 +49,7 @@ android:fontFamily="@font/open_sans" android:letterSpacing="-0.01" android:onClick="@{() -> viewModel.onDoneBtnClick()}" - android:text="@string/button_done" + android:text="@string/done" android:textAllCaps="false" android:textColor="@color/white" app:layout_constraintBottom_toTopOf="@+id/frameLayoutContainer" diff --git a/app/src/main/res/layout/fragment_forgot_password.xml b/app/src/main/res/layout/fragment_forgot_password.xml index 08c47a97..1b60d21b 100644 --- a/app/src/main/res/layout/fragment_forgot_password.xml +++ b/app/src/main/res/layout/fragment_forgot_password.xml @@ -34,7 +34,7 @@ android:layout_height="wrap_content" android:layout_marginTop="16dp" android:fontFamily="@font/open_sans_bold" - android:text="@string/login_screen_forgot_password" + android:text="@string/forgot_password" android:textColor="@color/colorAccent" android:textSize="18sp" app:layout_constraintBottom_toBottomOf="parent" diff --git a/app/src/main/res/layout/fragment_login.xml b/app/src/main/res/layout/fragment_login.xml deleted file mode 100644 index 1f43648a..00000000 --- a/app/src/main/res/layout/fragment_login.xml +++ /dev/null @@ -1,269 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_share_preview.xml b/app/src/main/res/layout/fragment_share_preview.xml index 75625c67..4898c06d 100644 --- a/app/src/main/res/layout/fragment_share_preview.xml +++ b/app/src/main/res/layout/fragment_share_preview.xml @@ -236,7 +236,7 @@ android:layout_marginTop="4dp" android:layout_marginBottom="24dp" android:onClick="@{() -> viewModel.onOkBtnClick()}" - android:text="@string/button_ok" + android:text="@string/ok" android:visibility="@{viewModel.currentState == PreviewState.ERROR ? View.VISIBLE : View.GONE}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" diff --git a/app/src/main/res/layout/fragment_sign_up.xml b/app/src/main/res/layout/fragment_sign_up.xml index 42998e1d..f0048b15 100644 --- a/app/src/main/res/layout/fragment_sign_up.xml +++ b/app/src/main/res/layout/fragment_sign_up.xml @@ -36,7 +36,7 @@ android:layout_height="wrap_content" android:layout_marginTop="16dp" android:fontFamily="@font/open_sans_bold" - android:text="@string/login_screen_sign_up" + android:text="@string/sign_up" android:textColor="@color/colorAccent" android:textSize="18sp" app:layout_constraintBottom_toBottomOf="parent" @@ -220,7 +220,7 @@ android:background="@null" android:fontFamily="@font/open_sans_semibold" android:onClick="@{() -> viewModel.onSignInBtnClick()}" - android:text="@string/login_screen_sign_in" + android:text="@string/sign_in" android:textAllCaps="false" android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textColor="@color/white" diff --git a/app/src/main/res/layout/fragment_verification_code.xml b/app/src/main/res/layout/fragment_verification_code.xml index 4bad1a46..b9490304 100644 --- a/app/src/main/res/layout/fragment_verification_code.xml +++ b/app/src/main/res/layout/fragment_verification_code.xml @@ -84,7 +84,7 @@ android:fontFamily="@font/open_sans_bold" android:letterSpacing="-0.01" android:onClick="@{() -> viewModel.done()}" - android:text="@string/button_verify" + android:text="@string/verify" android:textAllCaps="false" android:textColor="@color/colorPrimary" android:textSize="15sp" diff --git a/app/src/main/res/navigation/login_navigation_graph.xml b/app/src/main/res/navigation/login_navigation_graph.xml index 440f188d..0d8a5a01 100644 --- a/app/src/main/res/navigation/login_navigation_graph.xml +++ b/app/src/main/res/navigation/login_navigation_graph.xml @@ -7,21 +7,14 @@ + android:name="org.permanent.permanent.ui.login.AuthenticationFragment" + android:label="LoginFragment"> - - #F4F6FD #465084 #010A3F + #FEE4E2 #FFFBFA #FECDCA #F04438 @@ -34,9 +35,11 @@ #12B76A #32D583 #FFFFFF + #D6FFFFFF #80FFFFFF #50FFFFFF #20FFFFFF + #0AFFFFFF #EBEBEB #B4B4B4 #DADADA diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8de8d90c..e874ba90 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -7,10 +7,9 @@ Permanent.org Email Password - Sign in - Forgot Password? - Sign up - New to Permanent? + Forgot password? + Sign up + New to Permanent? Missing authentication token. Please try again. Use Touch ID Or @@ -26,13 +25,20 @@ Edit Terms and Conditions Declined Terms and Conditions - An email with instructions to change your password has been sent at %s. Enter verification code Code No notifications - Verify - Done - Update + Verify + Update + Sign in + Sign in to\nPermanent + Email address + Password + Register + The entered data is invalid + Haadsma Dairy Truck | The Haadsma Dairy Archive + At Permanent, we celebrate our members\' hard work through our public gallery and archive spotlights. Explore our gallery and, when you\'re ready, publish or share your own public archive. + Start Exploring Now Verification code can not be empty Invalid verification code. Please try again. @@ -254,7 +260,7 @@ Share More Save - OK + OK Send Approve Deny