move login fields code out of main activity and into email view

This commit is contained in:
Satindar Dhillon 2022-08-22 10:39:29 -07:00
parent 53b8f7eeac
commit a50e39efc3
6 changed files with 204 additions and 223 deletions

View file

@ -1,38 +1,17 @@
package app.omnivore.omnivore
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import app.omnivore.omnivore.ui.theme.OmnivoreTheme
import androidx.compose.runtime.livedata.observeAsState
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import app.omnivore.omnivore.ui.auth.GoogleAuthButton
import app.omnivore.omnivore.ui.auth.LoginViewModel
import app.omnivore.omnivore.ui.main.ScreenMain
import com.google.android.gms.common.GoogleApiAvailability
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@ -43,11 +22,7 @@ class MainActivity : ComponentActivity() {
setContent {
OmnivoreTheme {
// A surface container using the 'background' color from the theme
ScreenMain(viewModel = viewModel)
// Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
// WelcomeView(viewModel)
// }
}
}
@ -61,170 +36,3 @@ class MainActivity : ComponentActivity() {
}
}
}
@Composable
fun WelcomeView(viewModel: LoginViewModel) {
val hasAuthToken: Boolean by viewModel.hasAuthTokenLiveData.observeAsState(false)
if (hasAuthToken) {
LoggedInView(viewModel)
} else {
LoginView(viewModel)
}
}
@Composable
fun LoginView(viewModel: LoginViewModel) {
val isGoogleAuthAvailable: Boolean = GoogleApiAvailability
.getInstance()
.isGooglePlayServicesAvailable(LocalContext.current) == 0
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
) {
if (isGoogleAuthAvailable) {
GoogleAuthButton(viewModel)
}
EmailLoginView(viewModel)
}
}
@Composable
fun LoggedInView(viewModel: LoginViewModel) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
.fillMaxSize()
) {
Text("You have a valid auth token. Nice. Go save something in Chrome!")
Button(onClick = {
viewModel.logout()
}) {
Text(text = "Logout")
}
}
}
@SuppressLint("CoroutineCreationDuringComposition")
@Composable
fun EmailLoginView(viewModel: LoginViewModel) {
var email by rememberSaveable { mutableStateOf("") }
var password by rememberSaveable { mutableStateOf("") }
val focusManager = LocalFocusManager.current
val snackBarHostState = remember { SnackbarHostState() }
val coroutineScope = rememberCoroutineScope()
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
.fillMaxSize()
.clickable { focusManager.clearFocus() }
) {
LoginFields(
email,
password,
onEmailChange = { email = it },
onPasswordChange = { password = it },
onLoginClick = { viewModel.login(email, password) }
)
// TODO: add a activity indicator (maybe after a delay?)
if (viewModel.isLoading) {
Text("Loading...")
}
if (viewModel.errorMessage != null) {
coroutineScope.launch {
val result = snackBarHostState
.showSnackbar(
viewModel.errorMessage!!,
actionLabel = "Dismiss",
duration = SnackbarDuration.Indefinite
)
when (result) {
SnackbarResult.ActionPerformed -> viewModel.resetErrorMessage()
}
}
SnackbarHost(hostState = snackBarHostState)
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LoginFields(
email: String,
password: String,
onEmailChange: (String) -> Unit,
onPasswordChange: (String) -> Unit,
onLoginClick: () -> Unit
) {
val context = LocalContext.current
val focusManager = LocalFocusManager.current
Column(
modifier = Modifier
.fillMaxWidth()
.height(300.dp),
verticalArrangement = Arrangement.spacedBy(25.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Never miss a great read")
OutlinedTextField(
value = email,
placeholder = { Text(text = "user@email.com") },
label = { Text(text = "email") },
onValueChange = onEmailChange,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() })
)
OutlinedTextField(
value = password,
placeholder = { Text(text = "password") },
label = { Text(text = "password") },
onValueChange = onPasswordChange,
visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() })
)
Button(onClick = {
if (email.isNotBlank() && password.isNotBlank()) {
onLoginClick()
focusManager.clearFocus()
} else {
Toast.makeText(
context,
"Please enter an email address and password.",
Toast.LENGTH_SHORT
).show()
}
}) {
Text(text = "Login")
}
}
}
//@Preview(
// uiMode = Configuration.UI_MODE_NIGHT_YES,
// showBackground = true,
// name = "Dark Mode"
//)
//@Preview(showBackground = true)
//@Composable
//fun DefaultPreview() {
// OmnivoreTheme {
// LoginView()
// }
//}

View file

@ -1,6 +1,8 @@
package app.omnivore.omnivore
sealed class Routes(val route: String) {
object Splash : Routes("Splash")
object Root : Routes("Root")
object Home : Routes("Home")
object Welcome : Routes("Welcome")
object EmailLogin : Routes("EmailLogin")
}

View file

@ -1,19 +1,27 @@
package app.omnivore.omnivore.ui.auth
import android.annotation.SuppressLint
import android.widget.Toast
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import app.omnivore.omnivore.EmailLoginView
import kotlinx.coroutines.launch
@Composable
fun EmailLoginPage(viewModel: LoginViewModel, navController: NavHostController) {
fun EmailLoginViewContainer(viewModel: LoginViewModel, navController: NavHostController) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
@ -25,3 +33,108 @@ fun EmailLoginPage(viewModel: LoginViewModel, navController: NavHostController)
EmailLoginView(viewModel)
}
}
@SuppressLint("CoroutineCreationDuringComposition")
@Composable
fun EmailLoginView(viewModel: LoginViewModel) {
var email by rememberSaveable { mutableStateOf("") }
var password by rememberSaveable { mutableStateOf("") }
val focusManager = LocalFocusManager.current
val snackBarHostState = remember { SnackbarHostState() }
val coroutineScope = rememberCoroutineScope()
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
.fillMaxSize()
.clickable { focusManager.clearFocus() }
) {
LoginFields(
email,
password,
onEmailChange = { email = it },
onPasswordChange = { password = it },
onLoginClick = { viewModel.login(email, password) }
)
// TODO: add a activity indicator (maybe after a delay?)
if (viewModel.isLoading) {
Text("Loading...")
}
if (viewModel.errorMessage != null) {
coroutineScope.launch {
val result = snackBarHostState
.showSnackbar(
viewModel.errorMessage!!,
actionLabel = "Dismiss",
duration = SnackbarDuration.Indefinite
)
when (result) {
SnackbarResult.ActionPerformed -> viewModel.resetErrorMessage()
}
}
SnackbarHost(hostState = snackBarHostState)
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LoginFields(
email: String,
password: String,
onEmailChange: (String) -> Unit,
onPasswordChange: (String) -> Unit,
onLoginClick: () -> Unit
) {
val context = LocalContext.current
val focusManager = LocalFocusManager.current
Column(
modifier = Modifier
.fillMaxWidth()
.height(300.dp),
verticalArrangement = Arrangement.spacedBy(25.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Never miss a great read")
OutlinedTextField(
value = email,
placeholder = { Text(text = "user@email.com") },
label = { Text(text = "email") },
onValueChange = onEmailChange,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() })
)
OutlinedTextField(
value = password,
placeholder = { Text(text = "password") },
label = { Text(text = "password") },
onValueChange = onPasswordChange,
visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() })
)
Button(onClick = {
if (email.isNotBlank() && password.isNotBlank()) {
onLoginClick()
focusManager.clearFocus()
} else {
Toast.makeText(
context,
"Please enter an email address and password.",
Toast.LENGTH_SHORT
).show()
}
}) {
Text(text = "Login")
}
}
}

View file

@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
@ -21,27 +22,30 @@ import app.omnivore.omnivore.Routes
import com.google.android.gms.common.GoogleApiAvailability
@Composable
fun SplashPage(viewModel: LoginViewModel, navController: NavHostController) {
fun WelcomeScreen(viewModel: LoginViewModel, navController: NavHostController) {
val isGoogleAuthAvailable: Boolean = GoogleApiAvailability
.getInstance()
.isGooglePlayServicesAvailable(LocalContext.current) == 0
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
.fillMaxSize()
.navigationBarsPadding()
) {
Text("Never miss a great read")
MoreInfoButton()
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
Column(
verticalArrangement = Arrangement.Center,
if (isGoogleAuthAvailable) {
GoogleAuthButton(viewModel)
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
.fillMaxSize()
.navigationBarsPadding()
) {
Text("Never miss a great read")
MoreInfoButton()
if (isGoogleAuthAvailable) {
GoogleAuthButton(viewModel)
}
ContinueWithEmailButton(navController)
}
ContinueWithEmailButton(navController)
}
}

View file

@ -0,0 +1,33 @@
package app.omnivore.omnivore.ui.home
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController
import app.omnivore.omnivore.ui.auth.LoginViewModel
@Composable
fun HomeView(viewModel: LoginViewModel, navController: NavHostController) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
.fillMaxSize()
) {
Text("You have a valid auth token. Nice. Go save something in Chrome!")
Button(onClick = {
viewModel.logout()
}) {
Text(text = "Logout")
}
}
}

View file

@ -1,24 +1,45 @@
package app.omnivore.omnivore.ui.main
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import app.omnivore.omnivore.Routes
import app.omnivore.omnivore.ui.auth.EmailLoginPage
import app.omnivore.omnivore.ui.auth.SplashPage
import app.omnivore.omnivore.ui.auth.EmailLoginViewContainer
import app.omnivore.omnivore.ui.auth.LoginViewModel
import app.omnivore.omnivore.ui.auth.WelcomeScreen
import app.omnivore.omnivore.ui.home.HomeView
@Composable
fun ScreenMain(viewModel: LoginViewModel){
fun ScreenMain(viewModel: LoginViewModel) {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = Routes.Splash.route) {
composable(Routes.Splash.route) {
SplashPage(viewModel = viewModel, navController = navController)
NavHost(navController = navController, startDestination = Routes.Root.route) {
composable(Routes.Root.route) {
RootView(viewModel = viewModel, navController = navController)
}
composable(Routes.Welcome.route) {
WelcomeScreen(viewModel = viewModel, navController = navController)
}
composable(Routes.Home.route) {
HomeView(viewModel = viewModel, navController = navController)
}
composable(Routes.EmailLogin.route) {
EmailLoginPage(viewModel = viewModel, navController = navController)
EmailLoginViewContainer(viewModel = viewModel, navController = navController)
}
}
}
@Composable
fun RootView(viewModel: LoginViewModel, navController: NavHostController) {
val hasAuthToken: Boolean by viewModel.hasAuthTokenLiveData.observeAsState(false)
if (hasAuthToken) {
HomeView(viewModel = viewModel, navController = navController)
} else {
WelcomeScreen(viewModel = viewModel, navController = navController)
}
}