tmp
This commit is contained in:
		
							parent
							
								
									7898a51252
								
							
						
					
					
						commit
						8fbe3c0b7b
					
				
					 21 changed files with 359 additions and 35 deletions
				
			
		
							
								
								
									
										2
									
								
								.idea/misc.xml
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								.idea/misc.xml
									
										
									
										generated
									
									
									
								
							| 
						 | 
					@ -88,6 +88,8 @@
 | 
				
			||||||
        <entry key="../../../../../layout/compose-model-1620403526292.xml" value="0.16300675675675674" />
 | 
					        <entry key="../../../../../layout/compose-model-1620403526292.xml" value="0.16300675675675674" />
 | 
				
			||||||
        <entry key="../../../../../layout/compose-model-1620403554223.xml" value="0.23514851485148514" />
 | 
					        <entry key="../../../../../layout/compose-model-1620403554223.xml" value="0.23514851485148514" />
 | 
				
			||||||
        <entry key="../../../../../layout/compose-model-1620403674440.xml" value="0.2962962962962963" />
 | 
					        <entry key="../../../../../layout/compose-model-1620403674440.xml" value="0.2962962962962963" />
 | 
				
			||||||
 | 
					        <entry key="../../../../../layout/compose-model-1620414619902.xml" value="0.28125" />
 | 
				
			||||||
 | 
					        <entry key="../../../../../layout/compose-model-1620421200338.xml" value="0.3" />
 | 
				
			||||||
        <entry key="app/src/main/res/drawable-v24/ic_launcher_foreground.xml" value="0.2898148148148148" />
 | 
					        <entry key="app/src/main/res/drawable-v24/ic_launcher_foreground.xml" value="0.2898148148148148" />
 | 
				
			||||||
        <entry key="app/src/main/res/drawable/ic_baseline_local_library_24.xml" value="0.25462962962962965" />
 | 
					        <entry key="app/src/main/res/drawable/ic_baseline_local_library_24.xml" value="0.25462962962962965" />
 | 
				
			||||||
        <entry key="app/src/main/res/drawable/ic_google.xml" value="0.2962962962962963" />
 | 
					        <entry key="app/src/main/res/drawable/ic_google.xml" value="0.2962962962962963" />
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,21 +3,24 @@
 | 
				
			||||||
    package="com.pixelized.biblib">
 | 
					    package="com.pixelized.biblib">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <uses-permission android:name="android.permission.INTERNET" />
 | 
					    <uses-permission android:name="android.permission.INTERNET" />
 | 
				
			||||||
 | 
					    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <application
 | 
					    <application
 | 
				
			||||||
 | 
					        android:name=".BibLibApplication"
 | 
				
			||||||
        android:allowBackup="true"
 | 
					        android:allowBackup="true"
 | 
				
			||||||
 | 
					        android:fullBackupOnly="true"
 | 
				
			||||||
        android:icon="@mipmap/ic_launcher"
 | 
					        android:icon="@mipmap/ic_launcher"
 | 
				
			||||||
        android:label="@string/app_name"
 | 
					        android:label="@string/app_name"
 | 
				
			||||||
        android:roundIcon="@mipmap/ic_launcher_round"
 | 
					        android:roundIcon="@mipmap/ic_launcher_round"
 | 
				
			||||||
        android:supportsRtl="true"
 | 
					        android:supportsRtl="true"
 | 
				
			||||||
        android:theme="@style/Theme.MaterialComponents.DayNight.NoActionBar">
 | 
					        android:theme="@style/Theme.MaterialComponents.DayNight.NoActionBar">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <activity
 | 
					        <activity
 | 
				
			||||||
            android:name=".ui.MainActivity"
 | 
					            android:name=".ui.MainActivity"
 | 
				
			||||||
            android:exported="true"
 | 
					            android:exported="true"
 | 
				
			||||||
            android:label="@string/app_name">
 | 
					            android:label="@string/app_name">
 | 
				
			||||||
            <intent-filter>
 | 
					            <intent-filter>
 | 
				
			||||||
                <action android:name="android.intent.action.MAIN" />
 | 
					                <action android:name="android.intent.action.MAIN" />
 | 
				
			||||||
 | 
					 | 
				
			||||||
                <category android:name="android.intent.category.LAUNCHER" />
 | 
					                <category android:name="android.intent.category.LAUNCHER" />
 | 
				
			||||||
            </intent-filter>
 | 
					            </intent-filter>
 | 
				
			||||||
        </activity>
 | 
					        </activity>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										22
									
								
								app/src/main/java/com/pixelized/biblib/BibLibApplication.kt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								app/src/main/java/com/pixelized/biblib/BibLibApplication.kt
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,22 @@
 | 
				
			||||||
 | 
					package com.pixelized.biblib
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.app.Application
 | 
				
			||||||
 | 
					import com.google.gson.Gson
 | 
				
			||||||
 | 
					import com.google.gson.GsonBuilder
 | 
				
			||||||
 | 
					import com.pixelized.biblib.injection.Bob
 | 
				
			||||||
 | 
					import com.pixelized.biblib.network.client.BibLibClient
 | 
				
			||||||
 | 
					import com.pixelized.biblib.network.client.IBibLibClient
 | 
				
			||||||
 | 
					import com.pixelized.biblib.utils.BitmapCache
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BibLibApplication : Application() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun onCreate() {
 | 
				
			||||||
 | 
					        super.onCreate()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Bob[BitmapCache::class] = BitmapCache(this)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Bob[Gson::class] = GsonBuilder().create()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Bob[IBibLibClient::class] = BibLibClient()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,10 @@
 | 
				
			||||||
 | 
					package com.pixelized.biblib.data.network.query
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.google.gson.annotations.SerializedName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					data class AuthLoginQuery(
 | 
				
			||||||
 | 
					    @SerializedName("username")
 | 
				
			||||||
 | 
					    val username: String?,
 | 
				
			||||||
 | 
					    @SerializedName("password")
 | 
				
			||||||
 | 
					    val password: String?,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,8 @@
 | 
				
			||||||
 | 
					package com.pixelized.biblib.data.network.response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.google.gson.annotations.SerializedName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					data class AuthLoginResponse(
 | 
				
			||||||
 | 
					    @SerializedName("id_token")
 | 
				
			||||||
 | 
					    val token: String? = null,
 | 
				
			||||||
 | 
					) : ErrorResponse()
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,25 @@
 | 
				
			||||||
 | 
					package com.pixelized.biblib.data.network.response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.google.gson.annotations.SerializedName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					open class ErrorResponse(
 | 
				
			||||||
 | 
					    @SerializedName("error")
 | 
				
			||||||
 | 
					    val error: Error? = null,
 | 
				
			||||||
 | 
					    @SerializedName("message")
 | 
				
			||||||
 | 
					    val message: String? = null,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    val isError: Boolean get() = error != null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class Error(
 | 
				
			||||||
 | 
					        @SerializedName("expose")
 | 
				
			||||||
 | 
					        val expose: Boolean? = null,
 | 
				
			||||||
 | 
					        @SerializedName("statusCode")
 | 
				
			||||||
 | 
					        val statusCode: Int? = null,
 | 
				
			||||||
 | 
					        @SerializedName("status")
 | 
				
			||||||
 | 
					        val status: Int? = null,
 | 
				
			||||||
 | 
					        @SerializedName("body")
 | 
				
			||||||
 | 
					        val body: String? = null,
 | 
				
			||||||
 | 
					        @SerializedName("type")
 | 
				
			||||||
 | 
					        val type: String? = null,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
package com.pixelized.biblib.data.ui
 | 
					package com.pixelized.biblib.data.ui
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import com.pixelized.biblib.utils.Constant.THUMBNAIL_URL
 | 
					import com.pixelized.biblib.network.client.IBibLibClient.Companion.THUMBNAIL_URL
 | 
				
			||||||
import java.net.URL
 | 
					import java.net.URL
 | 
				
			||||||
 | 
					
 | 
				
			||||||
data class BookThumbnailUio(
 | 
					data class BookThumbnailUio(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
package com.pixelized.biblib.data.ui
 | 
					package com.pixelized.biblib.data.ui
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import com.pixelized.biblib.utils.Constant
 | 
					import com.pixelized.biblib.network.client.IBibLibClient.Companion.COVER_URL
 | 
				
			||||||
import java.net.URL
 | 
					import java.net.URL
 | 
				
			||||||
 | 
					
 | 
				
			||||||
data class BookUio(
 | 
					data class BookUio(
 | 
				
			||||||
| 
						 | 
					@ -14,5 +14,5 @@ data class BookUio(
 | 
				
			||||||
    val series: String?,
 | 
					    val series: String?,
 | 
				
			||||||
    val description: String,
 | 
					    val description: String,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
    val cover: URL = URL("${Constant.COVER_URL}/$id.jpg")
 | 
					    val cover: URL = URL("${COVER_URL}/$id.jpg")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										21
									
								
								app/src/main/java/com/pixelized/biblib/injection/Bob.kt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								app/src/main/java/com/pixelized/biblib/injection/Bob.kt
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,21 @@
 | 
				
			||||||
 | 
					package com.pixelized.biblib.injection
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.pixelized.biblib.utils.exception.InjectionException
 | 
				
			||||||
 | 
					import kotlin.reflect.KClass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Suppress("UNCHECKED_CAST")
 | 
				
			||||||
 | 
					object Bob {
 | 
				
			||||||
 | 
					    private val components = hashMapOf<KClass<*>, Any>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operator fun <I : Any, O : I> set(clazz: KClass<I>, component: O) {
 | 
				
			||||||
 | 
					        components[clazz] = component
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operator fun <T> get(clazz: KClass<*>): T {
 | 
				
			||||||
 | 
					        return components[clazz] as? T ?: throw InjectionException(clazz)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					inline fun <reified T> get(): T = Bob[T::class]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					inline fun <reified T> inject(): Lazy<T> = lazy { Bob[T::class] }
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,23 @@
 | 
				
			||||||
 | 
					package com.pixelized.biblib.network.client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import okhttp3.Interceptor
 | 
				
			||||||
 | 
					import okhttp3.Response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BearerInterceptor : Interceptor {
 | 
				
			||||||
 | 
					    private val bearer get() = "$BEARER $token"
 | 
				
			||||||
 | 
					    var token: String? = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun intercept(chain: Interceptor.Chain): Response {
 | 
				
			||||||
 | 
					        var request = chain.request()
 | 
				
			||||||
 | 
					        if (request.header(NO_AUTHORIZATION) == null && token.isNullOrEmpty().not()) {
 | 
				
			||||||
 | 
					            request = request.newBuilder().addHeader(AUTHORIZATION, bearer).build()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return chain.proceed(request)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    companion object {
 | 
				
			||||||
 | 
					        private const val NO_AUTHORIZATION = "No-Authentication"
 | 
				
			||||||
 | 
					        private const val AUTHORIZATION = "Authorization"
 | 
				
			||||||
 | 
					        private const val BEARER = "Bearer"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,30 @@
 | 
				
			||||||
 | 
					package com.pixelized.biblib.network.client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.google.gson.Gson
 | 
				
			||||||
 | 
					import com.pixelized.biblib.injection.inject
 | 
				
			||||||
 | 
					import com.pixelized.biblib.network.client.IBibLibClient.Companion.BASE_URL
 | 
				
			||||||
 | 
					import okhttp3.OkHttpClient
 | 
				
			||||||
 | 
					import retrofit2.Retrofit
 | 
				
			||||||
 | 
					import retrofit2.converter.gson.GsonConverterFactory
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BibLibClient : IBibLibClient {
 | 
				
			||||||
 | 
					    private val gson by inject<Gson>()
 | 
				
			||||||
 | 
					    private val interceptor = BearerInterceptor()
 | 
				
			||||||
 | 
					    private val retrofit: Retrofit = Retrofit.Builder()
 | 
				
			||||||
 | 
					        .addConverterFactory(GsonConverterFactory.create(gson))
 | 
				
			||||||
 | 
					        .client(OkHttpClient.Builder().addInterceptor(interceptor).build())
 | 
				
			||||||
 | 
					        .baseUrl(BASE_URL)
 | 
				
			||||||
 | 
					        .build()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override val service: IBibLibWebServiceAPI = retrofit.create(IBibLibWebServiceAPI::class.java)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // endregion
 | 
				
			||||||
 | 
					    ///////////////////////////////////
 | 
				
			||||||
 | 
					    // region BibLib webservice Auth
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun updateBearerToken(token: String?) {
 | 
				
			||||||
 | 
					        interceptor.token = token
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // endregion
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,15 @@
 | 
				
			||||||
 | 
					package com.pixelized.biblib.network.client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IBibLibClient {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val service: IBibLibWebServiceAPI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun updateBearerToken(token: String?)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    companion object {
 | 
				
			||||||
 | 
					        const val BASE_URL = "https://bib.bibulle.fr"
 | 
				
			||||||
 | 
					        const val THUMBNAIL_URL = "$BASE_URL/api/book/thumbnail"
 | 
				
			||||||
 | 
					        const val COVER_URL = "$BASE_URL/api/book/cover"
 | 
				
			||||||
 | 
					        const val REGISTER_URL = "$BASE_URL/signup"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,43 @@
 | 
				
			||||||
 | 
					package com.pixelized.biblib.network.client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.pixelized.biblib.data.network.query.AuthLoginQuery
 | 
				
			||||||
 | 
					import com.pixelized.biblib.data.network.response.AuthLoginResponse
 | 
				
			||||||
 | 
					import retrofit2.http.Body
 | 
				
			||||||
 | 
					import retrofit2.http.GET
 | 
				
			||||||
 | 
					import retrofit2.http.POST
 | 
				
			||||||
 | 
					import retrofit2.http.Query
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IBibLibWebServiceAPI {
 | 
				
			||||||
 | 
					    @POST("/authent/login")
 | 
				
			||||||
 | 
					    suspend fun login(@Body query: AuthLoginQuery): AuthLoginResponse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @GET("/authent/google-id-token")
 | 
				
			||||||
 | 
					    suspend fun loginWithGoogle(@Query("id_token") token: String): AuthLoginResponse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//    @GET("/authent/user")
 | 
				
			||||||
 | 
					//    suspend fun user(): UserResponse
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//    @GET("/api/book/new")
 | 
				
			||||||
 | 
					//    suspend fun new(): BookListResponse
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//    @GET("/api/book")
 | 
				
			||||||
 | 
					//    suspend fun list(): BookListResponse
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//    @GET("/api/book/{id}")
 | 
				
			||||||
 | 
					//    suspend fun detail(@Path("id") bookId: Int): BookDetailResponse
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//    @GET("/api/book/{id}/send/kindle")
 | 
				
			||||||
 | 
					//    suspend fun send(@Path("id") bookId: Int, @Query("mail") mail: String): LinkedTreeMap<String, Any>
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//    @GET("/api/book/{id}/epub/url")
 | 
				
			||||||
 | 
					//    suspend fun epub(@Path("id") bookId: Int): TokenResponse
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//    @GET("/api/book/{id}/mobi/url")
 | 
				
			||||||
 | 
					//    suspend fun mobi(@Path("id") bookId: Int): TokenResponse
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//    @GET("/api/book/{id}/epub")
 | 
				
			||||||
 | 
					//    fun downloadEpub(@Path("id") bookId: Int, @Query("token") token: String): Call<ResponseBody>
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//    @GET("/api/book/{id}/mobi")
 | 
				
			||||||
 | 
					//    fun downloadMobi(@Path("id") bookId: Int, @Query("token") token: String): Call<ResponseBody>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -5,28 +5,37 @@ import androidx.activity.ComponentActivity
 | 
				
			||||||
import androidx.activity.compose.setContent
 | 
					import androidx.activity.compose.setContent
 | 
				
			||||||
import androidx.activity.viewModels
 | 
					import androidx.activity.viewModels
 | 
				
			||||||
import androidx.compose.animation.Crossfade
 | 
					import androidx.compose.animation.Crossfade
 | 
				
			||||||
import androidx.compose.animation.core.tween
 | 
					 | 
				
			||||||
import androidx.compose.runtime.Composable
 | 
					import androidx.compose.runtime.Composable
 | 
				
			||||||
import androidx.compose.runtime.getValue
 | 
					import androidx.compose.runtime.getValue
 | 
				
			||||||
import androidx.compose.runtime.livedata.observeAsState
 | 
					import androidx.compose.runtime.livedata.observeAsState
 | 
				
			||||||
import androidx.lifecycle.viewmodel.compose.viewModel
 | 
					import androidx.lifecycle.viewmodel.compose.viewModel
 | 
				
			||||||
 | 
					import com.google.android.gms.auth.api.signin.GoogleSignIn
 | 
				
			||||||
 | 
					import com.google.android.gms.auth.api.signin.GoogleSignInOptions
 | 
				
			||||||
 | 
					import com.pixelized.biblib.R
 | 
				
			||||||
import com.pixelized.biblib.ui.composable.screen.LoginScreenComposable
 | 
					import com.pixelized.biblib.ui.composable.screen.LoginScreenComposable
 | 
				
			||||||
import com.pixelized.biblib.ui.composable.screen.MainScreenComposable
 | 
					import com.pixelized.biblib.ui.composable.screen.MainScreenComposable
 | 
				
			||||||
import com.pixelized.biblib.ui.composable.screen.SplashScreenComposable
 | 
					import com.pixelized.biblib.ui.composable.screen.SplashScreenComposable
 | 
				
			||||||
import com.pixelized.biblib.ui.theme.BibLibTheme
 | 
					import com.pixelized.biblib.ui.theme.BibLibTheme
 | 
				
			||||||
import com.pixelized.biblib.ui.viewmodel.AuthenticationViewModel
 | 
					 | 
				
			||||||
import com.pixelized.biblib.ui.viewmodel.NavigationViewModel
 | 
					import com.pixelized.biblib.ui.viewmodel.NavigationViewModel
 | 
				
			||||||
import com.pixelized.biblib.ui.viewmodel.NavigationViewModel.Screen
 | 
					import com.pixelized.biblib.ui.viewmodel.NavigationViewModel.Screen
 | 
				
			||||||
import com.pixelized.biblib.utils.BitmapCache
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MainActivity : ComponentActivity() {
 | 
					class MainActivity : ComponentActivity() {
 | 
				
			||||||
    private val navigationViewModel: NavigationViewModel by viewModels()
 | 
					    private val navigationViewModel: NavigationViewModel by viewModels()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private val googleSignInOption by lazy {
 | 
				
			||||||
 | 
					        GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
 | 
				
			||||||
 | 
					            .requestIdToken(getString(R.string.biblib_server_id))
 | 
				
			||||||
 | 
					            .requestEmail()
 | 
				
			||||||
 | 
					            .build()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val googleSignIn by lazy {
 | 
				
			||||||
 | 
					        GoogleSignIn.getClient(this, googleSignInOption)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
					    override fun onCreate(savedInstanceState: Bundle?) {
 | 
				
			||||||
        super.onCreate(savedInstanceState)
 | 
					        super.onCreate(savedInstanceState)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        BitmapCache.init(this)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        setContent {
 | 
					        setContent {
 | 
				
			||||||
            BibLibTheme {
 | 
					            BibLibTheme {
 | 
				
			||||||
                ContentComposable()
 | 
					                ContentComposable()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,10 @@
 | 
				
			||||||
package com.pixelized.biblib.ui.composable.screen
 | 
					package com.pixelized.biblib.ui.composable.screen
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.util.Log
 | 
				
			||||||
 | 
					import android.widget.Toast
 | 
				
			||||||
 | 
					import androidx.activity.ComponentActivity
 | 
				
			||||||
 | 
					import androidx.activity.compose.rememberLauncherForActivityResult
 | 
				
			||||||
 | 
					import androidx.activity.result.contract.ActivityResultContracts
 | 
				
			||||||
import androidx.compose.foundation.Image
 | 
					import androidx.compose.foundation.Image
 | 
				
			||||||
import androidx.compose.foundation.clickable
 | 
					import androidx.compose.foundation.clickable
 | 
				
			||||||
import androidx.compose.foundation.layout.*
 | 
					import androidx.compose.foundation.layout.*
 | 
				
			||||||
| 
						 | 
					@ -19,6 +24,7 @@ import androidx.compose.ui.Alignment
 | 
				
			||||||
import androidx.compose.ui.Modifier
 | 
					import androidx.compose.ui.Modifier
 | 
				
			||||||
import androidx.compose.ui.focus.FocusRequester
 | 
					import androidx.compose.ui.focus.FocusRequester
 | 
				
			||||||
import androidx.compose.ui.focus.focusRequester
 | 
					import androidx.compose.ui.focus.focusRequester
 | 
				
			||||||
 | 
					import androidx.compose.ui.platform.LocalContext
 | 
				
			||||||
import androidx.compose.ui.platform.LocalFocusManager
 | 
					import androidx.compose.ui.platform.LocalFocusManager
 | 
				
			||||||
import androidx.compose.ui.res.painterResource
 | 
					import androidx.compose.ui.res.painterResource
 | 
				
			||||||
import androidx.compose.ui.res.stringResource
 | 
					import androidx.compose.ui.res.stringResource
 | 
				
			||||||
| 
						 | 
					@ -28,7 +34,11 @@ import androidx.compose.ui.text.input.VisualTransformation
 | 
				
			||||||
import androidx.compose.ui.tooling.preview.Preview
 | 
					import androidx.compose.ui.tooling.preview.Preview
 | 
				
			||||||
import androidx.compose.ui.unit.dp
 | 
					import androidx.compose.ui.unit.dp
 | 
				
			||||||
import androidx.lifecycle.viewmodel.compose.viewModel
 | 
					import androidx.lifecycle.viewmodel.compose.viewModel
 | 
				
			||||||
 | 
					import com.google.android.gms.auth.api.signin.GoogleSignIn
 | 
				
			||||||
 | 
					import com.google.android.gms.auth.api.signin.GoogleSignInAccount
 | 
				
			||||||
 | 
					import com.google.android.gms.common.api.ApiException
 | 
				
			||||||
import com.pixelized.biblib.R
 | 
					import com.pixelized.biblib.R
 | 
				
			||||||
 | 
					import com.pixelized.biblib.ui.MainActivity
 | 
				
			||||||
import com.pixelized.biblib.ui.theme.BibLibTheme
 | 
					import com.pixelized.biblib.ui.theme.BibLibTheme
 | 
				
			||||||
import com.pixelized.biblib.ui.viewmodel.AuthenticationViewModel
 | 
					import com.pixelized.biblib.ui.viewmodel.AuthenticationViewModel
 | 
				
			||||||
import com.pixelized.biblib.ui.viewmodel.NavigationViewModel
 | 
					import com.pixelized.biblib.ui.viewmodel.NavigationViewModel
 | 
				
			||||||
| 
						 | 
					@ -43,10 +53,35 @@ fun LoginScreenComposablePreview() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Composable
 | 
					@Composable
 | 
				
			||||||
fun LoginScreenComposable(
 | 
					fun LoginScreenComposable(
 | 
				
			||||||
 | 
					    navigationViewModel: NavigationViewModel = viewModel(),
 | 
				
			||||||
 | 
					    authenticationViewModel: AuthenticationViewModel = viewModel(),
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
    val navigationViewModel = viewModel<NavigationViewModel>()
 | 
					    // TODO : c'est de la merde ça
 | 
				
			||||||
    val authenticationViewModel = viewModel<AuthenticationViewModel>()
 | 
					    val activity = LocalContext.current as MainActivity
 | 
				
			||||||
 | 
					    val result = remember { mutableStateOf<String?>(null) }
 | 
				
			||||||
 | 
					    val launcher =
 | 
				
			||||||
 | 
					        rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                val task = GoogleSignIn.getSignedInAccountFromIntent(it.data)
 | 
				
			||||||
 | 
					                val account: GoogleSignInAccount? = task.getResult(ApiException::class.java)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                val idToken = account?.idToken
 | 
				
			||||||
 | 
					//                if (idToken != null) {
 | 
				
			||||||
 | 
					//                    viewModel.loginWithGoogle(idToken).observeLogin()
 | 
				
			||||||
 | 
					//                } else {
 | 
				
			||||||
 | 
					//                    Toast.makeText(requireActivity(), "GoogleSignIn missing Token", Toast.LENGTH_SHORT).show()
 | 
				
			||||||
 | 
					//                }
 | 
				
			||||||
 | 
					                Log.e("AuthLoginFragment", "idToken: $idToken")
 | 
				
			||||||
 | 
					            } catch (exception: Exception) {
 | 
				
			||||||
 | 
					//                Toast.makeText(requireActivity(), "GoogleSignIn exception: ${exception.message}", Toast.LENGTH_SHORT).show()
 | 
				
			||||||
 | 
					                Log.e("AuthLoginFragment", exception.message, exception)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					//            // Here we just update the state, but you could imagine
 | 
				
			||||||
 | 
					//            // pre-processing the result, or updating a MutableSharedFlow that
 | 
				
			||||||
 | 
					//            // your composable collects
 | 
				
			||||||
 | 
					//            result.value = it
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val typography = MaterialTheme.typography
 | 
					    val typography = MaterialTheme.typography
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Column(
 | 
					    Column(
 | 
				
			||||||
| 
						 | 
					@ -102,14 +137,12 @@ fun LoginScreenComposable(
 | 
				
			||||||
                modifier = Modifier.padding(end = 8.dp),
 | 
					                modifier = Modifier.padding(end = 8.dp),
 | 
				
			||||||
                colors = outlinedButtonColors(),
 | 
					                colors = outlinedButtonColors(),
 | 
				
			||||||
                onClick = {
 | 
					                onClick = {
 | 
				
			||||||
                    // TODO:
 | 
					                    authenticationViewModel.register()
 | 
				
			||||||
                    navigationViewModel.navigateTo(NavigationViewModel.Screen.MainScreen)
 | 
					 | 
				
			||||||
                }) {
 | 
					                }) {
 | 
				
			||||||
                Text(text = stringResource(id = R.string.action_register))
 | 
					                Text(text = stringResource(id = R.string.action_register))
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Button(onClick = {
 | 
					            Button(onClick = {
 | 
				
			||||||
                // TODO:
 | 
					                authenticationViewModel.login()
 | 
				
			||||||
                navigationViewModel.navigateTo(NavigationViewModel.Screen.MainScreen)
 | 
					 | 
				
			||||||
            }) {
 | 
					            }) {
 | 
				
			||||||
                Text(text = stringResource(id = R.string.action_login))
 | 
					                Text(text = stringResource(id = R.string.action_login))
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					@ -121,8 +154,7 @@ fun LoginScreenComposable(
 | 
				
			||||||
            modifier = Modifier.fillMaxWidth(),
 | 
					            modifier = Modifier.fillMaxWidth(),
 | 
				
			||||||
            colors = outlinedButtonColors(),
 | 
					            colors = outlinedButtonColors(),
 | 
				
			||||||
            onClick = {
 | 
					            onClick = {
 | 
				
			||||||
                // TODO:
 | 
					                launcher.launch(activity.googleSignIn.signInIntent)
 | 
				
			||||||
                navigationViewModel.navigateTo(NavigationViewModel.Screen.MainScreen)
 | 
					 | 
				
			||||||
            }) {
 | 
					            }) {
 | 
				
			||||||
            Image(
 | 
					            Image(
 | 
				
			||||||
                modifier = Modifier.padding(end = 8.dp),
 | 
					                modifier = Modifier.padding(end = 8.dp),
 | 
				
			||||||
| 
						 | 
					@ -189,7 +221,7 @@ private fun PasswordField(
 | 
				
			||||||
private fun CredentialRemember(viewModel: AuthenticationViewModel, modifier: Modifier = Modifier) {
 | 
					private fun CredentialRemember(viewModel: AuthenticationViewModel, modifier: Modifier = Modifier) {
 | 
				
			||||||
    val credential = viewModel.rememberCredential.observeAsState()
 | 
					    val credential = viewModel.rememberCredential.observeAsState()
 | 
				
			||||||
    Row(modifier = modifier.clickable {
 | 
					    Row(modifier = modifier.clickable {
 | 
				
			||||||
        viewModel.updateRememberCredential(credential = credential.value?.not() ?: false)
 | 
					        viewModel.updateRememberCredential(rememberCredential = credential.value?.not() ?: false)
 | 
				
			||||||
    }) {
 | 
					    }) {
 | 
				
			||||||
        Checkbox(
 | 
					        Checkbox(
 | 
				
			||||||
            modifier = Modifier.align(Alignment.CenterVertically),
 | 
					            modifier = Modifier.align(Alignment.CenterVertically),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,10 +1,24 @@
 | 
				
			||||||
package com.pixelized.biblib.ui.viewmodel
 | 
					package com.pixelized.biblib.ui.viewmodel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.app.Application
 | 
				
			||||||
 | 
					import android.content.Context
 | 
				
			||||||
 | 
					import android.content.SharedPreferences
 | 
				
			||||||
 | 
					import android.util.Log
 | 
				
			||||||
 | 
					import androidx.core.content.edit
 | 
				
			||||||
 | 
					import androidx.lifecycle.AndroidViewModel
 | 
				
			||||||
import androidx.lifecycle.LiveData
 | 
					import androidx.lifecycle.LiveData
 | 
				
			||||||
import androidx.lifecycle.MutableLiveData
 | 
					import androidx.lifecycle.MutableLiveData
 | 
				
			||||||
import androidx.lifecycle.ViewModel
 | 
					import androidx.lifecycle.viewModelScope
 | 
				
			||||||
 | 
					import androidx.lifecycle.viewmodel.compose.viewModel
 | 
				
			||||||
 | 
					import com.pixelized.biblib.data.network.query.AuthLoginQuery
 | 
				
			||||||
 | 
					import com.pixelized.biblib.injection.inject
 | 
				
			||||||
 | 
					import com.pixelized.biblib.network.client.IBibLibClient
 | 
				
			||||||
 | 
					import kotlinx.coroutines.Dispatchers
 | 
				
			||||||
 | 
					import kotlinx.coroutines.launch
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AuthenticationViewModel: ViewModel() {
 | 
					class AuthenticationViewModel(application: Application) : AndroidViewModel(application) {
 | 
				
			||||||
 | 
					    private val preferences = application.getSharedPreferences(SHARED_PREF, Context.MODE_PRIVATE)
 | 
				
			||||||
 | 
					    private val client: IBibLibClient by inject()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private val _login = MutableLiveData<String>()
 | 
					    private val _login = MutableLiveData<String>()
 | 
				
			||||||
    val login: LiveData<String> get() = _login
 | 
					    val login: LiveData<String> get() = _login
 | 
				
			||||||
| 
						 | 
					@ -12,9 +26,17 @@ class AuthenticationViewModel: ViewModel() {
 | 
				
			||||||
    private val _password = MutableLiveData<String>()
 | 
					    private val _password = MutableLiveData<String>()
 | 
				
			||||||
    val password: LiveData<String> get() = _password
 | 
					    val password: LiveData<String> get() = _password
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private val _rememberCredential = MutableLiveData<Boolean>(false)
 | 
					    private val _rememberCredential = MutableLiveData<Boolean>()
 | 
				
			||||||
    val rememberCredential: LiveData<Boolean> get() = _rememberCredential
 | 
					    val rememberCredential: LiveData<Boolean> get() = _rememberCredential
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    init {
 | 
				
			||||||
 | 
					        viewModelScope.launch(Dispatchers.Main) {
 | 
				
			||||||
 | 
					            _login.value = preferences.login
 | 
				
			||||||
 | 
					            _password.value = preferences.password
 | 
				
			||||||
 | 
					            _rememberCredential.value = preferences.rememberCredential
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun updateLogin(login: String) {
 | 
					    fun updateLogin(login: String) {
 | 
				
			||||||
        _login.postValue(login)
 | 
					        _login.postValue(login)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -23,7 +45,54 @@ class AuthenticationViewModel: ViewModel() {
 | 
				
			||||||
        _password.postValue(password)
 | 
					        _password.postValue(password)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun updateRememberCredential(credential: Boolean) {
 | 
					    fun updateRememberCredential(rememberCredential: Boolean) {
 | 
				
			||||||
        _rememberCredential.postValue(credential)
 | 
					        _rememberCredential.postValue(rememberCredential)
 | 
				
			||||||
 | 
					        viewModelScope.launch {
 | 
				
			||||||
 | 
					            preferences.rememberCredential = rememberCredential
 | 
				
			||||||
 | 
					            if (rememberCredential.not()) {
 | 
				
			||||||
 | 
					                preferences.login = null
 | 
				
			||||||
 | 
					                preferences.password = null
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun register() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun login() {
 | 
				
			||||||
 | 
					        viewModelScope.launch(Dispatchers.IO) {
 | 
				
			||||||
 | 
					            if (rememberCredential.value == true) {
 | 
				
			||||||
 | 
					                preferences.login = login.value
 | 
				
			||||||
 | 
					                preferences.password = password.value
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            // TODO : validation !
 | 
				
			||||||
 | 
					            val query = AuthLoginQuery(
 | 
				
			||||||
 | 
					                username = login.value,
 | 
				
			||||||
 | 
					                password = password.value
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            // TODO : Repository (token management & co)
 | 
				
			||||||
 | 
					            val response = client.service.login(query)
 | 
				
			||||||
 | 
					            Log.e("pouet", response.toString())
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private var SharedPreferences.login: String?
 | 
				
			||||||
 | 
					        get() = getString(REMEMBER_USER, null)
 | 
				
			||||||
 | 
					        set(value) = edit { putString(REMEMBER_USER, value) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private var SharedPreferences.password: String?
 | 
				
			||||||
 | 
					        get() = getString(REMEMBER_PASSWORD, null)
 | 
				
			||||||
 | 
					        set(value) = edit { putString(REMEMBER_PASSWORD, value) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private var SharedPreferences.rememberCredential: Boolean
 | 
				
			||||||
 | 
					        get() = getBoolean(REMEMBER_CREDENTIAL, false)
 | 
				
			||||||
 | 
					        set(value) = edit { putBoolean(REMEMBER_CREDENTIAL, value) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    companion object {
 | 
				
			||||||
 | 
					        const val SHARED_PREF = "BIB_LIB_SHARED_PREF"
 | 
				
			||||||
 | 
					        const val REMEMBER_CREDENTIAL = "REMEMBER_CREDENTIAL"
 | 
				
			||||||
 | 
					        const val REMEMBER_USER = "REMEMBER_USER"
 | 
				
			||||||
 | 
					        const val REMEMBER_PASSWORD = "REMEMBER_PASSWORD"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
package com.pixelized.biblib.utils
 | 
					package com.pixelized.biblib.utils
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import android.content.Context
 | 
					import android.app.Application
 | 
				
			||||||
import android.graphics.Bitmap
 | 
					import android.graphics.Bitmap
 | 
				
			||||||
import android.graphics.BitmapFactory
 | 
					import android.graphics.BitmapFactory
 | 
				
			||||||
import android.util.Log
 | 
					import android.util.Log
 | 
				
			||||||
| 
						 | 
					@ -12,12 +12,11 @@ import java.io.File
 | 
				
			||||||
import java.io.IOException
 | 
					import java.io.IOException
 | 
				
			||||||
import java.net.URL
 | 
					import java.net.URL
 | 
				
			||||||
 | 
					
 | 
				
			||||||
object BitmapCache {
 | 
					class BitmapCache(application: Application) {
 | 
				
			||||||
    private val scope = CoroutineScope(Dispatchers.IO)
 | 
					 | 
				
			||||||
    private var cache: File? = null
 | 
					    private var cache: File? = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun init(context: Context) {
 | 
					    init {
 | 
				
			||||||
        cache = context.cacheDir
 | 
					        cache = application.cacheDir
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun writeToDisk(url: URL, bitmap: Bitmap) {
 | 
					    fun writeToDisk(url: URL, bitmap: Bitmap) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,5 @@
 | 
				
			||||||
 | 
					package com.pixelized.biblib.utils.exception
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import kotlin.reflect.KClass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class InjectionException(clazz: KClass<*>) : RuntimeException("InjectionException occur for class:${clazz}")
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					package com.pixelized.biblib.utils.exception
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class NoBearerException : RuntimeException("Bearer token is null")
 | 
				
			||||||
| 
						 | 
					@ -5,18 +5,19 @@ import androidx.compose.runtime.mutableStateOf
 | 
				
			||||||
import androidx.compose.ui.graphics.asImageBitmap
 | 
					import androidx.compose.ui.graphics.asImageBitmap
 | 
				
			||||||
import androidx.compose.ui.graphics.painter.BitmapPainter
 | 
					import androidx.compose.ui.graphics.painter.BitmapPainter
 | 
				
			||||||
import androidx.compose.ui.graphics.painter.Painter
 | 
					import androidx.compose.ui.graphics.painter.Painter
 | 
				
			||||||
 | 
					import com.pixelized.biblib.injection.get
 | 
				
			||||||
import com.pixelized.biblib.utils.BitmapCache
 | 
					import com.pixelized.biblib.utils.BitmapCache
 | 
				
			||||||
import java.net.URL
 | 
					import java.net.URL
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
fun URL.toImage(placeHolder: Painter): State<Painter> {
 | 
					fun URL.toImage(placeHolder: Painter): State<Painter> {
 | 
				
			||||||
    val cached = BitmapCache.readFromDisk(this)?.let { BitmapPainter(it.asImageBitmap()) }
 | 
					    val cache: BitmapCache = get()
 | 
				
			||||||
    val state = mutableStateOf(cached ?: placeHolder)
 | 
					    val resource = cache.readFromDisk(this)?.let { BitmapPainter(it.asImageBitmap()) }
 | 
				
			||||||
 | 
					    val state = mutableStateOf(resource ?: placeHolder)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (cached == null) {
 | 
					    if (resource == null) {
 | 
				
			||||||
        BitmapCache.download(url = this) { downloaded ->
 | 
					        cache.download(url = this) { downloaded ->
 | 
				
			||||||
            if (downloaded != null) {
 | 
					            if (downloaded != null) {
 | 
				
			||||||
                BitmapCache.writeToDisk(this, downloaded)
 | 
					                cache.writeToDisk(this, downloaded)
 | 
				
			||||||
                state.value = BitmapPainter(downloaded.asImageBitmap())
 | 
					                state.value = BitmapPainter(downloaded.asImageBitmap())
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										4
									
								
								app/src/main/res/values/biblib_server.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								app/src/main/res/values/biblib_server.xml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,4 @@
 | 
				
			||||||
 | 
					<?xml version="1.0" encoding="utf-8"?>
 | 
				
			||||||
 | 
					<resources>
 | 
				
			||||||
 | 
					    <string name="biblib_server_id" translatable="false">725701605591-rr2dqeabon4kjpfevoruru65eo3rukmv.apps.googleusercontent.com</string>
 | 
				
			||||||
 | 
					</resources>
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue