Merge pull request #1118 from omnivore-app/feature/android-login-screens

Draft: Android Welcome Screen
This commit is contained in:
Jackson Harper 2022-08-24 14:08:14 +08:00 committed by GitHub
commit 76ac1fd3c0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 435 additions and 407 deletions

View file

@ -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 {

View file

@ -29,7 +29,7 @@
</activity>
<activity
android:name=".NewFlowActivity"
android:name=".ui.save.NewFlowActivity"
android:exported="true"
android:theme="@style/Theme.AppCompat.Translucent">
<intent-filter>
@ -38,26 +38,5 @@
<data android:mimeType="text/*" />
</intent-filter>
</activity>
<!--
<activity
android:name=".SaveActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/pdf" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/*" />
</intent-filter>
</activity>
-->
</application>
</manifest>

View file

@ -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()
// }
//}

View file

@ -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")
}

View file

@ -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)
}
}
}

View file

@ -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)
}
}

View file

@ -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)
}
)
}

View file

@ -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")
}
}
}

View file

@ -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,

View file

@ -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
),

View file

@ -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

View file

@ -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)
)
}

View file

@ -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")
}
}
}

View file

@ -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)
}
}

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -82,7 +82,7 @@ fun OmnivoreTheme(
MaterialTheme(
colorScheme = colorScheme,
// typography = Typography,
typography = Typography,
// shapes = Shapes,
content = content
)

View file

@ -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
)
*/
)

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M21.281,18.424C20.933,19.228 20.521,19.967 20.044,20.647C19.394,21.574 18.861,22.216 18.451,22.572C17.815,23.157 17.134,23.457 16.404,23.474C15.88,23.474 15.248,23.325 14.513,23.022C13.775,22.721 13.097,22.572 12.477,22.572C11.827,22.572 11.129,22.721 10.383,23.022C9.636,23.325 9.035,23.482 8.574,23.498C7.875,23.528 7.177,23.22 6.481,22.572C6.037,22.185 5.481,21.52 4.815,20.579C4.101,19.574 3.513,18.408 3.053,17.08C2.56,15.644 2.313,14.254 2.313,12.909C2.313,11.367 2.646,10.038 3.313,8.924C3.838,8.029 4.535,7.323 5.408,6.805C6.281,6.287 7.224,6.023 8.24,6.006C8.796,6.006 9.525,6.178 10.431,6.516C11.334,6.855 11.914,7.027 12.168,7.027C12.358,7.027 13.002,6.826 14.095,6.425C15.127,6.053 15.999,5.899 16.713,5.96C18.648,6.116 20.102,6.879 21.069,8.253C19.338,9.302 18.482,10.77 18.499,12.654C18.515,14.122 19.047,15.343 20.094,16.313C20.568,16.763 21.097,17.111 21.687,17.358C21.559,17.728 21.424,18.083 21.281,18.424ZM16.843,0.96C16.843,2.11 16.422,3.184 15.585,4.178C14.574,5.36 13.351,6.043 12.025,5.935C12.008,5.797 11.999,5.652 11.999,5.5C11.999,4.395 12.479,3.213 13.333,2.247C13.759,1.758 14.301,1.351 14.959,1.027C15.615,0.707 16.235,0.53 16.818,0.5C16.835,0.654 16.843,0.808 16.843,0.96V0.96Z"
android:fillColor="#000000"/>
</vector>

View file

@ -0,0 +1,35 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="129dp"
android:height="26dp"
android:viewportWidth="129"
android:viewportHeight="26">
<path
android:pathData="M43.71,13.11C43.71,9.58 41.52,7.57 38.66,7.57C35.78,7.57 33.61,9.58 33.61,13.11C33.61,16.63 35.78,18.66 38.66,18.66C41.52,18.66 43.71,16.64 43.71,13.11ZM41.4,13.11C41.4,15.4 40.31,16.64 38.66,16.64C37,16.64 35.92,15.4 35.92,13.11C35.92,10.83 37,9.59 38.66,9.59C40.31,9.59 41.4,10.83 41.4,13.11Z"
android:fillColor="#3D3D3D"/>
<path
android:pathData="M47.63,7.72V18.51H49.84V11.46H49.93L52.72,18.46H54.23L57.02,11.49H57.11V18.51H59.32V7.72H56.51L53.54,14.97H53.41L50.44,7.72H47.63Z"
android:fillColor="#3D3D3D"/>
<path
android:pathData="M72.45,7.72H70.18V14.5H70.09L65.43,7.72H63.43V18.51H65.71V11.72H65.79L70.48,18.51H72.45V7.72Z"
android:fillColor="#3D3D3D"/>
<path
android:pathData="M78.84,7.72H76.56V18.51H78.84V7.72Z"
android:fillColor="#3D3D3D"/>
<path
android:pathData="M84.9,7.72H82.36L86.09,18.51H89.03L92.75,7.72H90.22L87.6,15.92H87.5L84.9,7.72Z"
android:fillColor="#3D3D3D"/>
<path
android:pathData="M105.58,13.11C105.58,9.58 103.39,7.57 100.52,7.57C97.65,7.57 95.47,9.58 95.47,13.11C95.47,16.63 97.65,18.66 100.52,18.66C103.39,18.66 105.58,16.64 105.58,13.11ZM103.26,13.11C103.26,15.4 102.18,16.64 100.52,16.64C98.86,16.64 97.78,15.4 97.78,13.11C97.78,10.83 98.86,9.59 100.52,9.59C102.18,9.59 103.26,10.83 103.26,13.11Z"
android:fillColor="#3D3D3D"/>
<path
android:pathData="M109.49,18.51H111.77V14.68H113.44L115.48,18.51H118L115.71,14.31C116.94,13.79 117.62,12.72 117.62,11.24C117.62,9.1 116.2,7.72 113.75,7.72H109.49V18.51ZM111.77,12.85V9.58H113.31C114.63,9.58 115.27,10.17 115.27,11.24C115.27,12.31 114.63,12.85 113.32,12.85H111.77Z"
android:fillColor="#3D3D3D"/>
<path
android:pathData="M121.45,18.51H128.75V16.63H123.74V14.05H128.35V12.17H123.74V9.6H128.73V7.72H121.45V18.51Z"
android:fillColor="#3D3D3D"/>
<path
android:pathData="M8.72,17.73V10.37C8.72,9.74 9.46,9.39 9.95,9.82L12.22,13.14C12.69,13.52 13.34,13.52 13.81,13.14L16.03,9.85C16.52,9.44 17.26,9.77 17.26,10.4V14.29C17.26,16.17 18.52,17.71 20.41,17.71H20.46C22.07,17.71 23.47,16.61 23.85,15.05C24.04,14.23 24.21,13.38 24.21,12.73C24.18,6.29 18.74,1.42 12.22,1.86C6.69,2.24 2.23,6.7 1.85,12.23C1.41,18.75 6.56,24.19 13.02,24.19"
android:strokeWidth="2.18182"
android:fillColor="#00000000"
android:strokeColor="#3D3D3D"/>
</vector>

View file

@ -1,4 +1,5 @@
<resources>
<string name="app_name">Omnivore</string>
<string name="gcp_id">590528454690-mpmbvcb4c3ifmnojmrultt37eo8f64at.apps.googleusercontent.com</string>
<string name="welcome_title">Everything you read.\nSafe, organized, and\neasy to share.</string>
</resources>

View file

@ -1,8 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Omnivore" parent="android:Theme.Material.Light">
<style name="Theme.Omnivore" parent="android:Theme.Material.Light.NoActionBar">
</style>
<style name="Theme.AppCompat.Translucent" parent="Theme.AppCompat.NoActionBar">

View file

@ -6,7 +6,7 @@
"components" : {
"alpha" : "1.000",
"blue" : "0xA8",
"green" : "0xEB",
"green" : "0xEA",
"red" : "0xFB"
}
},