This commit is contained in:
Thomas Andres Gomez 2021-05-08 08:48:16 +02:00
parent 7898a51252
commit 8fbe3c0b7b
21 changed files with 359 additions and 35 deletions

2
.idea/misc.xml generated
View file

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

View file

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

View 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()
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View 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] }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,5 @@
package com.pixelized.biblib.utils.exception
import kotlin.reflect.KClass
class InjectionException(clazz: KClass<*>) : RuntimeException("InjectionException occur for class:${clazz}")

View file

@ -0,0 +1,3 @@
package com.pixelized.biblib.utils.exception
class NoBearerException : RuntimeException("Bearer token is null")

View file

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

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