diff --git a/android/Omnivore/app/build.gradle b/android/Omnivore/app/build.gradle
index f403cb72d..86c1239ca 100644
--- a/android/Omnivore/app/build.gradle
+++ b/android/Omnivore/app/build.gradle
@@ -1,14 +1,17 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'dagger.hilt.android.plugin'
+ id 'com.apollographql.apollo3' version '3.5.0'
}
android {
- compileSdk 32
+ compileSdk 33
defaultConfig {
applicationId "app.omnivore.omnivore"
- minSdk 21
+ minSdk 23
targetSdk 32
versionCode 1
versionName "1.0"
@@ -36,7 +39,7 @@ android {
compose true
}
composeOptions {
- kotlinCompilerExtensionVersion compose_version
+ kotlinCompilerExtensionVersion = '1.2.0-alpha08'
}
packagingOptions {
resources {
@@ -46,6 +49,7 @@ android {
}
dependencies {
+ def nav_version = "2.5.1"
implementation 'androidx.core:core-ktx:1.7.0'
implementation "androidx.compose.ui:ui:$compose_version"
@@ -53,10 +57,79 @@ dependencies {
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
implementation 'androidx.activity:activity-compose:1.3.1'
+ implementation 'androidx.appcompat:appcompat:1.4.1'
+ implementation 'com.google.android.gms:play-services-base:17.6.0'
+
+ implementation "androidx.navigation:navigation-compose:$nav_version"
+
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
+
+ // Jetpack Lifecycle deps
+
+ // ViewModel
+ implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version")
+ // ViewModel utilities for Compose
+ implementation("androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version")
+ // LiveData
+ implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version")
+ implementation("androidx.compose.runtime:runtime-livedata:1.2.1")
+
+ // Saved state module for ViewModel
+ implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version")
+
+ // Annotation processor
+// kapt("androidx.lifecycle:lifecycle-compiler:$lifecycle_version")
+ // alternately - if using Java8, use the following instead of lifecycle-compiler
+ implementation("androidx.lifecycle:lifecycle-common-java8:$lifecycle_version")
+
+ // optional - helpers for implementing LifecycleOwner in a Service
+// implementation("androidx.lifecycle:lifecycle-service:$lifecycle_version")
+
+ // optional - ProcessLifecycleOwner provides a lifecycle for the whole application process
+// implementation("androidx.lifecycle:lifecycle-process:$lifecycle_version")
+
+ // optional - ReactiveStreams support for LiveData
+// implementation("androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycle_version")
+
+ // optional - Test helpers for LiveData
+// testImplementation("androidx.arch.core:core-testing:$arch_version")
+
+ // optional - Test helpers for Lifecycle runtime
+// testImplementation ("androidx.lifecycle:lifecycle-runtime-testing:$lifecycle_version")
+
+ implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
+ implementation 'com.squareup.retrofit2:retrofit:2.9.0'
+
+// coroutines
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4'
+
+ implementation "androidx.security:security-crypto:1.0.0"
+ implementation "androidx.datastore:datastore-preferences:1.0.0"
+
+
+
+// Activity KTX for viewModels()
+ // TODO: remove this since compose-activity imports ktx?
+// implementation "androidx.activity:activity-ktx:1.3.1"
+
+//Dagger - Hilt
+ implementation 'com.google.dagger:hilt-android:2.42'
+ kapt 'com.google.dagger:hilt-compiler:2.42'
+
+ implementation 'com.apollographql.apollo3:apollo-runtime:3.5.0'
+
+ implementation 'androidx.compose.material3:material3:1.0.0-alpha16'
+ implementation 'androidx.compose.material3:material3-window-size-class:1.0.0-alpha16'
+
+ implementation 'com.google.android.gms:play-services-auth:20.2.0'
+}
+
+apollo {
+ packageName.set 'app.omnivore.omnivore.graphql.generated'
}
diff --git a/android/Omnivore/app/src/main/AndroidManifest.xml b/android/Omnivore/app/src/main/AndroidManifest.xml
index f8e001890..ed213ee7f 100644
--- a/android/Omnivore/app/src/main/AndroidManifest.xml
+++ b/android/Omnivore/app/src/main/AndroidManifest.xml
@@ -3,7 +3,10 @@
xmlns:tools="http://schemas.android.com/tools"
package="app.omnivore.omnivore">
+
+
+
-
-
+
+
+
+
+
+
+
+
+
diff --git a/android/SaveToOmnivore/app/src/main/graphql/SaveUrl.graphql b/android/Omnivore/app/src/main/graphql/SaveUrl.graphql
similarity index 100%
rename from android/SaveToOmnivore/app/src/main/graphql/SaveUrl.graphql
rename to android/Omnivore/app/src/main/graphql/SaveUrl.graphql
diff --git a/android/SaveToOmnivore/app/src/main/graphql/schema.graphqls b/android/Omnivore/app/src/main/graphql/schema.graphqls
similarity index 93%
rename from android/SaveToOmnivore/app/src/main/graphql/schema.graphqls
rename to android/Omnivore/app/src/main/graphql/schema.graphqls
index 187b8d072..a0d0e1161 100644
--- a/android/SaveToOmnivore/app/src/main/graphql/schema.graphqls
+++ b/android/Omnivore/app/src/main/graphql/schema.graphqls
@@ -148,6 +148,7 @@ enum ArticleSavingRequestErrorCode {
union ArticleSavingRequestResult = ArticleSavingRequestError | ArticleSavingRequestSuccess
enum ArticleSavingRequestStatus {
+ DELETED
FAILED
PROCESSING
SUCCEEDED
@@ -419,6 +420,22 @@ type DeleteHighlightSuccess {
highlight: Highlight!
}
+type DeleteIntegrationError {
+ errorCodes: [DeleteIntegrationErrorCode!]!
+}
+
+enum DeleteIntegrationErrorCode {
+ BAD_REQUEST
+ NOT_FOUND
+ UNAUTHORIZED
+}
+
+union DeleteIntegrationResult = DeleteIntegrationError | DeleteIntegrationSuccess
+
+type DeleteIntegrationSuccess {
+ integration: Integration!
+}
+
type DeleteLabelError {
errorCodes: [DeleteLabelErrorCode!]!
}
@@ -657,12 +674,41 @@ type HighlightStats {
highlightCount: Int!
}
+type Integration {
+ createdAt: Date!
+ enabled: Boolean!
+ id: ID!
+ token: String!
+ type: IntegrationType!
+ updatedAt: Date!
+}
+
+enum IntegrationType {
+ READWISE
+}
+
+type IntegrationsError {
+ errorCodes: [IntegrationsErrorCode!]!
+}
+
+enum IntegrationsErrorCode {
+ BAD_REQUEST
+ UNAUTHORIZED
+}
+
+union IntegrationsResult = IntegrationsError | IntegrationsSuccess
+
+type IntegrationsSuccess {
+ integrations: [Integration!]!
+}
+
type Label {
color: String!
createdAt: Date
description: String
id: ID!
name: String!
+ position: Int
}
type LabelsError {
@@ -730,11 +776,6 @@ enum LoginErrorCode {
WRONG_SOURCE
}
-input LoginInput {
- email: String!
- password: String!
-}
-
union LoginResult = LoginError | LoginSuccess
type LoginSuccess {
@@ -772,6 +813,27 @@ type MergeHighlightSuccess {
overlapHighlightIdList: [String!]!
}
+type MoveLabelError {
+ errorCodes: [MoveLabelErrorCode!]!
+}
+
+enum MoveLabelErrorCode {
+ BAD_REQUEST
+ NOT_FOUND
+ UNAUTHORIZED
+}
+
+input MoveLabelInput {
+ afterLabelId: ID
+ labelId: ID!
+}
+
+union MoveLabelResult = MoveLabelError | MoveLabelSuccess
+
+type MoveLabelSuccess {
+ label: Label!
+}
+
type Mutation {
addPopularRead(name: String!): AddPopularReadResult!
createArticle(input: CreateArticleInput!): CreateArticleResult!
@@ -785,6 +847,7 @@ type Mutation {
deleteAccount(userID: ID!): DeleteAccountResult!
deleteHighlight(highlightId: ID!): DeleteHighlightResult!
deleteHighlightReply(highlightReplyId: ID!): DeleteHighlightReplyResult!
+ deleteIntegration(id: ID!): DeleteIntegrationResult!
deleteLabel(id: ID!): DeleteLabelResult!
deleteNewsletterEmail(newsletterEmailId: ID!): DeleteNewsletterEmailResult!
deleteReaction(id: ID!): DeleteReactionResult!
@@ -794,8 +857,8 @@ type Mutation {
googleLogin(input: GoogleLoginInput!): LoginResult!
googleSignup(input: GoogleSignupInput!): GoogleSignupResult!
logOut: LogOutResult!
- login(input: LoginInput!): LoginResult!
mergeHighlight(input: MergeHighlightInput!): MergeHighlightResult!
+ moveLabel(input: MoveLabelInput!): MoveLabelResult!
reportItem(input: ReportItemInput!): ReportItemResult!
revokeApiKey(id: ID!): RevokeApiKeyResult!
saveArticleReadingProgress(input: SaveArticleReadingProgressInput!): SaveArticleReadingProgressResult!
@@ -805,6 +868,7 @@ type Mutation {
setBookmarkArticle(input: SetBookmarkArticleInput!): SetBookmarkArticleResult!
setDeviceToken(input: SetDeviceTokenInput!): SetDeviceTokenResult!
setFollow(input: SetFollowInput!): SetFollowResult!
+ setIntegration(input: SetIntegrationInput!): SetIntegrationResult!
setLabels(input: SetLabelsInput!): SetLabelsResult!
setLabelsForHighlight(input: SetLabelsForHighlightInput!): SetLabelsResult!
setLinkArchived(input: ArchiveLinkInput!): ArchiveLinkResult!
@@ -812,7 +876,6 @@ type Mutation {
setShareHighlight(input: SetShareHighlightInput!): SetShareHighlightResult!
setUserPersonalization(input: SetUserPersonalizationInput!): SetUserPersonalizationResult!
setWebhook(input: SetWebhookInput!): SetWebhookResult!
- signup(input: SignupInput!): SignupResult!
subscribe(name: String!): SubscribeResult!
unsubscribe(name: String!): UnsubscribeResult!
updateHighlight(input: UpdateHighlightInput!): UpdateHighlightResult!
@@ -916,6 +979,7 @@ type Query {
getFollowing(userId: ID): GetFollowingResult!
getUserPersonalization: GetUserPersonalizationResult!
hello: String
+ integrations: IntegrationsResult!
labels: LabelsResult!
me: User
newsletterEmails: NewsletterEmailsResult!
@@ -925,6 +989,7 @@ type Query {
sharedArticle(selectedHighlightId: String, slug: String!, username: String!): SharedArticleResult!
subscriptions(sort: SortParams): SubscriptionsResult!
typeaheadSearch(first: Int, query: String!): TypeaheadSearchResult!
+ updatesSince(after: String, first: Int, since: Date!): UpdatesSinceResult!
user(userId: ID, username: String): UserResult!
users: UsersResult!
validateUsername(username: String!): Boolean!
@@ -1207,6 +1272,31 @@ type SetFollowSuccess {
updatedUser: User!
}
+type SetIntegrationError {
+ errorCodes: [SetIntegrationErrorCode!]!
+}
+
+enum SetIntegrationErrorCode {
+ ALREADY_EXISTS
+ BAD_REQUEST
+ INVALID_TOKEN
+ NOT_FOUND
+ UNAUTHORIZED
+}
+
+input SetIntegrationInput {
+ enabled: Boolean!
+ id: ID
+ token: String!
+ type: IntegrationType!
+}
+
+union SetIntegrationResult = SetIntegrationError | SetIntegrationSuccess
+
+type SetIntegrationSuccess {
+ integration: Integration!
+}
+
type SetLabelsError {
errorCodes: [SetLabelsErrorCode!]!
}
@@ -1347,35 +1437,17 @@ type SharedArticleSuccess {
article: Article!
}
-type SignupError {
- errorCodes: [SignupErrorCode]!
-}
-
enum SignupErrorCode {
ACCESS_DENIED
EXPIRED_TOKEN
GOOGLE_AUTH_ERROR
+ INVALID_EMAIL
INVALID_PASSWORD
INVALID_USERNAME
UNKNOWN
USER_EXISTS
}
-input SignupInput {
- bio: String
- email: String!
- name: String!
- password: String!
- pictureUrl: String
- username: String!
-}
-
-union SignupResult = SignupError | SignupSuccess
-
-type SignupSuccess {
- me: User!
-}
-
enum SortBy {
PUBLISHED_AT
SAVED_AT
@@ -1444,6 +1516,13 @@ type SubscriptionsSuccess {
subscriptions: [Subscription!]!
}
+type SyncUpdatedItemEdge {
+ cursor: String!
+ itemID: ID!
+ node: SearchItem
+ updateReason: UpdateReason!
+}
+
type TypeaheadSearchError {
errorCodes: [TypeaheadSearchErrorCode!]!
}
@@ -1596,6 +1675,12 @@ type UpdatePageSuccess {
updatedPage: Article!
}
+enum UpdateReason {
+ CREATED
+ DELETED
+ UPDATED
+}
+
type UpdateReminderError {
errorCodes: [UpdateReminderErrorCode!]!
}
@@ -1687,6 +1772,21 @@ type UpdateUserSuccess {
user: User!
}
+type UpdatesSinceError {
+ errorCodes: [UpdatesSinceErrorCode!]!
+}
+
+enum UpdatesSinceErrorCode {
+ UNAUTHORIZED
+}
+
+union UpdatesSinceResult = UpdatesSinceError | UpdatesSinceSuccess
+
+type UpdatesSinceSuccess {
+ edges: [SyncUpdatedItemEdge!]!
+ pageInfo: PageInfo!
+}
+
type UploadFileRequestError {
errorCodes: [UploadFileRequestErrorCode!]!
}
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/AppModule.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/AppModule.kt
new file mode 100644
index 000000000..c48739f95
--- /dev/null
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/AppModule.kt
@@ -0,0 +1,21 @@
+package app.omnivore.omnivore
+
+import android.content.Context
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object AppModule {
+
+ @Singleton
+ @Provides
+ fun provideDataStoreRepository(
+ @ApplicationContext app: Context
+ ): DatastoreRepository = OmnivoreDatastore(app)
+
+}
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
new file mode 100644
index 000000000..9ae6feaf6
--- /dev/null
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/Constants.kt
@@ -0,0 +1,11 @@
+package app.omnivore.omnivore
+
+object Constants {
+ const val demoProdURL = "https://api-demo.omnivore.app"
+ const val dataStoreName = "omnivore-datastore"
+}
+
+object DatastoreKeys {
+ const val omnivoreAuthToken = "omnivoreAuthToken"
+ const val omnivoreAuthCookieString = "omnivoreAuthCookieString"
+}
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/DatastoreRepository.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/DatastoreRepository.kt
new file mode 100644
index 000000000..7392ffeb9
--- /dev/null
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/DatastoreRepository.kt
@@ -0,0 +1,64 @@
+package app.omnivore.omnivore
+
+import android.content.Context
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.*
+import androidx.datastore.preferences.preferencesDataStore
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+interface DatastoreRepository {
+ val hasAuthTokenFlow: Flow
+ suspend fun clear()
+ suspend fun putString(key: String, value: String)
+ suspend fun putInt(key: String, value: Int)
+ suspend fun getString(key: String): String?
+ suspend fun getInt(key: String): Int?
+}
+
+class OmnivoreDatastore @Inject constructor(
+ private val context: Context
+) : DatastoreRepository {
+ private val Context.dataStore: DataStore by preferencesDataStore(
+ name = Constants.dataStoreName
+ )
+
+ override suspend fun putString(key: String, value: String) {
+ val preferencesKey = stringPreferencesKey(key)
+ context.dataStore.edit { preferences ->
+ preferences[preferencesKey] = value
+ }
+ }
+
+ override suspend fun putInt(key: String, value: Int) {
+ val preferencesKey = intPreferencesKey(key)
+ context.dataStore.edit { preferences ->
+ preferences[preferencesKey] = value
+ }
+ }
+
+ override suspend fun getString(key: String): String? {
+ val preferencesKey = stringPreferencesKey(key)
+ val preferences = context.dataStore.data.first()
+ return preferences[preferencesKey]
+ }
+
+ override suspend fun getInt(key: String): Int? {
+ val preferencesKey = intPreferencesKey(key)
+ val preferences = context.dataStore.data.first()
+ return preferences[preferencesKey]
+ }
+
+ override suspend fun clear() {
+ context.dataStore.edit { it.clear() }
+ }
+
+ override val hasAuthTokenFlow: Flow = context
+ .dataStore.data.map { preferences ->
+ val key = stringPreferencesKey(DatastoreKeys.omnivoreAuthToken)
+ val token = preferences[key]
+ token != null
+ }
+}
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/GoogleAuth.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/GoogleAuth.kt
new file mode 100644
index 000000000..ba8b9eae0
--- /dev/null
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/GoogleAuth.kt
@@ -0,0 +1,53 @@
+package app.omnivore.omnivore
+
+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 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)
+ .requestEmail()
+ .requestIdToken(stringResource(R.string.gcp_id))
+ .requestId()
+ .requestProfile()
+ .build()
+
+ val startForResult =
+ rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
+ if (result.resultCode == Activity.RESULT_OK) {
+ val intent = result.data
+ if (result.data != null) {
+ val task: Task = GoogleSignIn.getSignedInAccountFromIntent(intent)
+ viewModel.handleGoogleAuthTask(task)
+ }
+ }
+ }
+
+ GoogleSignInButton(
+ 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)
+ }
+ )
+}
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/GoogleSignInButton.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/GoogleSignInButton.kt
new file mode 100644
index 000000000..e80f8c0ce
--- /dev/null
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/GoogleSignInButton.kt
@@ -0,0 +1,70 @@
+package app.omnivore.omnivore
+
+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.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.unit.dp
+
+@ExperimentalMaterialApi
+@Composable
+fun GoogleSignInButton(
+ text: String,
+ loadingText: String = "Signing in...",
+ icon: Painter,
+ isLoading: Boolean = false,
+ shape: Shape = Shapes().medium,
+ borderColor: Color = Color.LightGray,
+ backgroundColor: Color = MaterialTheme.colors.surface,
+ progressIndicatorColor: Color = MaterialTheme.colors.primary,
+ onClick: () -> Unit
+) {
+ Surface(
+ modifier = Modifier.clickable(
+ enabled = !isLoading,
+ onClick = onClick
+ ),
+ shape = shape,
+ border = BorderStroke(width = 1.dp, color = borderColor),
+ color = backgroundColor
+ ) {
+ Row(
+ modifier = Modifier
+ .padding(
+ start = 12.dp,
+ end = 16.dp,
+ top = 12.dp,
+ bottom = 12.dp
+ ),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Center,
+ ) {
+ Icon(
+ painter = icon,
+ contentDescription = "SignInButton",
+ tint = Color.Unspecified
+ )
+ Spacer(modifier = Modifier.width(8.dp))
+
+ Text(text = if (isLoading) loadingText else text)
+ if (isLoading) {
+ Spacer(modifier = Modifier.width(16.dp))
+ CircularProgressIndicator(
+ modifier = Modifier
+ .height(16.dp)
+ .width(16.dp),
+ strokeWidth = 2.dp,
+ color = progressIndicatorColor
+ )
+ }
+ }
+ }
+}
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/LoginViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/LoginViewModel.kt
new file mode 100644
index 000000000..14db1873b
--- /dev/null
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/LoginViewModel.kt
@@ -0,0 +1,111 @@
+package app.omnivore.omnivore
+
+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 com.google.android.gms.auth.api.signin.GoogleSignInAccount
+import com.google.android.gms.common.api.ApiException
+import com.google.android.gms.tasks.Task
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class LoginViewModel @Inject constructor(
+ private val datastoreRepo: DatastoreRepository
+): ViewModel() {
+ var isLoading by mutableStateOf(false)
+ private set
+
+ var errorMessage by mutableStateOf(null)
+ private set
+
+ val hasAuthTokenLiveData: LiveData = datastoreRepo
+ .hasAuthTokenFlow
+ .distinctUntilChanged()
+ .asLiveData()
+
+ fun login(email: String, password: String) {
+ val emailLogin = RetrofitHelper.getInstance().create(EmailLoginSubmit::class.java)
+
+ viewModelScope.launch {
+ isLoading = true
+ errorMessage = null
+
+ val result = emailLogin.submitEmailLogin(
+ EmailLoginCredentials(email = email, password = password)
+ )
+
+ isLoading = false
+
+ if (result.body()?.pendingEmailVerification == true) {
+ errorMessage = "Email needs verification"
+ return@launch
+ }
+
+ 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"
+ }
+
+ if (result.body()?.authCookieString != null) {
+ datastoreRepo.putString(
+ DatastoreKeys.omnivoreAuthCookieString, result.body()?.authCookieString!!
+ )
+ }
+ }
+ }
+
+ fun logout() {
+ viewModelScope.launch {
+ datastoreRepo.clear()
+ }
+ }
+
+ fun resetErrorMessage() {
+ 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
+
+ if (googleIdToken == null) {
+ return
+ }
+
+ val login = RetrofitHelper.getInstance().create(AuthProviderLoginSubmit::class.java)
+
+ viewModelScope.launch {
+ isLoading = true
+ errorMessage = null
+
+ val result = login.submitAuthProviderLogin(
+ SignInParams(token = googleIdToken, provider = "GOOGLE")
+ )
+
+ isLoading = false
+
+ 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"
+ }
+
+ if (result.body()?.authCookieString != null) {
+ datastoreRepo.putString(
+ DatastoreKeys.omnivoreAuthCookieString, result.body()?.authCookieString!!
+ )
+ }
+ }
+ }
+}
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 f81c89a49..fbdd52768 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,75 +1,180 @@
package app.omnivore.omnivore
-import android.content.ContentValues.TAG
-import android.content.res.Configuration
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.content.Context
import android.os.Bundle
-import android.util.Log
+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.material.*
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.mutableStateOf
+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.text.input.ImeAction
import androidx.compose.ui.text.input.PasswordVisualTransformation
-import androidx.compose.ui.tooling.preview.Preview
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 dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.launch
+@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+
+ val viewModel: LoginViewModel by viewModels()
+
setContent {
OmnivoreTheme {
// A surface container using the 'background' color from the theme
- Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
- LoginView()
- }
+ ScreenMain(viewModel = viewModel)
+// Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
+// WelcomeView(viewModel)
+// }
}
}
+
+ // animate the view up when keyboard appears
+ WindowCompat.setDecorFitsSystemWindows(window, false)
+ val rootView = findViewById(android.R.id.content).rootView
+ ViewCompat.setOnApplyWindowInsetsListener(rootView) { _, insets ->
+ val imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
+ rootView.setPadding(0, 0, 0, imeHeight)
+ insets
+ }
}
}
@Composable
-fun LoginView() {
- var email by rememberSaveable { mutableStateOf("") }
- var password by rememberSaveable { mutableStateOf("") }
+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.colors.background)
+ .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 = { Log.v(TAG, "$email / $password") }
+ 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)
+ }
}
}
-//Log.v(TAG, "${numerator / denominator}")
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LoginFields(
email: String,
password: String,
onEmailChange: (String) -> Unit,
onPasswordChange: (String) -> Unit,
- onLoginClick: (String) -> Unit
+ onLoginClick: () -> Unit
) {
val context = LocalContext.current
val focusManager = LocalFocusManager.current
@@ -81,7 +186,7 @@ fun LoginFields(
verticalArrangement = Arrangement.spacedBy(25.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
- Text("Please login")
+ Text("Never miss a great read")
OutlinedTextField(
value = email,
@@ -104,17 +209,17 @@ fun LoginFields(
Button(onClick = {
if (email.isNotBlank() && password.isNotBlank()) {
- onLoginClick(email)
+ onLoginClick()
focusManager.clearFocus()
} else {
Toast.makeText(
context,
- "Please enter an email and password",
+ "Please enter an email address and password.",
Toast.LENGTH_SHORT
).show()
}
}) {
- Text("Login")
+ Text(text = "Login")
}
}
}
@@ -124,10 +229,10 @@ fun LoginFields(
// showBackground = true,
// name = "Dark Mode"
//)
-@Preview(showBackground = true)
-@Composable
-fun DefaultPreview() {
- OmnivoreTheme {
- LoginView()
- }
-}
+//@Preview(showBackground = true)
+//@Composable
+//fun DefaultPreview() {
+// OmnivoreTheme {
+// LoginView()
+// }
+//}
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/NewFlowActivity.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/NewFlowActivity.kt
new file mode 100644
index 000000000..0748fda0f
--- /dev/null
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/NewFlowActivity.kt
@@ -0,0 +1,11 @@
+package app.omnivore.omnivore
+
+import androidx.compose.material.ExperimentalMaterialApi
+
+// Not sure why we need this class, but directly opening SaveSheetActivity
+// causes the app to crash.
+
+@OptIn(ExperimentalMaterialApi::class)
+class NewFlowActivity : SaveSheetActivity() {
+
+}
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/OmnivoreApplication.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/OmnivoreApplication.kt
new file mode 100644
index 000000000..7e339793a
--- /dev/null
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/OmnivoreApplication.kt
@@ -0,0 +1,8 @@
+package app.omnivore.omnivore
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class OmnivoreApplication: Application() {
+}
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
new file mode 100644
index 000000000..c3956f73e
--- /dev/null
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/RESTNetworker.kt
@@ -0,0 +1,50 @@
+package app.omnivore.omnivore
+
+import retrofit2.Response
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+import retrofit2.http.Body
+import retrofit2.http.Headers
+import retrofit2.http.POST
+
+data class AuthPayload(
+ val authCookieString: String,
+ val authToken: String
+)
+
+data class SignInParams(
+ val token: String,
+ val provider: String, // APPLE or GOOGLE
+ val source: String = "ANDROID"
+)
+
+data class EmailAuthPayload(
+ val authCookieString: String?,
+ val authToken: String?,
+ val pendingEmailVerification: Boolean?
+)
+
+data class EmailLoginCredentials(
+ val email: String,
+ val password: String
+)
+
+interface EmailLoginSubmit {
+ @Headers("Content-Type: application/json")
+ @POST("/api/mobile-auth/email-sign-in")
+ suspend fun submitEmailLogin(@Body credentials: EmailLoginCredentials): Response
+}
+
+interface AuthProviderLoginSubmit {
+ @Headers("Content-Type: application/json")
+ @POST("/api/mobile-auth/sign-in")
+ suspend fun submitAuthProviderLogin(@Body params: SignInParams): Response
+}
+
+object RetrofitHelper {
+ fun getInstance(): Retrofit {
+ return Retrofit.Builder().baseUrl(Constants.demoProdURL)
+ .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
new file mode 100644
index 000000000..c6ca342c8
--- /dev/null
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/Routes.kt
@@ -0,0 +1,6 @@
+package app.omnivore.omnivore
+
+sealed class Routes(val route: String) {
+ object Splash : Routes("Splash")
+ object EmailLogin : Routes("EmailLogin")
+}
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/SaveContent.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/SaveContent.kt
new file mode 100644
index 000000000..4a569178b
--- /dev/null
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/SaveContent.kt
@@ -0,0 +1,41 @@
+package app.omnivore.omnivore
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+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 kotlinx.coroutines.launch
+
+@Composable
+@OptIn(ExperimentalMaterialApi::class)
+fun SaveContent(viewModel: SaveViewModel, modalBottomSheetState: ModalBottomSheetState, modifier: Modifier) {
+ val coroutineScope = rememberCoroutineScope()
+
+ Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
+ Column(
+ verticalArrangement = Arrangement.SpaceBetween,
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier
+ .background(MaterialTheme.colors.background)
+ .fillMaxSize()
+ .padding(top = 48.dp, bottom = 32.dp)
+ ) {
+ Text(text = viewModel.message ?: "Saving")
+ Button(onClick = {
+ coroutineScope.launch {
+ modalBottomSheetState.hide()
+ }
+ }) {
+ 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/SaveSheetActivity.kt
new file mode 100644
index 000000000..521e5c3df
--- /dev/null
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/SaveSheetActivity.kt
@@ -0,0 +1,169 @@
+package app.omnivore.omnivore
+
+import android.content.ContentValues
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import androidx.activity.compose.BackHandler
+import androidx.activity.compose.setContent
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment.Companion.TopCenter
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+
+@AndroidEntryPoint
+@OptIn(ExperimentalMaterialApi::class)
+abstract class SaveSheetActivity: AppCompatActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val viewModel: SaveViewModel by viewModels()
+ var extractedText: String? = null
+
+ when (intent?.action) {
+ Intent.ACTION_SEND -> {
+ if (intent.type?.startsWith("text/plain") == true) {
+ intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
+ Log.d(ContentValues.TAG, "Extracted text: $extractedText")
+ extractedText = it
+ viewModel.saveURL(it)
+ }
+ }
+
+ if (intent.type?.startsWith("text/html") == true) {
+ intent.getStringExtra(Intent.EXTRA_HTML_TEXT)?.let {
+ extractedText = it
+ }
+ }
+ }
+ else -> {
+ // Handle other intents, such as being started from the home screen
+ }
+ }
+
+ setContent {
+ val coroutineScope = rememberCoroutineScope()
+ val modalBottomSheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
+ val isSheetOpened = remember { mutableStateOf(false) }
+
+ ModalBottomSheetLayout(
+ sheetBackgroundColor = Color.Transparent,
+ sheetState = modalBottomSheetState,
+ sheetContent = {
+ BottomSheetUI {
+ ScreenContent(viewModel, modalBottomSheetState)
+ }
+ }
+ ) {}
+
+ BackHandler {
+ onFinish(coroutineScope, modalBottomSheetState)
+ }
+
+ // Take action based on hidden state
+ LaunchedEffect(modalBottomSheetState.currentValue) {
+ when (modalBottomSheetState.currentValue) {
+ ModalBottomSheetValue.Hidden -> {
+ handleBottomSheetAtHiddenState(
+ isSheetOpened,
+ modalBottomSheetState
+ )
+ }
+ else -> {
+ Log.i(TAG, "Bottom sheet ${modalBottomSheetState.currentValue} state")
+ }
+ }
+ }
+ }
+ }
+
+ @Composable
+ private fun BottomSheetUI(content: @Composable () -> Unit) {
+ Box(
+ modifier = Modifier
+ .wrapContentHeight()
+ .fillMaxWidth()
+ .clip(RoundedCornerShape(topEnd = 20.dp, topStart = 20.dp))
+ .background(Color.White)
+ .statusBarsPadding()
+ ) {
+ content()
+
+ Divider(
+ color = Color.Gray,
+ thickness = 5.dp,
+ modifier = Modifier
+ .padding(top = 15.dp)
+ .align(TopCenter)
+ .width(80.dp)
+ .clip(RoundedCornerShape(50.dp))
+ )
+ }
+ }
+
+ // Helper methods
+ private suspend fun handleBottomSheetAtHiddenState(
+ isSheetOpened: MutableState,
+ modalBottomSheetState: ModalBottomSheetState
+ ) {
+ when {
+ !isSheetOpened.value -> initializeModalLayout(isSheetOpened, modalBottomSheetState)
+ else -> exit()
+ }
+ }
+
+ private suspend fun initializeModalLayout(
+ isSheetOpened: MutableState,
+ modalBottomSheetState: ModalBottomSheetState
+ ) {
+ isSheetOpened.value = true
+ modalBottomSheetState.show()
+ }
+
+ open fun exit() = finish()
+
+ private fun onFinish(
+ coroutineScope: CoroutineScope,
+ modalBottomSheetState: ModalBottomSheetState,
+ withResults: Boolean = false,
+ result: Intent? = null
+ ) {
+ coroutineScope.launch {
+ if (withResults) setResult(RESULT_OK)
+ result?.let { intent = it}
+ modalBottomSheetState.hide() // will trigger the LaunchedEffect
+ }
+ }
+
+ @Composable
+ fun ScreenContent(
+ viewModel: SaveViewModel,
+ modalBottomSheetState: ModalBottomSheetState
+ ) {
+ Box(modifier = Modifier.height(300.dp).background(Color.White)) {
+ SaveContent(viewModel, modalBottomSheetState, modifier = Modifier.fillMaxSize())
+ }
+ }
+
+ override fun onPause() {
+ super.onPause()
+ overridePendingTransition(0, 0)
+ }
+
+ companion object {
+ private val TAG = SaveSheetActivity::class.java.simpleName
+ }
+}
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/SaveViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/SaveViewModel.kt
new file mode 100644
index 000000000..1cbf43078
--- /dev/null
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/SaveViewModel.kt
@@ -0,0 +1,73 @@
+package app.omnivore.omnivore
+
+import android.content.ContentValues
+import android.util.Log
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import app.omnivore.omnivore.graphql.generated.SaveUrlMutation
+import app.omnivore.omnivore.graphql.generated.type.SaveUrlInput
+import com.apollographql.apollo3.ApolloClient
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import java.util.*
+import javax.inject.Inject
+
+@HiltViewModel
+class SaveViewModel @Inject constructor(
+ private val datastoreRepo: DatastoreRepository
+): ViewModel() {
+ var isLoading by mutableStateOf(false)
+ private set
+
+ var message by mutableStateOf(null)
+ private set
+
+ private fun getAuthToken(): String? = runBlocking {
+ datastoreRepo.getString(DatastoreKeys.omnivoreAuthToken)
+ }
+
+ fun saveURL(url: String) {
+ viewModelScope.launch {
+ isLoading = true
+ message = "Saving to Omnivore..."
+
+ val apiKey = getAuthToken()
+
+ if (apiKey == null) {
+ message = "You are not logged in. Please login before saving."
+ isLoading = false
+ return@launch
+ }
+
+ val apolloClient = ApolloClient.Builder()
+ .serverUrl("${Constants.demoProdURL}/api/graphql")
+ .addHttpHeader("Authorization", value = apiKey)
+ .build()
+
+ val response = apolloClient.mutation(
+ SaveUrlMutation(
+ SaveUrlInput(
+ clientRequestId = UUID.randomUUID().toString(),
+ source = "android",
+ url = url
+ )
+ )
+ ).execute()
+
+ isLoading = false
+
+ val success = (response.data?.saveUrl?.onSaveSuccess?.url != null)
+ message = if (success) {
+ "Page Saved"
+ } else {
+ "There was an error saving your page"
+ }
+
+ Log.d(ContentValues.TAG, "Saved URL?: ${success.toString()}")
+ }
+ }
+}
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
new file mode 100644
index 000000000..a2248f02e
--- /dev/null
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ScreenMain.kt
@@ -0,0 +1,22 @@
+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
new file mode 100644
index 000000000..8d2784eb8
--- /dev/null
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/screen/EmailLogin.kt
@@ -0,0 +1,31 @@
+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
new file mode 100644
index 000000000..8e79e6930
--- /dev/null
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/screen/SplashPage.kt
@@ -0,0 +1,78 @@
+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/theme/Color.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/theme/Color.kt
index 6ae28aba9..e974e2bd3 100644
--- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/theme/Color.kt
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/theme/Color.kt
@@ -1,8 +1,62 @@
package app.omnivore.omnivore.ui.theme
-
import androidx.compose.ui.graphics.Color
-val Purple200 = Color(0xFFBB86FC)
-val Purple500 = Color(0xFF6200EE)
-val Purple700 = Color(0xFF3700B3)
-val Teal200 = Color(0xFF03DAC5)
\ No newline at end of file
+val md_theme_light_primary = Color(0xFF745B00)
+val md_theme_light_onPrimary = Color(0xFFFFFFFF)
+val md_theme_light_primaryContainer = Color(0xFFFFE08C)
+val md_theme_light_onPrimaryContainer = Color(0xFF241A00)
+val md_theme_light_secondary = Color(0xFF6D5E00)
+val md_theme_light_onSecondary = Color(0xFFFFFFFF)
+val md_theme_light_secondaryContainer = Color(0xFFFCE365)
+val md_theme_light_onSecondaryContainer = Color(0xFF211B00)
+val md_theme_light_tertiary = Color(0xFF4B670A)
+val md_theme_light_onTertiary = Color(0xFFFFFFFF)
+val md_theme_light_tertiaryContainer = Color(0xFFCBEF86)
+val md_theme_light_onTertiaryContainer = Color(0xFF141F00)
+val md_theme_light_error = Color(0xFFBA1A1A)
+val md_theme_light_errorContainer = Color(0xFFFFDAD6)
+val md_theme_light_onError = Color(0xFFFFFFFF)
+val md_theme_light_onErrorContainer = Color(0xFF410002)
+val md_theme_light_background = Color(0xFFF7FFED)
+val md_theme_light_onBackground = Color(0xFF032100)
+val md_theme_light_surface = Color(0xFFF7FFED)
+val md_theme_light_onSurface = Color(0xFF032100)
+val md_theme_light_surfaceVariant = Color(0xFFEBE1CF)
+val md_theme_light_onSurfaceVariant = Color(0xFF4C4639)
+val md_theme_light_outline = Color(0xFF7E7667)
+val md_theme_light_inverseOnSurface = Color(0xFFCBFFB5)
+val md_theme_light_inverseSurface = Color(0xFF083900)
+val md_theme_light_inversePrimary = Color(0xFFEFC125)
+val md_theme_light_shadow = Color(0xFF000000)
+val md_theme_light_surfaceTint = Color(0xFF745B00)
+
+val md_theme_dark_primary = Color(0xFFEFC125)
+val md_theme_dark_onPrimary = Color(0xFF3D2F00)
+val md_theme_dark_primaryContainer = Color(0xFF584400)
+val md_theme_dark_onPrimaryContainer = Color(0xFFFFE08C)
+val md_theme_dark_secondary = Color(0xFFDEC64C)
+val md_theme_dark_onSecondary = Color(0xFF393000)
+val md_theme_dark_secondaryContainer = Color(0xFF524600)
+val md_theme_dark_onSecondaryContainer = Color(0xFFFCE365)
+val md_theme_dark_tertiary = Color(0xFFB0D36D)
+val md_theme_dark_onTertiary = Color(0xFF243600)
+val md_theme_dark_tertiaryContainer = Color(0xFF364E00)
+val md_theme_dark_onTertiaryContainer = Color(0xFFCBEF86)
+val md_theme_dark_error = Color(0xFFFFB4AB)
+val md_theme_dark_errorContainer = Color(0xFF93000A)
+val md_theme_dark_onError = Color(0xFF690005)
+val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
+val md_theme_dark_background = Color(0xFF032100)
+val md_theme_dark_onBackground = Color(0xFFB4F39B)
+val md_theme_dark_surface = Color(0xFF032100)
+val md_theme_dark_onSurface = Color(0xFFB4F39B)
+val md_theme_dark_surfaceVariant = Color(0xFF4C4639)
+val md_theme_dark_onSurfaceVariant = Color(0xFFCFC5B4)
+val md_theme_dark_outline = Color(0xFF989080)
+val md_theme_dark_inverseOnSurface = Color(0xFF032100)
+val md_theme_dark_inverseSurface = Color(0xFFB4F39B)
+val md_theme_dark_inversePrimary = Color(0xFF745B00)
+val md_theme_dark_shadow = Color(0xFF000000)
+val md_theme_dark_surfaceTint = Color(0xFFEFC125)
+
+val seed = Color(0xFFE2B513)
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 a1538a94b..f55b9dea7 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
@@ -1,44 +1,89 @@
package app.omnivore.omnivore.ui.theme
+import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.darkColors
-import androidx.compose.material.lightColors
+import androidx.compose.material3.*
import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalContext
-private val DarkColorPalette = darkColors(
- primary = Purple200,
- primaryVariant = Purple700,
- secondary = Teal200
+private val LightColors = lightColorScheme(
+ primary = md_theme_light_primary,
+ onPrimary = md_theme_light_onPrimary,
+ primaryContainer = md_theme_light_primaryContainer,
+ onPrimaryContainer = md_theme_light_onPrimaryContainer,
+ secondary = md_theme_light_secondary,
+ onSecondary = md_theme_light_onSecondary,
+ secondaryContainer = md_theme_light_secondaryContainer,
+ onSecondaryContainer = md_theme_light_onSecondaryContainer,
+ tertiary = md_theme_light_tertiary,
+ onTertiary = md_theme_light_onTertiary,
+ tertiaryContainer = md_theme_light_tertiaryContainer,
+ onTertiaryContainer = md_theme_light_onTertiaryContainer,
+ error = md_theme_light_error,
+ errorContainer = md_theme_light_errorContainer,
+ onError = md_theme_light_onError,
+ onErrorContainer = md_theme_light_onErrorContainer,
+ background = md_theme_light_background,
+ onBackground = md_theme_light_onBackground,
+ surface = md_theme_light_surface,
+ onSurface = md_theme_light_onSurface,
+ surfaceVariant = md_theme_light_surfaceVariant,
+ onSurfaceVariant = md_theme_light_onSurfaceVariant,
+ outline = md_theme_light_outline,
+ inverseOnSurface = md_theme_light_inverseOnSurface,
+ inverseSurface = md_theme_light_inverseSurface,
+ inversePrimary = md_theme_light_inversePrimary,
+ surfaceTint = md_theme_light_surfaceTint,
)
-private val LightColorPalette = lightColors(
- primary = Purple500,
- primaryVariant = Purple700,
- secondary = Teal200
- /* Other default colors to override
- background = Color.White,
- surface = Color.White,
- onPrimary = Color.White,
- onSecondary = Color.Black,
- onBackground = Color.Black,
- onSurface = Color.Black,
- */
+private val DarkColors = darkColorScheme(
+ primary = md_theme_dark_primary,
+ onPrimary = md_theme_dark_onPrimary,
+ primaryContainer = md_theme_dark_primaryContainer,
+ onPrimaryContainer = md_theme_dark_onPrimaryContainer,
+ secondary = md_theme_dark_secondary,
+ onSecondary = md_theme_dark_onSecondary,
+ secondaryContainer = md_theme_dark_secondaryContainer,
+ onSecondaryContainer = md_theme_dark_onSecondaryContainer,
+ tertiary = md_theme_dark_tertiary,
+ onTertiary = md_theme_dark_onTertiary,
+ tertiaryContainer = md_theme_dark_tertiaryContainer,
+ onTertiaryContainer = md_theme_dark_onTertiaryContainer,
+ error = md_theme_dark_error,
+ errorContainer = md_theme_dark_errorContainer,
+ onError = md_theme_dark_onError,
+ onErrorContainer = md_theme_dark_onErrorContainer,
+ background = md_theme_dark_background,
+ onBackground = md_theme_dark_onBackground,
+ surface = md_theme_dark_surface,
+ onSurface = md_theme_dark_onSurface,
+ surfaceVariant = md_theme_dark_surfaceVariant,
+ onSurfaceVariant = md_theme_dark_onSurfaceVariant,
+ outline = md_theme_dark_outline,
+ inverseOnSurface = md_theme_dark_inverseOnSurface,
+ inverseSurface = md_theme_dark_inverseSurface,
+ inversePrimary = md_theme_dark_inversePrimary,
+ surfaceTint = md_theme_dark_surfaceTint,
)
@Composable
-fun OmnivoreTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) {
- val colors = if (darkTheme) {
- DarkColorPalette
+fun OmnivoreTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
+ useDynamicTheme: Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S,
+ content: @Composable () -> Unit
+) {
+ val colorScheme = if (useDynamicTheme) {
+ val context = LocalContext.current
+ if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
} else {
- LightColorPalette
+ if (darkTheme) DarkColors else LightColors
}
MaterialTheme(
- colors = colors,
- typography = Typography,
- shapes = Shapes,
+ colorScheme = colorScheme,
+// typography = Typography,
+// shapes = Shapes,
content = content
)
}
diff --git a/android/Omnivore/app/src/main/res/drawable/ic_logo_google.xml b/android/Omnivore/app/src/main/res/drawable/ic_logo_google.xml
new file mode 100644
index 000000000..d9425d95c
--- /dev/null
+++ b/android/Omnivore/app/src/main/res/drawable/ic_logo_google.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
diff --git a/android/Omnivore/app/src/main/res/drawable/ic_x_2.xml b/android/Omnivore/app/src/main/res/drawable/ic_x_2.xml
new file mode 100644
index 000000000..0899ee9ed
--- /dev/null
+++ b/android/Omnivore/app/src/main/res/drawable/ic_x_2.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/android/Omnivore/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/Omnivore/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
deleted file mode 100644
index 6b78462d6..000000000
--- a/android/Omnivore/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/android/Omnivore/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/Omnivore/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
deleted file mode 100644
index 6b78462d6..000000000
--- a/android/Omnivore/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/android/SaveToOmnivore/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/Omnivore/app/src/main/res/mipmap-hdpi/ic_launcher.png
similarity index 100%
rename from android/SaveToOmnivore/app/src/main/res/mipmap-hdpi/ic_launcher.png
rename to android/Omnivore/app/src/main/res/mipmap-hdpi/ic_launcher.png
diff --git a/android/Omnivore/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/android/Omnivore/app/src/main/res/mipmap-hdpi/ic_launcher.webp
deleted file mode 100644
index c209e78ec..000000000
Binary files a/android/Omnivore/app/src/main/res/mipmap-hdpi/ic_launcher.webp and /dev/null differ
diff --git a/android/SaveToOmnivore/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/android/Omnivore/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
similarity index 100%
rename from android/SaveToOmnivore/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
rename to android/Omnivore/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
diff --git a/android/Omnivore/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/android/Omnivore/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
deleted file mode 100644
index b2dfe3d1b..000000000
Binary files a/android/Omnivore/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/android/SaveToOmnivore/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/Omnivore/app/src/main/res/mipmap-mdpi/ic_launcher.png
similarity index 100%
rename from android/SaveToOmnivore/app/src/main/res/mipmap-mdpi/ic_launcher.png
rename to android/Omnivore/app/src/main/res/mipmap-mdpi/ic_launcher.png
diff --git a/android/Omnivore/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/android/Omnivore/app/src/main/res/mipmap-mdpi/ic_launcher.webp
deleted file mode 100644
index 4f0f1d64e..000000000
Binary files a/android/Omnivore/app/src/main/res/mipmap-mdpi/ic_launcher.webp and /dev/null differ
diff --git a/android/SaveToOmnivore/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/android/Omnivore/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
similarity index 100%
rename from android/SaveToOmnivore/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
rename to android/Omnivore/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
diff --git a/android/SaveToOmnivore/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/android/Omnivore/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
similarity index 100%
rename from android/SaveToOmnivore/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
rename to android/Omnivore/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
diff --git a/android/Omnivore/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/android/Omnivore/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
deleted file mode 100644
index 62b611da0..000000000
Binary files a/android/Omnivore/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/android/SaveToOmnivore/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/Omnivore/app/src/main/res/mipmap-xhdpi/ic_launcher.png
similarity index 100%
rename from android/SaveToOmnivore/app/src/main/res/mipmap-xhdpi/ic_launcher.png
rename to android/Omnivore/app/src/main/res/mipmap-xhdpi/ic_launcher.png
diff --git a/android/Omnivore/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/android/Omnivore/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
deleted file mode 100644
index 948a3070f..000000000
Binary files a/android/Omnivore/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and /dev/null differ
diff --git a/android/SaveToOmnivore/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/android/Omnivore/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
similarity index 100%
rename from android/SaveToOmnivore/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
rename to android/Omnivore/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
diff --git a/android/SaveToOmnivore/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/android/Omnivore/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
similarity index 100%
rename from android/SaveToOmnivore/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
rename to android/Omnivore/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
diff --git a/android/Omnivore/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/android/Omnivore/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
deleted file mode 100644
index 1b9a6956b..000000000
Binary files a/android/Omnivore/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/android/Omnivore/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/android/Omnivore/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
deleted file mode 100644
index 28d4b77f9..000000000
Binary files a/android/Omnivore/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and /dev/null differ
diff --git a/android/SaveToOmnivore/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/android/Omnivore/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
similarity index 100%
rename from android/SaveToOmnivore/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
rename to android/Omnivore/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
diff --git a/android/SaveToOmnivore/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/android/Omnivore/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
similarity index 100%
rename from android/SaveToOmnivore/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
rename to android/Omnivore/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
diff --git a/android/Omnivore/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/android/Omnivore/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
deleted file mode 100644
index 9287f5083..000000000
Binary files a/android/Omnivore/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/android/SaveToOmnivore/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/Omnivore/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
similarity index 100%
rename from android/SaveToOmnivore/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
rename to android/Omnivore/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
diff --git a/android/Omnivore/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/android/Omnivore/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
deleted file mode 100644
index aa7d6427e..000000000
Binary files a/android/Omnivore/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and /dev/null differ
diff --git a/android/SaveToOmnivore/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/android/Omnivore/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
similarity index 100%
rename from android/SaveToOmnivore/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
rename to android/Omnivore/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
diff --git a/android/SaveToOmnivore/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/Omnivore/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
similarity index 100%
rename from android/SaveToOmnivore/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
rename to android/Omnivore/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
diff --git a/android/Omnivore/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/android/Omnivore/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
deleted file mode 100644
index 9126ae37c..000000000
Binary files a/android/Omnivore/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/android/Omnivore/app/src/main/res/values/colors.xml b/android/Omnivore/app/src/main/res/values/colors.xml
index ca1931bca..ee4d3d2e0 100644
--- a/android/Omnivore/app/src/main/res/values/colors.xml
+++ b/android/Omnivore/app/src/main/res/values/colors.xml
@@ -1,10 +1,7 @@
- #FFBB86FC
- #FF6200EE
- #FF3700B3
- #FF03DAC5
- #FF018786
#FF000000
#FFFFFFFF
+ #F7F7F7
+ #B7B7B7
diff --git a/android/SaveToOmnivore/app/src/main/res/values/ic_launcher_background.xml b/android/Omnivore/app/src/main/res/values/ic_launcher_background.xml
similarity index 100%
rename from android/SaveToOmnivore/app/src/main/res/values/ic_launcher_background.xml
rename to android/Omnivore/app/src/main/res/values/ic_launcher_background.xml
diff --git a/android/Omnivore/app/src/main/res/values/strings.xml b/android/Omnivore/app/src/main/res/values/strings.xml
index e537d2ef1..2f4570ea1 100644
--- a/android/Omnivore/app/src/main/res/values/strings.xml
+++ b/android/Omnivore/app/src/main/res/values/strings.xml
@@ -1,3 +1,4 @@
Omnivore
-
\ No newline at end of file
+ 590528454690-mpmbvcb4c3ifmnojmrultt37eo8f64at.apps.googleusercontent.com
+
diff --git a/android/Omnivore/app/src/main/res/values/themes.xml b/android/Omnivore/app/src/main/res/values/themes.xml
index fb34267cb..f5b5ca333 100644
--- a/android/Omnivore/app/src/main/res/values/themes.xml
+++ b/android/Omnivore/app/src/main/res/values/themes.xml
@@ -1,7 +1,25 @@
-
+
+
diff --git a/android/Omnivore/build.gradle b/android/Omnivore/build.gradle
index abceb186b..a6761e531 100644
--- a/android/Omnivore/build.gradle
+++ b/android/Omnivore/build.gradle
@@ -1,14 +1,26 @@
buildscript {
ext {
- compose_version = '1.1.0-beta01'
+ compose_version = '1.3.0-alpha03'
+ lifecycle_version = '2.5.1'
+ kotlin_version = '1.6.20'
}
+
+ dependencies {
+ classpath "com.google.dagger:hilt-android-gradle-plugin:2.42"
+ classpath 'com.android.tools.build:gradle:7.2.2'
+ }
+
}// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
- id 'com.android.application' version '7.2.1' apply false
- id 'com.android.library' version '7.2.1' apply false
- id 'org.jetbrains.kotlin.android' version '1.5.31' apply false
+ id 'com.android.application' version '7.2.2' apply false
+ id 'com.android.library' version '7.2.2' apply false
+ id 'org.jetbrains.kotlin.android' version '1.6.20' apply false
+// id 'com.android.application' version '7.2.2' apply false
+// id 'com.android.library' version '7.2.2' apply false
+// id 'org.jetbrains.kotlin.android' version '1.7.20-Beta' apply false
}
task clean(type: Delete) {
delete rootProject.buildDir
}
+
diff --git a/android/Omnivore/gradle/wrapper/gradle-wrapper.properties b/android/Omnivore/gradle/wrapper/gradle-wrapper.properties
index 29d14b04b..5bf8d8081 100644
--- a/android/Omnivore/gradle/wrapper/gradle-wrapper.properties
+++ b/android/Omnivore/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Tue Aug 02 13:19:15 PDT 2022
+#Wed Aug 10 17:39:47 PDT 2022
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
diff --git a/android/SaveToOmnivore/.gitignore b/android/SaveToOmnivore/.gitignore
deleted file mode 100644
index aa724b770..000000000
--- a/android/SaveToOmnivore/.gitignore
+++ /dev/null
@@ -1,15 +0,0 @@
-*.iml
-.gradle
-/local.properties
-/.idea/caches
-/.idea/libraries
-/.idea/modules.xml
-/.idea/workspace.xml
-/.idea/navEditor.xml
-/.idea/assetWizardSettings.xml
-.DS_Store
-/build
-/captures
-.externalNativeBuild
-.cxx
-local.properties
diff --git a/android/SaveToOmnivore/app/.gitignore b/android/SaveToOmnivore/app/.gitignore
deleted file mode 100644
index 42afabfd2..000000000
--- a/android/SaveToOmnivore/app/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
\ No newline at end of file
diff --git a/android/SaveToOmnivore/app/build.gradle b/android/SaveToOmnivore/app/build.gradle
deleted file mode 100644
index 9c89431b1..000000000
--- a/android/SaveToOmnivore/app/build.gradle
+++ /dev/null
@@ -1,73 +0,0 @@
-
-plugins {
- id 'com.android.application'
- id 'org.jetbrains.kotlin.android'
- id("com.apollographql.apollo3").version("3.4.0")
-}
-
-android {
- compileSdk 32
-
- defaultConfig {
- applicationId "app.omnivore.savetoomnivore"
- minSdk 23
- targetSdk 32
- versionCode 3
- versionName "0.3"
-
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- vectorDrawables {
- useSupportLibrary true
- }
- }
-
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
- }
- }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
- kotlinOptions {
- jvmTarget = '1.8'
- }
- buildFeatures {
- compose true
- }
- composeOptions {
- kotlinCompilerExtensionVersion "1.2.0-alpha08"
- }
- packagingOptions {
- resources {
- excludes += '/META-INF/{AL2.0,LGPL2.1}'
- }
- }
-}
-
-dependencies {
-
- implementation 'androidx.core:core-ktx:1.7.0'
- implementation "androidx.compose.ui:ui:$compose_version"
- implementation 'androidx.compose.material3:material3:1.0.0-alpha14'
- implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
- implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
- implementation 'androidx.activity:activity-compose:1.3.1'
- implementation 'com.apollographql.apollo3:apollo-runtime:3.4.0'
- implementation 'androidx.security:security-crypto-ktx:1.1.0-alpha03'
- implementation 'androidx.datastore:datastore-core:1.0.0-rc01'
- implementation "androidx.datastore:datastore-preferences:1.0.0"
-
- testImplementation 'junit:junit:4.13.2'
- androidTestImplementation 'androidx.test.ext:junit:1.1.3'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
- androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
- debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
- debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
-}
-
-apollo {
- packageName.set("app.omnivore.generated")
-}
\ No newline at end of file
diff --git a/android/SaveToOmnivore/app/proguard-rules.pro b/android/SaveToOmnivore/app/proguard-rules.pro
deleted file mode 100644
index 481bb4348..000000000
--- a/android/SaveToOmnivore/app/proguard-rules.pro
+++ /dev/null
@@ -1,21 +0,0 @@
-# Add project specific ProGuard rules here.
-# You can control the set of applied configuration files using the
-# proguardFiles setting in build.gradle.
-#
-# For more details, see
-# http://developer.android.com/guide/developing/tools/proguard.html
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-# public *;
-#}
-
-# Uncomment this to preserve the line number information for
-# debugging stack traces.
-#-keepattributes SourceFile,LineNumberTable
-
-# If you keep the line number information, uncomment this to
-# hide the original source file name.
-#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/android/SaveToOmnivore/app/src/androidTest/java/app/omnivore/savetoomnivore/ExampleInstrumentedTest.kt b/android/SaveToOmnivore/app/src/androidTest/java/app/omnivore/savetoomnivore/ExampleInstrumentedTest.kt
deleted file mode 100644
index 7bda9bc5a..000000000
--- a/android/SaveToOmnivore/app/src/androidTest/java/app/omnivore/savetoomnivore/ExampleInstrumentedTest.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package app.omnivore.savetoomnivore
-
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.ext.junit.runners.AndroidJUnit4
-
-import org.junit.Test
-import org.junit.runner.RunWith
-
-import org.junit.Assert.*
-
-/**
- * Instrumented test, which will execute on an Android device.
- *
- * See [testing documentation](http://d.android.com/tools/testing).
- */
-@RunWith(AndroidJUnit4::class)
-class ExampleInstrumentedTest {
- @Test
- fun useAppContext() {
- // Context of the app under test.
- val appContext = InstrumentationRegistry.getInstrumentation().targetContext
- assertEquals("app.omnivore.savetoomnivore", appContext.packageName)
- }
-}
\ No newline at end of file
diff --git a/android/SaveToOmnivore/app/src/main/AndroidManifest.xml b/android/SaveToOmnivore/app/src/main/AndroidManifest.xml
deleted file mode 100644
index bcab33f06..000000000
--- a/android/SaveToOmnivore/app/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/android/SaveToOmnivore/app/src/main/ic_launcher-playstore.png b/android/SaveToOmnivore/app/src/main/ic_launcher-playstore.png
deleted file mode 100644
index 3f5ca0237..000000000
Binary files a/android/SaveToOmnivore/app/src/main/ic_launcher-playstore.png and /dev/null differ
diff --git a/android/SaveToOmnivore/app/src/main/java/app/omnivore/savetoomnivore/AppDatastore.kt b/android/SaveToOmnivore/app/src/main/java/app/omnivore/savetoomnivore/AppDatastore.kt
deleted file mode 100644
index 2ad6b5ded..000000000
--- a/android/SaveToOmnivore/app/src/main/java/app/omnivore/savetoomnivore/AppDatastore.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-package app.omnivore.savetoomnivore
-
-import android.annotation.SuppressLint
-import android.content.Context
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.ui.platform.LocalContext
-import androidx.datastore.core.DataStore
-import androidx.datastore.preferences.core.Preferences
-import androidx.datastore.preferences.core.edit
-import androidx.datastore.preferences.core.stringPreferencesKey
-import androidx.datastore.preferences.preferencesDataStore
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
-
-class AppDatastore(private val context: Context) {
- private val Context.datastore: DataStore by preferencesDataStore(name = "app_datastore")
- private val APIKEY = stringPreferencesKey(name = "API_KEY")
-
- companion object {
- @SuppressLint("StaticFieldLeak")
- var INSTANCE: AppDatastore? = null
- fun getInstance(base: Context): AppDatastore? {
- if (INSTANCE == null) {
- synchronized(AppDatastore::class.java) {
- INSTANCE = AppDatastore(base.applicationContext)
- }
- }
-
- return INSTANCE
- }
- }
-
- suspend fun setApiKey(apiKey: String) {
- context.datastore.edit { preferences ->
- preferences[APIKEY] = apiKey
- }
- }
-
- val getApiKey: Flow = context.datastore.data.map { preferences ->
- preferences[APIKEY] ?: ""
- }
-}
\ No newline at end of file
diff --git a/android/SaveToOmnivore/app/src/main/java/app/omnivore/savetoomnivore/ExtensionActivity.kt b/android/SaveToOmnivore/app/src/main/java/app/omnivore/savetoomnivore/ExtensionActivity.kt
deleted file mode 100644
index f0545f764..000000000
--- a/android/SaveToOmnivore/app/src/main/java/app/omnivore/savetoomnivore/ExtensionActivity.kt
+++ /dev/null
@@ -1,106 +0,0 @@
-package app.omnivore.savetoomnivore
-
-import android.app.Activity
-import android.content.Context
-import android.content.Intent
-import android.os.Bundle
-import android.text.TextUtils.isEmpty
-import android.widget.Toast
-import androidx.activity.ComponentActivity
-import androidx.datastore.core.DataStore
-import androidx.datastore.preferences.core.Preferences
-import androidx.datastore.preferences.preferencesDataStore
-import app.omnivore.generated.SaveUrlMutation
-import app.omnivore.generated.type.SaveUrlInput
-import com.apollographql.apollo3.ApolloClient
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.launch
-import java.util.*
-import java.util.regex.Matcher
-import java.util.regex.Pattern
-
-
-private val Context.dataStore: DataStore by preferencesDataStore(
- name = "settings"
-)
-
-class ExtensionActivity : ComponentActivity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- when {
- intent?.action == Intent.ACTION_SEND -> {
- if ("text/plain" == intent.type) {
- handleSendText(intent) // Handle text being sent
- }
- }
- }
- finish()
- }
-
- fun saveURL(url: String) {
-
- GlobalScope.launch {
- val apiKey = AppDatastore.getInstance(baseContext)?.getApiKey?.first()
-
- val apolloClient = ApolloClient.Builder()
- .serverUrl("https://api-prod.omnivore.app/api/graphql")
- .addHttpHeader(
- "Authorization",
- value = apiKey.toString()
- )
- .build()
-
- val source = "android"
- val clientRequestId = UUID.randomUUID().toString()
- val response = apolloClient.mutation(
- SaveUrlMutation(
- SaveUrlInput(
- clientRequestId = clientRequestId,
- source = source,
- url = url
- )
- )
- ).execute()
-
- val success = (response.data?.saveUrl?.onSaveSuccess?.url != null)
-
- GlobalScope.launch(Dispatchers.Main) {
- runOnUiThread(Runnable {
- val message = if (success) "Saved to Omnivore" else "Error saving to Omnivore"
- Toast.makeText(baseContext, message, Toast.LENGTH_LONG).show()
- })
- }
- }
- }
-
- private fun handleSendText(intent: Intent) {
- intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
- val url = getUrl(it)
- if (url == null || isEmpty(url)) {
- Toast.makeText(this, "Error: no URL found.", Toast.LENGTH_LONG).show()
- } else {
- Toast.makeText(this, "Saving to Omnivore", Toast.LENGTH_LONG).show()
- saveURL(it)
- }
- }
- }
-
- fun getUrl(s: String): String? {
- val pattern =
- Pattern.compile("[(http(s)?):\\/\\/(www\\.)?a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)")
- try {
- val matcher: Matcher = pattern.matcher(s)
- if (matcher.find()) {
- return s.substring(matcher.start(), matcher.end())
- }
- } catch (e: Exception) {
- System.out.println("exception parsing string")
- System.out.println(e)
- return null
- }
- return null
- }
-}
diff --git a/android/SaveToOmnivore/app/src/main/java/app/omnivore/savetoomnivore/MainActivity.kt b/android/SaveToOmnivore/app/src/main/java/app/omnivore/savetoomnivore/MainActivity.kt
deleted file mode 100644
index aaaff30e9..000000000
--- a/android/SaveToOmnivore/app/src/main/java/app/omnivore/savetoomnivore/MainActivity.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-package app.omnivore.savetoomnivore
-
-import android.content.Context
-import android.content.SharedPreferences
-import android.os.Build
-import android.os.Bundle
-import android.provider.Settings
-import androidx.activity.ComponentActivity
-import androidx.activity.compose.setContent
-import androidx.annotation.RequiresApi
-import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.text.BasicTextField
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
-import androidx.compose.runtime.*
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.material3.OutlinedTextField
-import kotlinx.coroutines.launch
-import androidx.lifecycle.lifecycleScope
-import kotlinx.coroutines.GlobalScope
-
-class MainActivity : ComponentActivity() {
-
- @RequiresApi(Build.VERSION_CODES.M)
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- setContent {
- MaterialTheme {
- // A surface container using the 'background' color from the theme
- Surface(
- modifier = Modifier.fillMaxSize(),
- color = MaterialTheme.colorScheme.background
- ) {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- modifier = Modifier.fillMaxWidth()
- ) {
- Text("Save to Omnivore")
- var apiKey by remember { mutableStateOf("") }
- OutlinedTextField(
- value = apiKey,
- onValueChange = {
- apiKey = it
- GlobalScope.launch {
- setApiKey(apiKey)
- }
- },
- label = { Text("API Key") }
- )
- Text("Get an API key from https://omnivore.app/settings/api")
- }
- }
- }
- }
- }
-
- private suspend fun setApiKey(apiKey: String) {
- AppDatastore.getInstance(base = baseContext)?.setApiKey(apiKey)
- }
-}
diff --git a/android/SaveToOmnivore/app/src/main/java/app/omnivore/savetoomnivore/ui/theme/Color.kt b/android/SaveToOmnivore/app/src/main/java/app/omnivore/savetoomnivore/ui/theme/Color.kt
deleted file mode 100644
index babd26191..000000000
--- a/android/SaveToOmnivore/app/src/main/java/app/omnivore/savetoomnivore/ui/theme/Color.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package app.omnivore.savetoomnivore.ui.theme
-
-import androidx.compose.ui.graphics.Color
-
-val Purple80 = Color(0xFFD0BCFF)
-val PurpleGrey80 = Color(0xFFCCC2DC)
-val Pink80 = Color(0xFFEFB8C8)
-
-val Purple40 = Color(0xFF6650a4)
-val PurpleGrey40 = Color(0xFF625b71)
-val Pink40 = Color(0xFF7D5260)
\ No newline at end of file
diff --git a/android/SaveToOmnivore/app/src/main/java/app/omnivore/savetoomnivore/ui/theme/Theme.kt b/android/SaveToOmnivore/app/src/main/java/app/omnivore/savetoomnivore/ui/theme/Theme.kt
deleted file mode 100644
index 6dc8511dd..000000000
--- a/android/SaveToOmnivore/app/src/main/java/app/omnivore/savetoomnivore/ui/theme/Theme.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-package app.omnivore.savetoomnivore.ui.theme
-
-import android.app.Activity
-import android.os.Build
-import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.darkColorScheme
-import androidx.compose.material3.dynamicDarkColorScheme
-import androidx.compose.material3.dynamicLightColorScheme
-import androidx.compose.material3.lightColorScheme
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.SideEffect
-import androidx.compose.ui.graphics.toArgb
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalView
-import androidx.core.view.ViewCompat
-
-private val DarkColorScheme = darkColorScheme(
- primary = Purple80,
- secondary = PurpleGrey80,
- tertiary = Pink80
-)
-
-private val LightColorScheme = lightColorScheme(
- primary = Purple40,
- secondary = PurpleGrey40,
- tertiary = Pink40
-
- /* Other default colors to override
- background = Color(0xFFFFFBFE),
- surface = Color(0xFFFFFBFE),
- onPrimary = Color.White,
- onSecondary = Color.White,
- onTertiary = Color.White,
- onBackground = Color(0xFF1C1B1F),
- onSurface = Color(0xFF1C1B1F),
- */
-)
-
-@Composable
-fun SaveToOmnivoreTheme(
- darkTheme: Boolean = isSystemInDarkTheme(),
- // Dynamic color is available on Android 12+
- dynamicColor: Boolean = true,
- content: @Composable () -> Unit
-) {
- val colorScheme = when {
- dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
- val context = LocalContext.current
- if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
- }
- darkTheme -> DarkColorScheme
- else -> LightColorScheme
- }
- val view = LocalView.current
- if (!view.isInEditMode) {
- SideEffect {
- (view.context as Activity).window.statusBarColor = colorScheme.primary.toArgb()
- ViewCompat.getWindowInsetsController(view)?.isAppearanceLightStatusBars = darkTheme
- }
- }
-
- MaterialTheme(
- colorScheme = colorScheme,
- typography = Typography,
- content = content
- )
-}
\ No newline at end of file
diff --git a/android/SaveToOmnivore/app/src/main/java/app/omnivore/savetoomnivore/ui/theme/Type.kt b/android/SaveToOmnivore/app/src/main/java/app/omnivore/savetoomnivore/ui/theme/Type.kt
deleted file mode 100644
index be24a8304..000000000
--- a/android/SaveToOmnivore/app/src/main/java/app/omnivore/savetoomnivore/ui/theme/Type.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-package app.omnivore.savetoomnivore.ui.theme
-
-import androidx.compose.material3.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
-
-// Set of Material typography styles to start with
-val Typography = Typography(
- bodyLarge = TextStyle(
- fontFamily = FontFamily.Default,
- fontWeight = FontWeight.Normal,
- fontSize = 16.sp,
- lineHeight = 24.sp,
- letterSpacing = 0.5.sp
- )
- /* Other default text styles to override
- titleLarge = TextStyle(
- fontFamily = FontFamily.Default,
- fontWeight = FontWeight.Normal,
- fontSize = 22.sp,
- lineHeight = 28.sp,
- letterSpacing = 0.sp
- ),
- labelSmall = TextStyle(
- fontFamily = FontFamily.Default,
- fontWeight = FontWeight.Medium,
- fontSize = 11.sp,
- lineHeight = 16.sp,
- letterSpacing = 0.5.sp
- )
- */
-)
\ No newline at end of file
diff --git a/android/SaveToOmnivore/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/android/SaveToOmnivore/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
deleted file mode 100644
index 2b068d114..000000000
--- a/android/SaveToOmnivore/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/android/SaveToOmnivore/app/src/main/res/drawable/ic_launcher_background.xml b/android/SaveToOmnivore/app/src/main/res/drawable/ic_launcher_background.xml
deleted file mode 100644
index 07d5da9cb..000000000
--- a/android/SaveToOmnivore/app/src/main/res/drawable/ic_launcher_background.xml
+++ /dev/null
@@ -1,170 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/android/SaveToOmnivore/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/android/SaveToOmnivore/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
deleted file mode 100644
index 84f3ca04b..000000000
Binary files a/android/SaveToOmnivore/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/android/SaveToOmnivore/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/SaveToOmnivore/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index 624d9e310..000000000
Binary files a/android/SaveToOmnivore/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ
diff --git a/android/SaveToOmnivore/app/src/main/res/values/colors.xml b/android/SaveToOmnivore/app/src/main/res/values/colors.xml
deleted file mode 100644
index f8c6127d3..000000000
--- a/android/SaveToOmnivore/app/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
- #FFBB86FC
- #FF6200EE
- #FF3700B3
- #FF03DAC5
- #FF018786
- #FF000000
- #FFFFFFFF
-
\ No newline at end of file
diff --git a/android/SaveToOmnivore/app/src/main/res/values/strings.xml b/android/SaveToOmnivore/app/src/main/res/values/strings.xml
deleted file mode 100644
index e537d2ef1..000000000
--- a/android/SaveToOmnivore/app/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
- Omnivore
-
\ No newline at end of file
diff --git a/android/SaveToOmnivore/app/src/main/res/values/themes.xml b/android/SaveToOmnivore/app/src/main/res/values/themes.xml
deleted file mode 100644
index 1a1372a95..000000000
--- a/android/SaveToOmnivore/app/src/main/res/values/themes.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/android/SaveToOmnivore/app/src/main/res/xml/backup_rules.xml b/android/SaveToOmnivore/app/src/main/res/xml/backup_rules.xml
deleted file mode 100644
index fa0f996d2..000000000
--- a/android/SaveToOmnivore/app/src/main/res/xml/backup_rules.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/android/SaveToOmnivore/app/src/main/res/xml/data_extraction_rules.xml b/android/SaveToOmnivore/app/src/main/res/xml/data_extraction_rules.xml
deleted file mode 100644
index 9ee9997b0..000000000
--- a/android/SaveToOmnivore/app/src/main/res/xml/data_extraction_rules.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/android/SaveToOmnivore/app/src/test/java/app/omnivore/savetoomnivore/ExampleUnitTest.kt b/android/SaveToOmnivore/app/src/test/java/app/omnivore/savetoomnivore/ExampleUnitTest.kt
deleted file mode 100644
index 332df63c4..000000000
--- a/android/SaveToOmnivore/app/src/test/java/app/omnivore/savetoomnivore/ExampleUnitTest.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package app.omnivore.savetoomnivore
-
-import org.junit.Test
-
-import org.junit.Assert.*
-
-/**
- * Example local unit test, which will execute on the development machine (host).
- *
- * See [testing documentation](http://d.android.com/tools/testing).
- */
-class ExampleUnitTest {
- @Test
- fun addition_isCorrect() {
- assertEquals(4, 2 + 2)
- }
-}
\ No newline at end of file
diff --git a/android/SaveToOmnivore/build.gradle b/android/SaveToOmnivore/build.gradle
deleted file mode 100644
index caac89d71..000000000
--- a/android/SaveToOmnivore/build.gradle
+++ /dev/null
@@ -1,14 +0,0 @@
-buildscript {
- ext {
- compose_version = '1.1.0-beta01'
- }
-}// Top-level build file where you can add configuration options common to all sub-projects/modules.
-plugins {
- id 'com.android.application' version '7.2.1' apply false
- id 'com.android.library' version '7.2.1' apply false
- id 'org.jetbrains.kotlin.android' version '1.6.20' apply false
-}
-
-task clean(type: Delete) {
- delete rootProject.buildDir
-}
\ No newline at end of file
diff --git a/android/SaveToOmnivore/gradle.properties b/android/SaveToOmnivore/gradle.properties
deleted file mode 100644
index cd0519bb2..000000000
--- a/android/SaveToOmnivore/gradle.properties
+++ /dev/null
@@ -1,23 +0,0 @@
-# Project-wide Gradle settings.
-# IDE (e.g. Android Studio) users:
-# Gradle settings configured through the IDE *will override*
-# any settings specified in this file.
-# For more details on how to configure your build environment visit
-# http://www.gradle.org/docs/current/userguide/build_environment.html
-# Specifies the JVM arguments used for the daemon process.
-# The setting is particularly useful for tweaking memory settings.
-org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
-# When configured, Gradle will run in incubating parallel mode.
-# This option should only be used with decoupled projects. More details, visit
-# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
-# org.gradle.parallel=true
-# AndroidX package structure to make it clearer which packages are bundled with the
-# Android operating system, and which are packaged with your app"s APK
-# https://developer.android.com/topic/libraries/support-library/androidx-rn
-android.useAndroidX=true
-# Kotlin code style for this project: "official" or "obsolete":
-kotlin.code.style=official
-# Enables namespacing of each library's R class so that its R class includes only the
-# resources declared in the library itself and none from the library's dependencies,
-# thereby reducing the size of the R class for that library
-android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/android/SaveToOmnivore/gradle/wrapper/gradle-wrapper.jar b/android/SaveToOmnivore/gradle/wrapper/gradle-wrapper.jar
deleted file mode 100644
index e708b1c02..000000000
Binary files a/android/SaveToOmnivore/gradle/wrapper/gradle-wrapper.jar and /dev/null differ
diff --git a/android/SaveToOmnivore/gradle/wrapper/gradle-wrapper.properties b/android/SaveToOmnivore/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index 9e79af52e..000000000
--- a/android/SaveToOmnivore/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-#Tue Jul 19 16:31:46 PDT 2022
-distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
-distributionPath=wrapper/dists
-zipStorePath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
diff --git a/android/SaveToOmnivore/gradlew b/android/SaveToOmnivore/gradlew
deleted file mode 100755
index 4f906e0c8..000000000
--- a/android/SaveToOmnivore/gradlew
+++ /dev/null
@@ -1,185 +0,0 @@
-#!/usr/bin/env sh
-
-#
-# Copyright 2015 the original author or authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# https://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-##############################################################################
-##
-## Gradle start up script for UN*X
-##
-##############################################################################
-
-# Attempt to set APP_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
-
-warn () {
- echo "$*"
-}
-
-die () {
- echo
- echo "$*"
- echo
- exit 1
-}
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-nonstop=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
- NONSTOP* )
- nonstop=true
- ;;
-esac
-
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
-
-
-# Determine the Java command to use to start the JVM.
-if [ -n "$JAVA_HOME" ] ; then
- if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
- # IBM's JDK on AIX uses strange locations for the executables
- JAVACMD="$JAVA_HOME/jre/sh/java"
- else
- JAVACMD="$JAVA_HOME/bin/java"
- fi
- if [ ! -x "$JAVACMD" ] ; then
- die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
- fi
-else
- JAVACMD="java"
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-fi
-
-# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
- MAX_FD_LIMIT=`ulimit -H -n`
- if [ $? -eq 0 ] ; then
- if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
- MAX_FD="$MAX_FD_LIMIT"
- fi
- ulimit -n $MAX_FD
- if [ $? -ne 0 ] ; then
- warn "Could not set maximum file descriptor limit: $MAX_FD"
- fi
- else
- warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
- fi
-fi
-
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
- GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
-
-# For Cygwin or MSYS, switch paths to Windows format before running java
-if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
- APP_HOME=`cygpath --path --mixed "$APP_HOME"`
- CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
-
- JAVACMD=`cygpath --unix "$JAVACMD"`
-
- # We build the pattern for arguments to be converted via cygpath
- ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
- SEP=""
- for dir in $ROOTDIRSRAW ; do
- ROOTDIRS="$ROOTDIRS$SEP$dir"
- SEP="|"
- done
- OURCYGPATTERN="(^($ROOTDIRS))"
- # Add a user-defined pattern to the cygpath arguments
- if [ "$GRADLE_CYGPATTERN" != "" ] ; then
- OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
- fi
- # Now convert the arguments - kludge to limit ourselves to /bin/sh
- i=0
- for arg in "$@" ; do
- CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
- CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
-
- if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
- eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
- else
- eval `echo args$i`="\"$arg\""
- fi
- i=`expr $i + 1`
- done
- case $i in
- 0) set -- ;;
- 1) set -- "$args0" ;;
- 2) set -- "$args0" "$args1" ;;
- 3) set -- "$args0" "$args1" "$args2" ;;
- 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
- esac
-fi
-
-# Escape application args
-save () {
- for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
- echo " "
-}
-APP_ARGS=`save "$@"`
-
-# Collect all arguments for the java command, following the shell quoting and substitution rules
-eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
-
-exec "$JAVACMD" "$@"
diff --git a/android/SaveToOmnivore/gradlew.bat b/android/SaveToOmnivore/gradlew.bat
deleted file mode 100644
index ac1b06f93..000000000
--- a/android/SaveToOmnivore/gradlew.bat
+++ /dev/null
@@ -1,89 +0,0 @@
-@rem
-@rem Copyright 2015 the original author or authors.
-@rem
-@rem Licensed under the Apache License, Version 2.0 (the "License");
-@rem you may not use this file except in compliance with the License.
-@rem You may obtain a copy of the License at
-@rem
-@rem https://www.apache.org/licenses/LICENSE-2.0
-@rem
-@rem Unless required by applicable law or agreed to in writing, software
-@rem distributed under the License is distributed on an "AS IS" BASIS,
-@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-@rem See the License for the specific language governing permissions and
-@rem limitations under the License.
-@rem
-
-@if "%DEBUG%" == "" @echo off
-@rem ##########################################################################
-@rem
-@rem Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Resolve any "." and ".." in APP_HOME to make it shorter.
-for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto execute
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto execute
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
-
-:end
-@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
diff --git a/android/SaveToOmnivore/settings.gradle b/android/SaveToOmnivore/settings.gradle
deleted file mode 100644
index 40d2999e2..000000000
--- a/android/SaveToOmnivore/settings.gradle
+++ /dev/null
@@ -1,16 +0,0 @@
-pluginManagement {
- repositories {
- gradlePluginPortal()
- google()
- mavenCentral()
- }
-}
-dependencyResolutionManagement {
- repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
- repositories {
- google()
- mavenCentral()
- }
-}
-rootProject.name = "SaveToOmnivore"
-include ':app'
diff --git a/packages/api/.env.example b/packages/api/.env.example
index 6e90843b6..78aa2387a 100644
--- a/packages/api/.env.example
+++ b/packages/api/.env.example
@@ -12,6 +12,7 @@ GATEWAY_URL=http://localhost:4000/api
IMAGE_PROXY_URL=http://localhost:8080
IMAGE_PROXY_SECRET_KEY=some-secret
GAUTH_IOS_CLIENT_ID=
+GAUTH_ANDROID_CLIENT_ID=
GAUTH_CLIENT_ID='notset'
GAUTH_SECRET='notset'
GCP_PROJECT_ID=omnivore-local
diff --git a/packages/api/.env.test b/packages/api/.env.test
index 16fd0aaf8..bfdce2355 100644
--- a/packages/api/.env.test
+++ b/packages/api/.env.test
@@ -12,6 +12,7 @@ GATEWAY_URL=http://localhost:4000/api
IMAGE_PROXY_URL=http://localhost:8080
IMAGE_PROXY_SECRET_KEY=some-secret
GAUTH_IOS_CLIENT_ID=
+GAUTH_ANDROID_CLIENT_ID=
GAUTH_CLIENT_ID='notset'
GAUTH_SECRET='notset'
GCP_PROJECT_ID=omnivore-local
diff --git a/packages/api/src/routers/auth/google_auth.ts b/packages/api/src/routers/auth/google_auth.ts
index e6c78eafb..c414dcf5c 100644
--- a/packages/api/src/routers/auth/google_auth.ts
+++ b/packages/api/src/routers/auth/google_auth.ts
@@ -57,16 +57,22 @@ export const validateGoogleUser = async (
}
}
-const googleMobileClient = new OAuth2Client(env.google.auth.iosClientId)
-const googleWebClient = new OAuth2Client(env.google.auth.clientId)
+const iosClientId = env.google.auth.iosClientId
+const webClientId = env.google.auth.clientId
+
+const googleWebClient = new OAuth2Client(webClientId)
+const googleIOSClient = new OAuth2Client(iosClientId)
export async function decodeGoogleToken(
- idToken: string
+ idToken: string,
+ isAndroid: boolean
): Promise {
try {
+ const googleMobileClient = isAndroid ? googleWebClient : googleIOSClient
+
const loginTicket = await googleMobileClient.verifyIdToken({
idToken,
- audience: env.google.auth.iosClientId,
+ audience: isAndroid ? webClientId : iosClientId,
})
const email = loginTicket.getPayload()?.email
diff --git a/packages/api/src/routers/auth/mobile/mobile_auth_router.ts b/packages/api/src/routers/auth/mobile/mobile_auth_router.ts
index 138aae9c8..50890aaab 100644
--- a/packages/api/src/routers/auth/mobile/mobile_auth_router.ts
+++ b/packages/api/src/routers/auth/mobile/mobile_auth_router.ts
@@ -16,8 +16,9 @@ export function mobileAuthRouter() {
const router = express.Router()
router.post('/sign-in', async (req, res) => {
- const { token, provider } = req.body
- const payload = await createMobileSignInResponse(token, provider)
+ const { token, provider, source } = req.body
+ const isAndroid = source === 'ANDROID'
+ const payload = await createMobileSignInResponse(isAndroid, token, provider)
res.status(payload.statusCode).json(payload.json)
})
@@ -39,8 +40,14 @@ export function mobileAuthRouter() {
})
router.post('/sign-up', async (req, res) => {
- const { token, provider, name } = req.body
- const payload = await createMobileSignUpResponse(token, provider, name)
+ const { token, provider, name, source } = req.body
+ const isAndroid = source === 'ANDROID'
+ const payload = await createMobileSignUpResponse(
+ isAndroid,
+ token,
+ provider,
+ name
+ )
res.status(payload.statusCode).json(payload.json)
})
diff --git a/packages/api/src/routers/auth/mobile/sign_in.ts b/packages/api/src/routers/auth/mobile/sign_in.ts
index 699c38aa0..8feb36d41 100644
--- a/packages/api/src/routers/auth/mobile/sign_in.ts
+++ b/packages/api/src/routers/auth/mobile/sign_in.ts
@@ -15,12 +15,13 @@ import { StatusType } from '../../../datalayer/user/model'
import { comparePassword } from '../../../utils/auth'
export async function createMobileSignInResponse(
+ isAndroid: boolean,
token?: string,
provider?: AuthProvider
): Promise {
try {
if (token && provider === 'GOOGLE') {
- const decodedTokenResult = await decodeGoogleToken(token)
+ const decodedTokenResult = await decodeGoogleToken(token, isAndroid)
return createAuthResponsePayload(provider, decodedTokenResult)
}
diff --git a/packages/api/src/routers/auth/mobile/sign_up.ts b/packages/api/src/routers/auth/mobile/sign_up.ts
index ef36f481c..fea03ef97 100644
--- a/packages/api/src/routers/auth/mobile/sign_up.ts
+++ b/packages/api/src/routers/auth/mobile/sign_up.ts
@@ -13,13 +13,14 @@ import { hashPassword } from '../../../utils/auth'
import { createUser } from '../../../services/create_user'
export async function createMobileSignUpResponse(
+ isAndroid: boolean,
token?: string,
provider?: AuthProvider,
name?: string
): Promise {
try {
if (token && provider === 'GOOGLE') {
- const decodedTokenResult = await decodeGoogleToken(token)
+ const decodedTokenResult = await decodeGoogleToken(token, isAndroid)
return createSignUpResponsePayload(
provider,
decodedTokenResult,
diff --git a/packages/api/src/util.ts b/packages/api/src/util.ts
index 1ca344d02..ea61b3ba2 100755
--- a/packages/api/src/util.ts
+++ b/packages/api/src/util.ts
@@ -30,6 +30,7 @@ interface BackendEnv {
google: {
auth: {
iosClientId: string
+ androidClientId: string
clientId: string
secret: string
}
@@ -127,6 +128,7 @@ const nullableEnvVars = [
'PREVIEW_GENERATION_SERVICE_URL',
'GCS_UPLOAD_SA_KEY_FILE_PATH',
'GAUTH_IOS_CLIENT_ID',
+ 'GAUTH_ANDROID_CLIENT_ID',
'GAUTH_CLIENT_ID',
'GAUTH_SECRET',
'SEGMENT_WRITE_KEY',
@@ -203,6 +205,7 @@ export function getEnv(): BackendEnv {
const google = {
auth: {
iosClientId: parse('GAUTH_IOS_CLIENT_ID'),
+ androidClientId: parse('GAUTH_ANDROID_CLIENT_ID'),
clientId: parse('GAUTH_CLIENT_ID'),
secret: parse('GAUTH_SECRET'),
},