mirror of
https://github.com/foobnix/LibreraReader.git
synced 2025-10-06 00:02:43 +02:00
refactoring
This commit is contained in:
@@ -81,6 +81,10 @@ android {
|
||||
debuggable = true
|
||||
}
|
||||
}
|
||||
buildFeatures {
|
||||
compose true
|
||||
}
|
||||
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
@@ -287,6 +291,10 @@ dependencies {
|
||||
fdroidImplementation project(':pro')
|
||||
implementation project(':smartreflow')
|
||||
|
||||
//compose
|
||||
implementation 'androidx.compose.ui:ui'
|
||||
implementation project(":googleDrive")
|
||||
|
||||
|
||||
/** AndroidX **/
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
@@ -455,7 +463,7 @@ task incVersion() {
|
||||
if (!taskName.contains("Fdroid")) {
|
||||
dependencies {
|
||||
implementation "com.github.junrar:junrar:4.0.0"
|
||||
implementation project(":googleDrive")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -31,6 +31,7 @@ import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.compose.ui.platform.ComposeView;
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
import androidx.core.util.Pair;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
@@ -94,6 +95,8 @@ import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
|
||||
import mobi.librera.lib.gdrive.GoogleSignInComposeHelper;
|
||||
|
||||
public class SearchFragment2 extends UIFragment<FileMeta> {
|
||||
|
||||
public static final String EMPTY_ID = "\u00A0";
|
||||
@@ -356,6 +359,18 @@ public class SearchFragment2 extends UIFragment<FileMeta> {
|
||||
|
||||
handler = new Handler(Looper.getMainLooper());
|
||||
|
||||
|
||||
ComposeView composeView = (ComposeView) view.findViewById(R.id.compose_view);
|
||||
|
||||
GoogleSignInComposeHelper.createSimpleGoogleSignInButton(
|
||||
composeView,
|
||||
"Sign in with Google",
|
||||
() -> {
|
||||
// Handle sign-in click
|
||||
LOG.d("Google Sign-In button clicked");
|
||||
}
|
||||
);
|
||||
|
||||
secondTopPanel = view.findViewById(R.id.secondTopPanel);
|
||||
countBooks = (TextView) view.findViewById(R.id.countBooks);
|
||||
onRefresh = view.findViewById(R.id.onRefresh);
|
||||
|
@@ -6,6 +6,12 @@
|
||||
android:orientation="vertical">
|
||||
|
||||
|
||||
<androidx.compose.ui.platform.ComposeView
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:id="@+id/compose_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/filterLayout"
|
||||
@@ -29,7 +35,6 @@
|
||||
android:src="@drawable/glyphicons_600_menu" />
|
||||
|
||||
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_marginLeft="2dip"
|
||||
android:layout_marginRight="2dip"
|
||||
@@ -167,8 +172,7 @@
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
>
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.foobnix.ui2.fast.FastScrollRecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
@@ -179,8 +183,7 @@
|
||||
app:handleColor="#999999"
|
||||
app:hideScrollbar="true"
|
||||
app:showTrack="false"
|
||||
app:trackColor="#bbbbbb" >
|
||||
</com.foobnix.ui2.fast.FastScrollRecyclerView>
|
||||
app:trackColor="#bbbbbb"></com.foobnix.ui2.fast.FastScrollRecyclerView>
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
@@ -50,6 +50,26 @@ android {
|
||||
room {
|
||||
schemaDirectory("$projectDir/schemas")
|
||||
}
|
||||
|
||||
packaging {
|
||||
resources {
|
||||
excludes += setOf(
|
||||
"META-INF/DEPENDENCIES",
|
||||
"META-INF/NOTICE",
|
||||
"META-INF/LICENSE",
|
||||
"META-INF/LICENSE.txt",
|
||||
"META-INF/NOTICE.txt",
|
||||
"META-INF/INDEX.LIST",
|
||||
"META-INF/io.netty.versions.properties"
|
||||
)
|
||||
pickFirsts += setOf(
|
||||
"google/protobuf/*.proto",
|
||||
"META-INF/services/io.grpc.LoadBalancerProvider",
|
||||
"META-INF/services/io.grpc.ManagedChannelProvider",
|
||||
"META-INF/services/io.grpc.NameResolverProvider"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
@@ -60,29 +80,10 @@ kotlin {
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation(platform("com.google.firebase:firebase-bom:34.0.0"))
|
||||
implementation("com.google.firebase:firebase-auth")
|
||||
implementation("com.google.firebase:firebase-firestore")
|
||||
//implementation("com.google.firebase:firebase-database") realtime
|
||||
implementation("com.google.firebase:firebase-storage")
|
||||
// Firebase BOM
|
||||
|
||||
implementation("androidx.credentials:credentials:1.5.0")
|
||||
implementation("androidx.credentials:credentials-play-services-auth:1.5.0")
|
||||
implementation("com.google.android.libraries.identity.googleid:googleid:1.1.1")
|
||||
|
||||
//google drive
|
||||
implementation("com.google.android.gms:play-services-auth:21.4.0")
|
||||
implementation("com.google.api-client:google-api-client-android:2.8.0") {
|
||||
exclude(group = "org.apache.httpcomponents")
|
||||
}
|
||||
implementation("com.google.http-client:google-http-client-gson:1.47.1") {
|
||||
exclude(group = "org.apache.httpcomponents")
|
||||
}
|
||||
implementation("com.google.apis:google-api-services-drive:v3-rev20230822-2.0.0") {
|
||||
exclude(group = "org.apache.httpcomponents")
|
||||
}
|
||||
//end google drive
|
||||
|
||||
implementation(project(":googleDrive"))
|
||||
|
||||
//BOM begin
|
||||
implementation(platform("androidx.compose:compose-bom:2025.07.00"))
|
||||
@@ -94,25 +95,23 @@ dependencies {
|
||||
implementation("androidx.compose.material3:material3")
|
||||
//BOM end
|
||||
|
||||
implementation("androidx.datastore:datastore:1.1.7")
|
||||
implementation("androidx.datastore:datastore-preferences:1.1.7")
|
||||
|
||||
implementation("androidx.activity:activity-compose:1.10.1")
|
||||
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.9.2")
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.9.2")
|
||||
implementation("androidx.core:core-ktx:1.16.0")
|
||||
implementation("androidx.navigation:navigation-compose:2.9.3")
|
||||
|
||||
|
||||
implementation("io.coil-kt.coil3:coil-compose:3.3.0")
|
||||
implementation("io.coil-kt.coil3:coil-network-okhttp:3.3.0")
|
||||
implementation("com.squareup.okhttp3:okhttp:5.1.0")
|
||||
|
||||
|
||||
implementation("androidx.core:core-ktx:1.16.0")
|
||||
|
||||
implementation("androidx.navigation:navigation-compose:2.9.3")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
|
||||
|
||||
|
||||
|
||||
implementation("androidx.datastore:datastore:1.1.7")
|
||||
implementation("androidx.datastore:datastore-preferences:1.1.7")
|
||||
|
||||
|
||||
implementation(platform("io.insert-koin:koin-bom:4.1.0"))
|
||||
@@ -137,13 +136,13 @@ dependencies {
|
||||
|
||||
implementation("net.java.dev.jna:jna:5.17.0@aar")
|
||||
|
||||
implementation("io.github.vinceglb:filekit-dialogs:0.10.0-beta04")
|
||||
implementation("io.github.vinceglb:filekit-dialogs-compose:0.10.0-beta04")
|
||||
implementation("io.github.vinceglb:filekit-dialogs:0.10.0")
|
||||
implementation("io.github.vinceglb:filekit-dialogs-compose:0.10.0")
|
||||
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
androidTestImplementation(platform("androidx.compose:compose-bom:2025.07.00"))
|
||||
androidTestImplementation("androidx.test.ext:junit:1.2.1")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.3.0")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0")
|
||||
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
|
||||
debugImplementation("androidx.compose.ui:ui-tooling")
|
||||
debugImplementation("androidx.compose.ui:ui-test-manifest")
|
||||
|
16
appCompose/proguard-rules.pro
vendored
16
appCompose/proguard-rules.pro
vendored
@@ -19,3 +19,19 @@
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
||||
# gRPC rules
|
||||
-keep class io.grpc.** {*;}
|
||||
-keep class com.google.protobuf.** {*;}
|
||||
-keepclassmembers class com.google.protobuf.** {
|
||||
public <methods>;
|
||||
}
|
||||
-dontwarn io.grpc.**
|
||||
-dontwarn com.google.protobuf.**
|
||||
-dontwarn javax.naming.**
|
||||
|
||||
# Firebase rules
|
||||
-keep class com.google.firebase.** { *; }
|
||||
-keep class com.google.android.gms.** { *; }
|
||||
-dontwarn com.google.firebase.**
|
||||
-dontwarn com.google.android.gms.**
|
||||
|
@@ -14,8 +14,6 @@
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT" />
|
||||
|
@@ -19,7 +19,7 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil3.compose.AsyncImage
|
||||
import kotlinx.coroutines.launch
|
||||
import mobi.librera.appcompose.components.GoogleSignInButton
|
||||
import mobi.librera.lib.gdrive.GoogleSignInButton
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
|
||||
|
@@ -3,6 +3,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("org.jetbrains.kotlin.plugin.compose")
|
||||
}
|
||||
|
||||
android {
|
||||
@@ -25,6 +26,30 @@ android {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose = true
|
||||
}
|
||||
|
||||
packaging {
|
||||
resources {
|
||||
excludes += setOf(
|
||||
"META-INF/DEPENDENCIES",
|
||||
"META-INF/NOTICE",
|
||||
"META-INF/LICENSE",
|
||||
"META-INF/LICENSE.txt",
|
||||
"META-INF/NOTICE.txt",
|
||||
"META-INF/INDEX.LIST",
|
||||
"META-INF/io.netty.versions.properties"
|
||||
)
|
||||
pickFirsts += setOf(
|
||||
"google/protobuf/*.proto",
|
||||
"META-INF/services/io.grpc.LoadBalancerProvider",
|
||||
"META-INF/services/io.grpc.ManagedChannelProvider",
|
||||
"META-INF/services/io.grpc.NameResolverProvider"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
@@ -34,6 +59,37 @@ kotlin {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation(platform("androidx.compose:compose-bom:2025.07.00"))
|
||||
implementation("androidx.compose.ui:ui")
|
||||
implementation("androidx.compose.ui:ui-graphics")
|
||||
implementation("androidx.compose.material:material-icons-core")
|
||||
implementation("androidx.compose.material:material-icons-extended")
|
||||
implementation("androidx.compose.ui:ui-tooling-preview")
|
||||
implementation("androidx.compose.material3:material3")
|
||||
implementation("androidx.activity:activity-compose:1.10.1")
|
||||
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.9.2")
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.9.2")
|
||||
implementation("androidx.core:core-ktx:1.16.0")
|
||||
implementation("androidx.fragment:fragment-ktx:1.8.8")
|
||||
implementation("androidx.activity:activity-ktx:1.10.1")
|
||||
implementation("io.coil-kt.coil3:coil-compose:3.3.0")
|
||||
implementation("io.coil-kt.coil3:coil-network-okhttp:3.3.0")
|
||||
implementation("com.squareup.okhttp3:okhttp:5.1.0")
|
||||
|
||||
api(platform("com.google.firebase:firebase-bom:34.0.0"))
|
||||
api("com.google.firebase:firebase-auth")
|
||||
api("com.google.firebase:firebase-firestore")
|
||||
api("com.google.firebase:firebase-storage")//optional to remove
|
||||
|
||||
|
||||
//gRPC dependencies for Firestore
|
||||
api("io.grpc:grpc-okhttp:1.74.0")
|
||||
api("io.grpc:grpc-android:1.74.0")
|
||||
api("io.grpc:grpc-protobuf-lite:1.74.0")
|
||||
api("io.grpc:grpc-stub:1.74.0")
|
||||
|
||||
|
||||
api("com.google.android.gms:play-services-auth:21.4.0")
|
||||
api("com.google.apis:google-api-services-drive:v3-rev20220815-2.0.0")
|
||||
api("com.google.api-client:google-api-client-android:2.8.0")
|
||||
@@ -44,4 +100,5 @@ dependencies {
|
||||
api("androidx.credentials:credentials:1.5.0")
|
||||
api("androidx.credentials:credentials-play-services-auth:1.5.0")
|
||||
api("com.google.android.libraries.identity.googleid:googleid:1.1.1")
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,101 @@
|
||||
package mobi.librera.lib.gdrive
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.core.net.toUri
|
||||
import com.google.firebase.Firebase
|
||||
import com.google.firebase.auth.auth
|
||||
import com.google.firebase.firestore.firestore
|
||||
import com.google.firebase.storage.storage
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import java.io.File
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
val KEY_USERS = "users"
|
||||
val KEY_BOOKS = "books"
|
||||
|
||||
data class BookState(
|
||||
val bookPath: String,
|
||||
val fileName: String,
|
||||
val progress: Float,
|
||||
)
|
||||
|
||||
object FirestoreBooksRepository {
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private val db = Firebase.firestore
|
||||
private val collection = db.collection("book_state")
|
||||
|
||||
fun listenToNotes(onChange: (List<BookState>) -> Unit) {
|
||||
Firebase.auth.currentUser?.let { user ->
|
||||
db.collection(KEY_USERS).document(user.uid).collection(KEY_BOOKS)
|
||||
.addSnapshotListener { snapshot, _ ->
|
||||
if (snapshot != null && !snapshot.isEmpty) {
|
||||
val books = snapshot.toObjects(BookState::class.java)
|
||||
onChange(books)
|
||||
} else {
|
||||
onChange(emptyList())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun uploadImageToFirebase(
|
||||
book: File, coverFile: File
|
||||
): String = suspendCancellableCoroutine { continuation ->
|
||||
val storageRef = Firebase.storage
|
||||
val userUID = Firebase.auth.currentUser?.uid
|
||||
|
||||
val imageRef = storageRef.getReference("users/$userUID/${book.name}")
|
||||
|
||||
println("uploadImageToFirebase ${imageRef.path} $imageRef")
|
||||
|
||||
|
||||
imageRef.putFile(coverFile.toUri()).addOnSuccessListener {
|
||||
imageRef.downloadUrl.addOnSuccessListener { downloadUrl ->
|
||||
continuation.resume(downloadUrl.toString(), onCancellation = { a, b, c -> {} })
|
||||
//imaUrl(downloadUrl.toString())
|
||||
println("uploadImageToFirebase onSuccess |$downloadUrl")
|
||||
}
|
||||
}.addOnFailureListener { e ->
|
||||
println("uploadImageToFirebase onError ${e.message}")
|
||||
continuation.resumeWithException(e)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun syncBook(book: BookState) = runBlocking {
|
||||
// if (book.bookPaths[App.DEVICE_ID].isNullOrEmpty()) {
|
||||
// println("Sync Book skip")
|
||||
// return@runBlocking
|
||||
// }
|
||||
|
||||
// launch {
|
||||
// if (book.imageUrl.isEmpty()) {
|
||||
// val bookPath = book.bookPaths[App.DEVICE_ID]
|
||||
// if (!bookPath.isNullOrEmpty()) {
|
||||
// val coverFile = File(bookPath)
|
||||
// println("uploadImageToFirebase coverFile ${coverFile.isFile}")
|
||||
// if (coverFile.isFile) {
|
||||
// println("uploadImageToFirebase 2")
|
||||
//
|
||||
// book.imageUrl = uploadImageToFirebase(coverFile)
|
||||
// println("Sync image url $book.imageUrl")
|
||||
//
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
println("Sync Book $book")
|
||||
Firebase.auth.currentUser?.let { user ->
|
||||
db.collection(KEY_USERS).document(user.uid).collection(KEY_BOOKS)
|
||||
.document(book.fileName).set(book).addOnSuccessListener { documentReference ->
|
||||
println("Sync success")
|
||||
}.addOnFailureListener { e ->
|
||||
println("Sync fail ${e.printStackTrace()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -1,4 +0,0 @@
|
||||
package mobi.librera.lib.gdrive
|
||||
|
||||
class GoogleDrive {
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package mobi.librera.appcompose.components
|
||||
package mobi.librera.lib.gdrive
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -14,10 +14,8 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import mobi.librera.appcompose.R
|
||||
import mobi.librera.appcompose.ui.theme.LibreraTheme
|
||||
import com.foobnix.googledrive.R
|
||||
|
||||
@Composable
|
||||
fun GoogleSignInButton(
|
||||
@@ -50,11 +48,4 @@ fun GoogleSignInButton(
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun Preview() {
|
||||
LibreraTheme {
|
||||
GoogleSignInButton("Hello", onClick = {})
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,22 @@
|
||||
package mobi.librera.lib.gdrive
|
||||
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
|
||||
object GoogleSignInComposeHelper {
|
||||
|
||||
@JvmStatic
|
||||
fun createSimpleGoogleSignInButton(
|
||||
composeView: ComposeView,
|
||||
buttonText: String,
|
||||
onSignInClick: Runnable
|
||||
) {
|
||||
composeView.setContent {
|
||||
GoogleSignInButton(
|
||||
text = buttonText,
|
||||
onClick = {
|
||||
onSignInClick.run()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,81 @@
|
||||
package mobi.librera.lib.gdrive
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import coil3.compose.AsyncImage
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
@Composable
|
||||
fun GoogleSignInScreen(
|
||||
clientId: String
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
|
||||
|
||||
val viewModel: GoogleSingInViewModel = viewModel()
|
||||
val singInState by viewModel.singInState.collectAsState()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.checkSingInState()
|
||||
}
|
||||
|
||||
when (val state = singInState) {
|
||||
is SingInState.NotSignIn -> {
|
||||
GoogleSignInButton(
|
||||
"Sing in with Google", onClick = {
|
||||
scope.launch {
|
||||
viewModel.signInWithGoogle(context, clientId)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
is SingInState.Success -> {
|
||||
Row(
|
||||
verticalAlignment = Alignment.Top,
|
||||
horizontalArrangement = Arrangement.Start
|
||||
) {
|
||||
AsyncImage(
|
||||
model = state.user.photoUrl,
|
||||
modifier = Modifier
|
||||
.size(40.dp)
|
||||
.clip(CircleShape),
|
||||
contentDescription = state.user.name
|
||||
)
|
||||
Column(Modifier.padding(start = 12.dp)) {
|
||||
Text(state.user.name)
|
||||
Text(state.user.email)
|
||||
}
|
||||
}
|
||||
GoogleSignInButton(
|
||||
"Sing out", onClick = {
|
||||
scope.launch {
|
||||
viewModel.signOut(context)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
is SingInState.Error -> {
|
||||
Text("Error: ${state.message}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,160 @@
|
||||
package mobi.librera.lib.gdrive
|
||||
|
||||
import android.content.Context
|
||||
import androidx.credentials.ClearCredentialStateRequest
|
||||
import androidx.credentials.CredentialManager
|
||||
import androidx.credentials.CustomCredential
|
||||
import androidx.credentials.GetCredentialRequest
|
||||
import androidx.credentials.GetCredentialResponse
|
||||
import androidx.credentials.exceptions.GetCredentialException
|
||||
import androidx.lifecycle.ViewModel
|
||||
import coil3.Uri
|
||||
import coil3.toCoilUri
|
||||
import com.google.android.libraries.identity.googleid.GetGoogleIdOption
|
||||
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
|
||||
import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException
|
||||
import com.google.firebase.Firebase
|
||||
import com.google.firebase.auth.FirebaseUser
|
||||
import com.google.firebase.auth.GoogleAuthProvider
|
||||
import com.google.firebase.auth.auth
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
// TODO: These imports should be injected or passed as parameters to avoid circular dependency
|
||||
// import mobi.librera.appcompose.App
|
||||
// import mobi.librera.appcompose.R
|
||||
// import mobi.librera.appcompose.room.BookRepository
|
||||
|
||||
data class User(val name: String, val email: String, val photoUrl: Uri?)
|
||||
|
||||
fun FirebaseUser?.toUser(): User = User(
|
||||
this?.displayName.orEmpty(), this?.email.orEmpty(), this?.photoUrl?.toCoilUri()
|
||||
)
|
||||
|
||||
sealed class SingInState {
|
||||
data object NotSignIn : SingInState()
|
||||
data class Success(val user: User) : SingInState()
|
||||
data class Error(val message: String) : SingInState()
|
||||
}
|
||||
|
||||
|
||||
// Simplified version without BookRepository dependency to avoid circular dependency
|
||||
class GoogleSingInViewModel() : ViewModel() {
|
||||
|
||||
private val _state = MutableStateFlow<SingInState>(SingInState.NotSignIn)
|
||||
val singInState: StateFlow<SingInState> = _state
|
||||
|
||||
|
||||
private fun observeFirestoreAndSyncToRoom() {
|
||||
// TODO: This functionality should be moved to the app module where BookRepository is available
|
||||
// For now, we'll just skip the sync functionality
|
||||
println("Sync: Skipped due to circular dependency")
|
||||
}
|
||||
|
||||
|
||||
init {
|
||||
// Skip initialization of sync for now
|
||||
// observeFirestoreAndSyncToRoom()
|
||||
}
|
||||
|
||||
|
||||
fun checkSingInState() {
|
||||
val auth = Firebase.auth
|
||||
|
||||
val currentUser = auth.currentUser
|
||||
if (currentUser == null) {
|
||||
_state.value = SingInState.NotSignIn
|
||||
} else {
|
||||
_state.value = SingInState.Success(currentUser.toUser())
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun signInWithGoogle(context: Context, clientId: String) {
|
||||
val credentialManager = CredentialManager.create(context)
|
||||
|
||||
val googleIdOption: GetGoogleIdOption =
|
||||
GetGoogleIdOption.Builder().setFilterByAuthorizedAccounts(false)
|
||||
.setServerClientId(clientId).build()
|
||||
|
||||
val request: GetCredentialRequest =
|
||||
GetCredentialRequest.Builder().addCredentialOption(googleIdOption).build()
|
||||
|
||||
coroutineScope {
|
||||
try {
|
||||
val result = credentialManager.getCredential(
|
||||
request = request,
|
||||
context = context,
|
||||
)
|
||||
|
||||
handleSignInWithGoogleOption(result)
|
||||
} catch (e: GetCredentialException) {
|
||||
e.printStackTrace()
|
||||
_state.value = SingInState.Error(e.message.orEmpty())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun handleSignInWithGoogleOption(result: GetCredentialResponse) {
|
||||
when (val credential = result.credential) {
|
||||
is CustomCredential -> {
|
||||
if (credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) {
|
||||
try {
|
||||
val googleIdTokenCredential =
|
||||
GoogleIdTokenCredential.createFrom(credential.data)
|
||||
|
||||
firebaseAuthWithGoogle(googleIdTokenCredential.idToken)
|
||||
|
||||
} catch (e: GoogleIdTokenParsingException) {
|
||||
e.printStackTrace()
|
||||
_state.value = SingInState.Error(e.message.orEmpty())
|
||||
}
|
||||
} else {
|
||||
_state.value = SingInState.Error("Unexpected type of credential")
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
_state.value = SingInState.Error("Unexpected Error")
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun firebaseAuthWithGoogle(idToken: String) {
|
||||
val credential = GoogleAuthProvider.getCredential(idToken, null)
|
||||
val auth = Firebase.auth
|
||||
auth.signInWithCredential(credential).addOnCompleteListener { task ->
|
||||
if (task.isSuccessful) {
|
||||
val user = auth.currentUser
|
||||
println("current user $user")
|
||||
_state.value = SingInState.Success(user.toUser())
|
||||
|
||||
observeFirestoreAndSyncToRoom()
|
||||
|
||||
} else {
|
||||
_state.value = SingInState.Error(task.exception?.message.orEmpty())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun signOut(context: Context) {
|
||||
val auth = Firebase.auth
|
||||
|
||||
val credentialManager = CredentialManager.create(context)
|
||||
|
||||
val clearRequest = ClearCredentialStateRequest()
|
||||
coroutineScope {
|
||||
credentialManager.clearCredentialState(clearRequest)
|
||||
}
|
||||
|
||||
auth.signOut()
|
||||
|
||||
_state.value = SingInState.NotSignIn
|
||||
}
|
||||
|
||||
|
||||
}
|
Reference in New Issue
Block a user