diff --git a/android/Omnivore/app/.gitignore b/android/Omnivore/app/.gitignore
index 42afabfd2..6a4362e1c 100644
--- a/android/Omnivore/app/.gitignore
+++ b/android/Omnivore/app/.gitignore
@@ -1 +1,3 @@
-/build
\ No newline at end of file
+/build
+keystore.properties
+*.keystore
\ No newline at end of file
diff --git a/android/Omnivore/app/build.gradle b/android/Omnivore/app/build.gradle
index 86c1239ca..78849006b 100644
--- a/android/Omnivore/app/build.gradle
+++ b/android/Omnivore/app/build.gradle
@@ -6,6 +6,10 @@ plugins {
id 'com.apollographql.apollo3' version '3.5.0'
}
+def keystorePropertiesFile = rootProject.file("app/external/keystore.properties");
+def keystoreProperties = new Properties()
+keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
+
android {
compileSdk 33
@@ -13,8 +17,8 @@ android {
applicationId "app.omnivore.omnivore"
minSdk 23
targetSdk 32
- versionCode 1
- versionName "1.0"
+ versionCode 2
+ versionName "0.0.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
@@ -22,9 +26,32 @@ android {
}
}
+ signingConfigs{
+ release{
+ keyAlias 'key0'
+ storeFile file('external/omnivore-prod.keystore')
+ storePassword keystoreProperties['prodStorePassword']
+ keyPassword keystoreProperties['prodKeyPassword']
+ }
+ debug{
+ keyAlias 'androiddebugkey'
+ storeFile file('external/omnivore-demo.keystore')
+ storePassword keystoreProperties['demoStorePassword']
+ keyPassword keystoreProperties['demoKeyPassword']
+ }
+ }
+
buildTypes {
+ debug{
+ signingConfig signingConfigs.debug
+ buildConfigField("String", "OMNIVORE_API_URL", "\"https://api-demo.omnivore.app\"")
+ buildConfigField("String", "OMNIVORE_GAUTH_SERVER_CLIENT_ID", "\"267918240109-eu2ar09unac3lqqigluknhk7t0021b54.apps.googleusercontent.com\"")
+ }
release {
minifyEnabled false
+ signingConfig signingConfigs.release
+ buildConfigField("String", "OMNIVORE_API_URL", "\"https://api-prod.omnivore.app\"")
+ buildConfigField("String", "OMNIVORE_GAUTH_SERVER_CLIENT_ID", "\"687911924401-lq8j1e97n0sv3khhb8g8n368lk4dqkbp.apps.googleusercontent.com\"")
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
@@ -128,6 +155,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/Constants.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/Constants.kt
index 9ae6feaf6..e4852a6bc 100644
--- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/Constants.kt
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/Constants.kt
@@ -1,7 +1,7 @@
package app.omnivore.omnivore
object Constants {
- const val demoProdURL = "https://api-demo.omnivore.app"
+ const val apiURL = BuildConfig.OMNIVORE_API_URL
const val dataStoreName = "omnivore-datastore"
}
@@ -9,3 +9,11 @@ object DatastoreKeys {
const val omnivoreAuthToken = "omnivoreAuthToken"
const val omnivoreAuthCookieString = "omnivoreAuthCookieString"
}
+
+object AppleConstants {
+ const val clientId = "app.omnivore"
+ const val redirectURI = "https://api-demo.omnivore.app/api/auth/vercel/apple-redirect"
+ const val scope = "name%20email"
+ const val authUrl = "https://appleid.apple.com/auth/authorize"
+ const val tokenUrl = "https://appleid.apple.com/auth/token"
+}
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/RESTNetworker.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/RESTNetworker.kt
index c3956f73e..c9e238e67 100644
--- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/RESTNetworker.kt
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/RESTNetworker.kt
@@ -43,7 +43,7 @@ interface AuthProviderLoginSubmit {
object RetrofitHelper {
fun getInstance(): Retrofit {
- return Retrofit.Builder().baseUrl(Constants.demoProdURL)
+ return Retrofit.Builder().baseUrl(Constants.apiURL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
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/AppleAuth.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/AppleAuth.kt
new file mode 100644
index 000000000..5bfd56466
--- /dev/null
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/AppleAuth.kt
@@ -0,0 +1,197 @@
+package app.omnivore.omnivore.ui.auth
+
+import android.annotation.SuppressLint
+import android.util.Log
+import android.view.ViewGroup
+import android.webkit.WebResourceRequest
+import android.webkit.WebView
+import android.webkit.WebViewClient
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.TopAppBar
+import androidx.compose.material3.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.compose.ui.window.Dialog
+import app.omnivore.omnivore.AppleConstants
+import app.omnivore.omnivore.R
+import java.util.*
+
+@Composable
+fun AppleAuthButton(viewModel: LoginViewModel) {
+ val showDialog = remember { mutableStateOf(false) }
+
+ LoadingButtonWithIcon(
+ text = "Continue with Apple",
+ loadingText = "Signing in...",
+ isLoading = viewModel.isLoading,
+ icon = painterResource(id = R.drawable.ic_logo_apple),
+ modifier = Modifier.padding(vertical = 6.dp),
+ onClick = { showDialog.value = true }
+ )
+
+ if (showDialog.value) {
+ AppleAuthDialog(onDismiss = {
+ showDialog.value = false
+ Log.i("Apple payload: ", it ?: "null")
+ })
+ }
+}
+
+@Composable
+fun AppleAuthDialog(onDismiss: (String?) -> Unit) {
+ Dialog(onDismissRequest = { onDismiss(null) }) {
+ Surface(
+ shape = RoundedCornerShape(16.dp),
+ color = Color.White
+ ) {
+ AppleAuthWebContainerView(onDismiss)
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
+@Composable
+fun AppleAuthWebContainerView(onDismiss: (String?) -> Unit) {
+ Scaffold(
+ topBar = { TopAppBar(title = { Text("WebView", color = Color.White) }, backgroundColor = Color(0xff0f9d58)) },
+ content = { AppleAuthWebView(onDismiss) }
+ )
+}
+
+@SuppressLint("SetJavaScriptEnabled")
+@Composable
+fun AppleAuthWebView(onDismiss: (String?) -> Unit) {
+ val url = AppleConstants.authUrl +
+ "?client_id=" +
+ AppleConstants.clientId +
+ "&redirect_uri=" +
+ AppleConstants.redirectURI +
+ "&response_type=code%20id_token&scope=" +
+ AppleConstants.scope +
+ "&response_mode=form_post&state=android:login"
+
+// clientId="app.omnivore"
+// scope="name email"
+// state="web:login"
+// redirectURI={appleAuthRedirectURI}
+// responseMode="form_post"
+// responseType="code id_token"
+// designProp={{
+// color: 'black',
+
+ // Adding a WebView inside AndroidView
+ // with layout as full screen
+ AndroidView(factory = {
+ WebView(it).apply {
+ layoutParams = ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ )
+// webViewClient = WebViewClient()
+ webViewClient = object : WebViewClient() {
+ override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
+ Log.i("Apple payload one: ", request?.url.toString() ?: "null")
+ if (request?.url.toString().startsWith(AppleConstants.redirectURI)) {
+// handleUrl(request?.url.toString())
+ onDismiss(request?.url.toString())
+ // Close the dialog after getting the authorization code
+ if (request?.url.toString().contains("success=")) {
+ onDismiss(null)
+ }
+ return true
+ }
+ return true
+ }
+ }
+ settings.javaScriptEnabled = true
+ loadUrl(url)
+ }
+ }, update = {
+ it.loadUrl(url)
+ })
+}
+
+//// A client to know about WebView navigation
+//// For API 21 and above
+//class AppleWebViewClient : WebViewClient() {
+// @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+// override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
+// if (request?.url.toString().startsWith(AppleConstants.redirectURI)) {
+// handleUrl(request?.url.toString())
+// // Close the dialog after getting the authorization code
+// if (request.url.toString().contains("success=")) {
+//// appledialog.dismiss()
+// }
+// return true
+// }
+// return true
+// }
+
+// // For API 19 and below
+// override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
+// if (url.startsWith(AppleConstants.redirectURI)) {
+// handleUrl(url)
+// // Close the dialog after getting the authorization code
+// if (url.contains("success=")) {
+//// appledialog.dismiss()
+// }
+// return true
+// }
+// return false
+// }
+
+// @SuppressLint("ClickableViewAccessibility")
+// override fun onPageFinished(view: WebView?, url: String?) {
+// super.onPageFinished(view, url)
+// // retrieve display dimensions
+// val displayRectangle = Rect()
+// val window = this@AppleWebViewClient.w
+// window.decorView.getWindowVisibleDisplayFrame(displayRectangle)
+// // Set height of the Dialog to 90% of the screen
+// val layoutParams = view?.layoutParams
+// layoutParams?.height = (displayRectangle.height() * 0.9f).toInt()
+// view?.layoutParams = layoutParams
+// }
+
+// // Check WebView url for access token code or error
+// @SuppressLint("LongLogTag")
+// private fun handleUrl(url: String) {
+// val uri = Uri.parse(url)
+// val success = uri.getQueryParameter("success")
+// if (success == "true") {
+// // Get the Authorization Code from the URL
+//// appleAuthCode = uri.getQueryParameter("code") ?: ""
+//// Log.i("Apple Code: ", appleAuthCode)
+// // Get the Client Secret from the URL
+//// appleClientSecret = uri.getQueryParameter("client_secret") ?: ""
+//// Log.i("Apple Client Secret: ", appleClientSecret)
+// //Check if user gave access to the app for the first time by checking if the url contains their email
+// if (url.contains("email")) {
+// //Get user's First Name
+// val firstName = uri.getQueryParameter("first_name")
+// Log.i("Apple User First Name: ", firstName ?: "")
+// //Get user's Middle Name
+// val middleName = uri.getQueryParameter("middle_name")
+// Log.i("Apple User Middle Name: ", middleName ?: "")
+// //Get user's Last Name
+// val lastName = uri.getQueryParameter("last_name")
+// Log.i("Apple User Last Name: ", lastName ?: "")
+// //Get user's email
+// val email = uri.getQueryParameter("email")
+// Log.i("Apple User Email: ", email ?: "Not exists")
+// }
+// // Exchange the Auth Code for Access Token
+//// requestForAccessToken(appleAuthCode, appleClientSecret)
+// } else if (success == "false") {
+// Log.e("ERROR", "We couldn't get the Auth Code")
+// }
+// }
+//}
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..99b41f432
--- /dev/null
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailLogin.kt
@@ -0,0 +1,123 @@
+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.graphics.Color
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.PasswordVisualTransformation
+import androidx.compose.ui.text.style.TextDecoration
+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("Return to Social Login"),
+ style = MaterialTheme.typography.titleMedium
+ .plus(TextStyle(textDecoration = TextDecoration.Underline)),
+ 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()
+ }
+ }, colors = ButtonDefaults.buttonColors(
+ contentColor = Color(0xFF3D3D3D),
+ containerColor = Color(0xffffd234)
+ )
+ ) {
+ Text(
+ text = "Login",
+ modifier = Modifier.padding(horizontal = 100.dp)
+ )
+ }
+ }
+}
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 70%
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..436bc31c6 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,32 +1,27 @@
-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.BuildConfig
+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
import com.google.android.gms.tasks.Task
-
-@OptIn(ExperimentalMaterialApi::class)
@Composable
fun GoogleAuthButton(viewModel: LoginViewModel) {
val context = LocalContext.current
+
val signInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
+ .requestIdToken(BuildConfig.OMNIVORE_GAUTH_SERVER_CLIENT_ID)
.requestEmail()
- .requestIdToken(stringResource(R.string.gcp_id))
- .requestId()
- .requestProfile()
.build()
val startForResult =
@@ -37,17 +32,30 @@ fun GoogleAuthButton(viewModel: LoginViewModel) {
val task: Task = GoogleSignIn.getSignedInAccountFromIntent(intent)
viewModel.handleGoogleAuthTask(task)
}
+ } else {
+ viewModel.showGoogleErrorMessage()
}
}
- GoogleSignInButton(
+ LoadingButtonWithIcon(
text = "Continue with Google",
loadingText = "Signing in...",
isLoading = viewModel.isLoading,
icon = painterResource(id = R.drawable.ic_logo_google),
onClick = {
val googleSignIn = GoogleSignIn.getClient(context, signInOptions)
- startForResult.launch(googleSignIn.signInIntent)
+
+ googleSignIn.silentSignIn()
+ .addOnCompleteListener { task ->
+ if (task.isSuccessful) {
+ viewModel.handleGoogleAuthTask(task)
+ } else {
+ startForResult.launch(googleSignIn.signInIntent)
+ }
+ }
+ .addOnFailureListener {
+ startForResult.launch(googleSignIn.signInIntent)
+ }
}
)
}
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 80%
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..352281598 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,12 @@ 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
@@ -73,13 +78,21 @@ class LoginViewModel @Inject constructor(
errorMessage = null
}
- fun handleGoogleAuthTask(task: Task) {
- val googleIdToken = task?.getResult(ApiException::class.java).idToken
- Log.d(ContentValues.TAG, "Google Result?: $googleIdToken")
- // TODO: submit id token to backend
- // If token is missing then set the error message
+ fun showGoogleErrorMessage() {
+ errorMessage = "Failed to authenticate with Google."
+ }
+ fun handleGoogleAuthTask(task: Task) {
+ val result = task?.getResult(ApiException::class.java)
+ Log.d(ContentValues.TAG, "server auth code?: ${result.serverAuthCode}")
+ Log.d(ContentValues.TAG, "is Expired?: ${result.isExpired}")
+ Log.d(ContentValues.TAG, "granted Scopes?: ${result.grantedScopes}")
+ val googleIdToken = result.idToken
+ Log.d(ContentValues.TAG, "Google id token?: $googleIdToken")
+
+ // If token is missing then set the error message
if (googleIdToken == null) {
+ errorMessage = "No authentication token found."
return
}
@@ -98,7 +111,7 @@ class LoginViewModel @Inject constructor(
if (result.body()?.authToken != null) {
datastoreRepo.putString(DatastoreKeys.omnivoreAuthToken, result.body()?.authToken!!)
} else {
- errorMessage = "Something went wrong. Please check your email/password and try again"
+ errorMessage = "Something went wrong. Please check your credentials and try again"
}
if (result.body()?.authCookieString != null) {
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..ccd6b4162
--- /dev/null
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/WelcomeScreen.kt
@@ -0,0 +1,170 @@
+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.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.text.SpanStyle
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.unit.dp
+import app.omnivore.omnivore.R
+import com.google.android.gms.common.GoogleApiAvailability
+import kotlinx.coroutines.launch
+
+@Composable
+fun WelcomeScreen(viewModel: LoginViewModel) {
+ Surface(modifier = Modifier.fillMaxSize(), color = Color(0xFFFCEBA8)) {
+ 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
+ .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
+ )
+
+ Text(
+ text = stringResource(id = R.string.welcome_subtitle),
+ style = MaterialTheme.typography.titleSmall
+ )
+
+ 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
+ verticalArrangement = Arrangement.spacedBy(8.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ if (isGoogleAuthAvailable) {
+ GoogleAuthButton(viewModel)
+ }
+
+ // AppleAuthButton(viewModel)
+
+ ClickableText(
+ text = AnnotatedString("Continue with Email"),
+ style = MaterialTheme.typography.titleMedium
+ .plus(TextStyle(textDecoration = TextDecoration.Underline)),
+ 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(
+ stringResource(id = R.string.learn_more),
+ ),
+ style = MaterialTheme.typography.titleSmall
+ .plus(TextStyle(textDecoration = TextDecoration.Underline)),
+ 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..da1c0e4d9
--- /dev/null
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeView.kt
@@ -0,0 +1,48 @@
+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.platform.LocalContext
+import androidx.compose.ui.unit.dp
+import androidx.navigation.NavHostController
+import app.omnivore.omnivore.ui.auth.LoginViewModel
+import com.google.android.gms.auth.api.signin.GoogleSignIn
+import com.google.android.gms.auth.api.signin.GoogleSignInOptions
+
+@Composable
+fun HomeView(viewModel: LoginViewModel) {
+ val context = LocalContext.current
+
+ 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 = {
+ // Sign out google users
+ val signInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
+ .build()
+
+ val googleSignIn = GoogleSignIn.getClient(context, signInOptions)
+ googleSignIn.signOut()
+
+ 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 79%
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..710768e67 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,11 @@ 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.material.ButtonDefaults
import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
+import app.omnivore.omnivore.ui.save.SaveViewModel
import kotlinx.coroutines.launch
@Composable
@@ -32,7 +34,12 @@ fun SaveContent(viewModel: SaveViewModel, modalBottomSheetState: ModalBottomShee
coroutineScope.launch {
modalBottomSheetState.hide()
}
- }) {
+ },
+ colors = ButtonDefaults.buttonColors(
+ contentColor = Color(0xFF3D3D3D),
+ backgroundColor = Color(0xffffd234)
+ )
+ ) {
Text(text = "Dismiss")
}
}
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 89%
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..4e1acc23c 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
@@ -44,7 +47,7 @@ class SaveViewModel @Inject constructor(
}
val apolloClient = ApolloClient.Builder()
- .serverUrl("${Constants.demoProdURL}/api/graphql")
+ .serverUrl("${Constants.apiURL}/api/graphql")
.addHttpHeader("Authorization", value = apiKey)
.build()
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..366c15319 100644
--- a/android/Omnivore/app/src/main/res/values/strings.xml
+++ b/android/Omnivore/app/src/main/res/values/strings.xml
@@ -1,4 +1,8 @@
Omnivore
590528454690-mpmbvcb4c3ifmnojmrultt37eo8f64at.apps.googleusercontent.com
+
+ Never miss a great read
+ Learn More
+ Save articles and read them later in our distraction-free reader.
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 @@
-