diff --git a/android/Omnivore/app/build.gradle b/android/Omnivore/app/build.gradle index 86c1239ca..cb1022569 100644 --- a/android/Omnivore/app/build.gradle +++ b/android/Omnivore/app/build.gradle @@ -128,6 +128,7 @@ dependencies { implementation 'androidx.compose.material3:material3-window-size-class:1.0.0-alpha16' implementation 'com.google.android.gms:play-services-auth:20.2.0' + implementation "com.google.accompanist:accompanist-systemuicontroller:0.25.1" } apollo { diff --git a/android/Omnivore/app/src/main/AndroidManifest.xml b/android/Omnivore/app/src/main/AndroidManifest.xml index ed213ee7f..bf65b39f7 100644 --- a/android/Omnivore/app/src/main/AndroidManifest.xml +++ b/android/Omnivore/app/src/main/AndroidManifest.xml @@ -29,7 +29,7 @@ @@ -38,26 +38,5 @@ - diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/MainActivity.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/MainActivity.kt index fbdd52768..eed5b92be 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/MainActivity.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/MainActivity.kt @@ -1,46 +1,17 @@ package app.omnivore.omnivore -import android.annotation.SuppressLint -import android.app.Activity -import android.content.Context import android.os.Bundle import android.view.View -import android.widget.Toast import androidx.activity.ComponentActivity -import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.setContent -import androidx.activity.result.ActivityResult -import androidx.activity.result.contract.ActivityResultContracts 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 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.GoogleApiAvailability -import com.google.android.gms.common.GooglePlayServicesUtil.isGooglePlayServicesAvailable -import com.google.android.gms.tasks.Task +import app.omnivore.omnivore.ui.auth.LoginViewModel +import app.omnivore.omnivore.ui.root.RootView import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.launch @AndroidEntryPoint class MainActivity : ComponentActivity() { @@ -51,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) -// } + RootView(viewModel = viewModel) } } @@ -69,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() -// } -//} diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/Routes.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/Routes.kt index c6ca342c8..47f70b2b3 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/Routes.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/Routes.kt @@ -1,6 +1,7 @@ package app.omnivore.omnivore sealed class Routes(val route: String) { - object Splash : Routes("Splash") - object EmailLogin : Routes("EmailLogin") + object Root : Routes("Root") + object Home : Routes("Home") + object Welcome : Routes("Welcome") } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ScreenMain.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ScreenMain.kt deleted file mode 100644 index a2248f02e..000000000 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ScreenMain.kt +++ /dev/null @@ -1,22 +0,0 @@ -package app.omnivore.omnivore - -import androidx.compose.runtime.Composable -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController -import app.omnivore.omnivore.screen.EmailLoginPage -import app.omnivore.omnivore.screen.SplashPage - -@Composable -fun ScreenMain(viewModel: LoginViewModel){ - val navController = rememberNavController() - - NavHost(navController = navController, startDestination = Routes.Splash.route) { - composable(Routes.Splash.route) { - SplashPage(viewModel = viewModel, navController = navController) - } - composable(Routes.EmailLogin.route) { - EmailLoginPage(viewModel = viewModel, navController = navController) - } - } -} diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/screen/EmailLogin.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/screen/EmailLogin.kt deleted file mode 100644 index 8d2784eb8..000000000 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/screen/EmailLogin.kt +++ /dev/null @@ -1,31 +0,0 @@ -package app.omnivore.omnivore.screen - -import android.content.Intent -import android.net.Uri -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.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.navigation.NavHostController -import app.omnivore.omnivore.EmailLoginView -import app.omnivore.omnivore.LoginViewModel - - -@Composable -fun EmailLoginPage(viewModel: LoginViewModel, navController: NavHostController) { - Column( - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier - .background(MaterialTheme.colorScheme.background) - .fillMaxSize() - .navigationBarsPadding() - ) { - EmailLoginView(viewModel) - } -} diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/screen/SplashPage.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/screen/SplashPage.kt deleted file mode 100644 index 8e79e6930..000000000 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/screen/SplashPage.kt +++ /dev/null @@ -1,78 +0,0 @@ -package app.omnivore.omnivore.screen - -import android.content.Intent -import android.net.Uri -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -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.foundation.text.ClickableText -import androidx.compose.material3.Button -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalUriHandler -import androidx.compose.ui.text.AnnotatedString -import androidx.core.content.ContextCompat.startActivity -import androidx.lifecycle.viewModelScope -import androidx.navigation.NavHostController -import app.omnivore.omnivore.EmailLoginView -import app.omnivore.omnivore.GoogleAuthButton -import app.omnivore.omnivore.LoginViewModel -import app.omnivore.omnivore.Routes -import com.google.android.gms.common.GoogleApiAvailability -import kotlinx.coroutines.launch - -@Composable -fun SplashPage(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() - - if (isGoogleAuthAvailable) { - GoogleAuthButton(viewModel) - } - - ContinueWithEmailButton(navController) - } -} - -@Composable -fun MoreInfoButton() { - val context = LocalContext.current - val intent = remember { Intent(Intent.ACTION_VIEW, Uri.parse("https://omnivore.app/about")) } - - ClickableText( - text = AnnotatedString("Learn More ->"), - onClick = { - context.startActivity(intent) - } - ) -} - -@Composable -fun ContinueWithEmailButton(navController: NavHostController) { - ClickableText( - text = AnnotatedString("Continue with Email ->"), - onClick = { - navController.navigate(Routes.EmailLogin.route) - } - ) -} diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailLogin.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailLogin.kt new file mode 100644 index 000000000..c30b53790 --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailLogin.kt @@ -0,0 +1,110 @@ +package app.omnivore.omnivore.ui.auth + +import android.annotation.SuppressLint +import android.widget.Toast +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.text.ClickableText +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.AnnotatedString +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.unit.dp + +@SuppressLint("CoroutineCreationDuringComposition") +@Composable +fun EmailLoginView(viewModel: LoginViewModel, onAuthProviderButtonTap: () -> Unit) { + var email by rememberSaveable { mutableStateOf("") } + var password by rememberSaveable { mutableStateOf("") } + + Row( + horizontalArrangement = Arrangement.Center + ) { + Spacer(modifier = Modifier.weight(1.0F)) + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + 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...") + } + + ClickableText( + text = AnnotatedString("Continue with Google/Apple ->"), + onClick = { onAuthProviderButtonTap() } + ) + } + Spacer(modifier = Modifier.weight(1.0F)) + } +} + +@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 + ) { + 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") + } + } +} diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/GoogleAuth.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/GoogleAuth.kt similarity index 93% rename from android/Omnivore/app/src/main/java/app/omnivore/omnivore/GoogleAuth.kt rename to android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/GoogleAuth.kt index ba8b9eae0..853787007 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/GoogleAuth.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/GoogleAuth.kt @@ -1,16 +1,15 @@ -package app.omnivore.omnivore +package app.omnivore.omnivore.ui.auth import android.app.Activity import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.ActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material3.Button -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import app.omnivore.omnivore.R 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.GoogleSignInOptions @@ -40,7 +39,7 @@ fun GoogleAuthButton(viewModel: LoginViewModel) { } } - GoogleSignInButton( + LoadingButtonWithIcon( text = "Continue with Google", loadingText = "Signing in...", isLoading = viewModel.isLoading, diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/GoogleSignInButton.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoadingButtonWithIcon.kt similarity index 83% rename from android/Omnivore/app/src/main/java/app/omnivore/omnivore/GoogleSignInButton.kt rename to android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoadingButtonWithIcon.kt index e80f8c0ce..03e273231 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/GoogleSignInButton.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoadingButtonWithIcon.kt @@ -1,10 +1,9 @@ -package app.omnivore.omnivore +package app.omnivore.omnivore.ui.auth import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* -import androidx.compose.material.* -import androidx.compose.material.Surface +import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -14,21 +13,21 @@ import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.unit.dp -@ExperimentalMaterialApi @Composable -fun GoogleSignInButton( +fun LoadingButtonWithIcon( text: String, - loadingText: String = "Signing in...", + loadingText: String, icon: Painter, isLoading: Boolean = false, shape: Shape = Shapes().medium, borderColor: Color = Color.LightGray, - backgroundColor: Color = MaterialTheme.colors.surface, - progressIndicatorColor: Color = MaterialTheme.colors.primary, + backgroundColor: Color = MaterialTheme.colorScheme.surface, + progressIndicatorColor: Color = MaterialTheme.colorScheme.primary, + modifier: Modifier = Modifier, onClick: () -> Unit ) { Surface( - modifier = Modifier.clickable( + modifier = modifier.clickable( enabled = !isLoading, onClick = onClick ), diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/LoginViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt similarity index 95% rename from android/Omnivore/app/src/main/java/app/omnivore/omnivore/LoginViewModel.kt rename to android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt index 14db1873b..b9186ed13 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/LoginViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt @@ -1,13 +1,12 @@ -package app.omnivore.omnivore +package app.omnivore.omnivore.ui.auth import android.content.ContentValues import android.util.Log -import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue -import androidx.compose.ui.platform.LocalUriHandler import androidx.lifecycle.* +import app.omnivore.omnivore.* import com.google.android.gms.auth.api.signin.GoogleSignInAccount import com.google.android.gms.common.api.ApiException import com.google.android.gms.tasks.Task @@ -16,6 +15,11 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch import javax.inject.Inject +enum class RegistrationState { + AuthProviderButtons, + EmailSignIn +} + @HiltViewModel class LoginViewModel @Inject constructor( private val datastoreRepo: DatastoreRepository diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/WelcomeScreen.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/WelcomeScreen.kt new file mode 100644 index 000000000..dadfd961f --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/WelcomeScreen.kt @@ -0,0 +1,165 @@ +package app.omnivore.omnivore.ui.auth + +import android.annotation.SuppressLint +import android.content.Intent +import android.net.Uri +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.text.ClickableText +import androidx.compose.material.ExperimentalMaterialApi +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.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import app.omnivore.omnivore.R +import app.omnivore.omnivore.Routes +import com.google.android.gms.common.GoogleApiAvailability +import kotlinx.coroutines.launch + +@Composable +fun WelcomeScreen(viewModel: LoginViewModel) { + Surface(modifier = Modifier.fillMaxSize(), color = Color(0xFFFBEAA8 )) { + WelcomeScreenContent(viewModel = viewModel) + } +} + +@SuppressLint("CoroutineCreationDuringComposition") +@Composable +fun WelcomeScreenContent(viewModel: LoginViewModel) { + var registrationState by rememberSaveable { mutableStateOf(RegistrationState.AuthProviderButtons) } + + val onRegistrationStateChange = { state: RegistrationState -> + registrationState = state + } + + val snackBarHostState = remember { SnackbarHostState() } + val coroutineScope = rememberCoroutineScope() + val focusManager = LocalFocusManager.current + + Column( + verticalArrangement = Arrangement.SpaceAround, + horizontalAlignment = Alignment.Start, + modifier = Modifier + .background(Color(0xFFFBEAA8)) + .fillMaxSize() + .navigationBarsPadding() + .padding(horizontal = 16.dp) + .clickable { focusManager.clearFocus() } + ) { + Spacer(modifier = Modifier.height(50.dp)) + Image( + painter = painterResource(id = R.drawable.ic_omnivore_name_logo), + contentDescription = "Omnivore Icon with Name" + ) + Spacer(modifier = Modifier.height(50.dp)) + + when(registrationState) { + RegistrationState.EmailSignIn -> { + EmailLoginView( + viewModel = viewModel, + onAuthProviderButtonTap = { + onRegistrationStateChange(RegistrationState.AuthProviderButtons) + } + ) + } + RegistrationState.AuthProviderButtons -> { + Text( + text = stringResource(id = R.string.welcome_title), + style = MaterialTheme.typography.headlineLarge + ) + + MoreInfoButton() + + Spacer(modifier = Modifier.height(50.dp)) + + AuthProviderView( + viewModel = viewModel, + onEmailButtonTap = { onRegistrationStateChange(RegistrationState.EmailSignIn) } + ) + } + } + + Spacer(modifier = Modifier.weight(1.0F)) + } + + 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) + } +} + +@Composable +fun AuthProviderView( + viewModel: LoginViewModel, + onEmailButtonTap: () -> Unit +) { + val isGoogleAuthAvailable: Boolean = GoogleApiAvailability + .getInstance() + .isGooglePlayServicesAvailable(LocalContext.current) == 0 + + Row( + horizontalArrangement = Arrangement.Center + ) { + Spacer(modifier = Modifier.weight(1.0F)) + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + if (isGoogleAuthAvailable) { + GoogleAuthButton(viewModel) + } + + LoadingButtonWithIcon( + text = "Continue with Apple", + loadingText = "Signing in...", + icon = painterResource(id = R.drawable.ic_logo_apple), + modifier = Modifier.padding(vertical = 6.dp), + onClick = {} + ) + + ClickableText( + text = AnnotatedString("Continue with Email ->"), + onClick = { onEmailButtonTap() } + ) + } + Spacer(modifier = Modifier.weight(1.0F)) + } +} + +@Composable +fun MoreInfoButton() { + val context = LocalContext.current + val intent = remember { Intent(Intent.ACTION_VIEW, Uri.parse("https://omnivore.app/about")) } + + ClickableText( + text = AnnotatedString("Learn More ->"), + style = MaterialTheme.typography.titleLarge, + onClick = { + context.startActivity(intent) + }, + modifier = Modifier.padding(vertical = 6.dp) + ) +} + diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeView.kt new file mode 100644 index 000000000..11ce48e54 --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeView.kt @@ -0,0 +1,36 @@ +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.foundation.layout.padding +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.compose.ui.unit.dp +import androidx.navigation.NavHostController +import app.omnivore.omnivore.ui.auth.LoginViewModel + +@Composable +fun HomeView(viewModel: LoginViewModel) { + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .background(MaterialTheme.colorScheme.background) + .fillMaxSize() + .padding(horizontal = 6.dp) + ) { + Text("You have a valid auth token. Nice. Go save something in Chrome!") + + Button(onClick = { + viewModel.logout() + }) { + Text(text = "Logout") + } + } +} diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/root/RootView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/root/RootView.kt new file mode 100644 index 000000000..9bdd36a04 --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/root/RootView.kt @@ -0,0 +1,19 @@ +package app.omnivore.omnivore.ui.root + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState +import app.omnivore.omnivore.ui.auth.LoginViewModel +import app.omnivore.omnivore.ui.auth.WelcomeScreen +import app.omnivore.omnivore.ui.home.HomeView + +@Composable +fun RootView(viewModel: LoginViewModel) { + val hasAuthToken: Boolean by viewModel.hasAuthTokenLiveData.observeAsState(false) + + if (hasAuthToken) { + HomeView(viewModel = viewModel) + } else { + WelcomeScreen(viewModel = viewModel) + } +} diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/NewFlowActivity.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/NewFlowActivity.kt similarity index 72% rename from android/Omnivore/app/src/main/java/app/omnivore/omnivore/NewFlowActivity.kt rename to android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/NewFlowActivity.kt index 0748fda0f..571a622f8 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/NewFlowActivity.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/NewFlowActivity.kt @@ -1,6 +1,7 @@ -package app.omnivore.omnivore +package app.omnivore.omnivore.ui.save import androidx.compose.material.ExperimentalMaterialApi +import app.omnivore.omnivore.ui.save.SaveSheetActivity // Not sure why we need this class, but directly opening SaveSheetActivity // causes the app to crash. diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/SaveContent.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveContent.kt similarity index 96% rename from android/Omnivore/app/src/main/java/app/omnivore/omnivore/SaveContent.kt rename to android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveContent.kt index 4a569178b..62a355061 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/SaveContent.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveContent.kt @@ -8,9 +8,9 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.material.* -import androidx.compose.material.MaterialTheme.colors import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.unit.dp +import app.omnivore.omnivore.ui.save.SaveViewModel import kotlinx.coroutines.launch @Composable diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/SaveSheetActivity.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveSheetActivity.kt similarity index 98% rename from android/Omnivore/app/src/main/java/app/omnivore/omnivore/SaveSheetActivity.kt rename to android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveSheetActivity.kt index 521e5c3df..4ffc0fcae 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/SaveSheetActivity.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveSheetActivity.kt @@ -1,4 +1,4 @@ -package app.omnivore.omnivore +package app.omnivore.omnivore.ui.save import android.content.ContentValues import android.content.Intent @@ -18,6 +18,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp +import app.omnivore.omnivore.SaveContent import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/SaveViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveViewModel.kt similarity index 92% rename from android/Omnivore/app/src/main/java/app/omnivore/omnivore/SaveViewModel.kt rename to android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveViewModel.kt index 1cbf43078..710554047 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/SaveViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveViewModel.kt @@ -1,4 +1,4 @@ -package app.omnivore.omnivore +package app.omnivore.omnivore.ui.save import android.content.ContentValues import android.util.Log @@ -7,6 +7,9 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import app.omnivore.omnivore.Constants +import app.omnivore.omnivore.DatastoreKeys +import app.omnivore.omnivore.DatastoreRepository import app.omnivore.omnivore.graphql.generated.SaveUrlMutation import app.omnivore.omnivore.graphql.generated.type.SaveUrlInput import com.apollographql.apollo3.ApolloClient diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/theme/Theme.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/theme/Theme.kt index f55b9dea7..38c6bc295 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/theme/Theme.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/theme/Theme.kt @@ -82,7 +82,7 @@ fun OmnivoreTheme( MaterialTheme( colorScheme = colorScheme, -// typography = Typography, + typography = Typography, // shapes = Shapes, content = content ) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/theme/Type.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/theme/Type.kt index 5a70fbaec..86df5fe88 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/theme/Type.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/theme/Type.kt @@ -1,28 +1,25 @@ package app.omnivore.omnivore.ui.theme -import androidx.compose.material.Typography -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.sp +import androidx.compose.material3.Typography + +val Typography = Typography() + +// ex: https://github.com/android/compose-samples/blob/main/Jetchat/app/src/main/java/com/example/compose/jetchat/theme/Typography.kt +//val Typography = Typography( +//displayLarge: TextStyle = TypographyTokens.DisplayLarge, +//displayMedium: TextStyle = TypographyTokens.DisplayMedium, +//displaySmall: TextStyle = TypographyTokens.DisplaySmall, +//headlineLarge: TextStyle = TypographyTokens.HeadlineLarge, +//headlineMedium: TextStyle = TypographyTokens.HeadlineMedium, +//headlineSmall: TextStyle = TypographyTokens.HeadlineSmall, +//titleLarge: TextStyle = TypographyTokens.TitleLarge, +//titleMedium: TextStyle = TypographyTokens.TitleMedium, +//titleSmall: TextStyle = TypographyTokens.TitleSmall, +//bodyLarge: TextStyle = TypographyTokens.BodyLarge, +//bodyMedium: TextStyle = TypographyTokens.BodyMedium, +//bodySmall: TextStyle = TypographyTokens.BodySmall, +//labelLarge: TextStyle = TypographyTokens.LabelLarge, +//labelMedium: TextStyle = TypographyTokens.LabelMedium, +//labelSmall: TextStyle = TypographyTokens.LabelSmall +//) -// Set of Material typography styles to start with -val Typography = Typography( - body1 = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 16.sp - ) - /* Other default text styles to override - button = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.W500, - fontSize = 14.sp - ), - caption = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 12.sp - ) - */ -) diff --git a/android/Omnivore/app/src/main/res/drawable/ic_logo_apple.xml b/android/Omnivore/app/src/main/res/drawable/ic_logo_apple.xml new file mode 100644 index 000000000..84a25baf6 --- /dev/null +++ b/android/Omnivore/app/src/main/res/drawable/ic_logo_apple.xml @@ -0,0 +1,9 @@ + + + diff --git a/android/Omnivore/app/src/main/res/drawable/ic_omnivore_name_logo.xml b/android/Omnivore/app/src/main/res/drawable/ic_omnivore_name_logo.xml new file mode 100644 index 000000000..3129b743c --- /dev/null +++ b/android/Omnivore/app/src/main/res/drawable/ic_omnivore_name_logo.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + diff --git a/android/Omnivore/app/src/main/res/values/strings.xml b/android/Omnivore/app/src/main/res/values/strings.xml index 2f4570ea1..cf0a10906 100644 --- a/android/Omnivore/app/src/main/res/values/strings.xml +++ b/android/Omnivore/app/src/main/res/values/strings.xml @@ -1,4 +1,5 @@ Omnivore 590528454690-mpmbvcb4c3ifmnojmrultt37eo8f64at.apps.googleusercontent.com + Everything you read.\nSafe, organized, and\neasy to share. diff --git a/android/Omnivore/app/src/main/res/values/themes.xml b/android/Omnivore/app/src/main/res/values/themes.xml index f5b5ca333..fa82596de 100644 --- a/android/Omnivore/app/src/main/res/values/themes.xml +++ b/android/Omnivore/app/src/main/res/values/themes.xml @@ -1,8 +1,7 @@ -