Add APICache + splashscreen init.

This commit is contained in:
Thomas Andres Gomez 2021-05-09 14:45:37 +02:00
parent e9cb8cd0ac
commit 2ad8d5953a
16 changed files with 309 additions and 15 deletions

View file

@ -1,29 +1,38 @@
package com.pixelized.biblib
import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.pixelized.biblib.utils.injection.Bob
import com.pixelized.biblib.network.client.BibLibClient
import com.pixelized.biblib.network.client.IBibLibClient
import com.pixelized.biblib.repository.apiCache.APICacheRepository
import com.pixelized.biblib.repository.apiCache.IAPICacheRepository
import com.pixelized.biblib.repository.credential.CredentialRepository
import com.pixelized.biblib.repository.credential.ICredentialRepository
import com.pixelized.biblib.repository.googlesignin.GoogleSingInRepository
import com.pixelized.biblib.repository.googlesignin.IGoogleSingInRepository
import com.pixelized.biblib.repository.googleSignIn.GoogleSingInRepository
import com.pixelized.biblib.repository.googleSignIn.IGoogleSingInRepository
import com.pixelized.biblib.utils.BitmapCache
import com.pixelized.biblib.utils.injection.Bob
class BibLibApplication : Application() {
override fun onCreate() {
super.onCreate()
Bob[BitmapCache::class] = BitmapCache(this)
Bob[SharedPreferences::class] = getSharedPreferences(SHARED_PREF, Context.MODE_PRIVATE)
Bob[Gson::class] = GsonBuilder().create()
Bob[BitmapCache::class] = BitmapCache(this)
Bob[IBibLibClient::class] = BibLibClient()
Bob[IGoogleSingInRepository::class] = GoogleSingInRepository(this)
Bob[ICredentialRepository::class] = CredentialRepository(this)
Bob[ICredentialRepository::class] = CredentialRepository()
Bob[IAPICacheRepository::class] = APICacheRepository()
}
companion object {
const val SHARED_PREF = "BIB_LIB_SHARED_PREF"
}
}

View file

@ -0,0 +1,7 @@
package com.pixelized.biblib.model
data class Author(
val id: String,
val name: String,
val sort: String,
)

View file

@ -0,0 +1,31 @@
package com.pixelized.biblib.model
import java.util.*
data class Book(
val id: Int,
val title: String,
val sort: String,
val author: List<Author>,
val haveCover: Boolean,
val releaseDate: Date,
val language: Language? = null,
val rating: Int? = null,
val genre: List<Genre>? = null,
// details
val series: Series? = null,
val synopsis: String? = null,
// source
val isNew: Boolean = false,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Book) return false
if (id != other.id) return false
return true
}
override fun hashCode(): Int {
return id
}
}

View file

@ -0,0 +1,6 @@
package com.pixelized.biblib.model
data class Genre(
val id: String,
val name: String,
)

View file

@ -0,0 +1,6 @@
package com.pixelized.biblib.model
data class Language (
val id : String,
val code: String,
)

View file

@ -0,0 +1,8 @@
package com.pixelized.biblib.model
data class Series(
val id: String?,
val name: String,
val sort: String,
val index: Int?,
)

View file

@ -0,0 +1,157 @@
package com.pixelized.biblib.network.factory
import com.pixelized.biblib.model.*
import com.pixelized.biblib.network.data.response.BookDetailResponse
import com.pixelized.biblib.network.data.response.BookListResponse
import com.pixelized.biblib.utils.exception.MandatoryFieldMissingException
import com.pixelized.biblib.utils.extention.toBoolean
import java.text.SimpleDateFormat
import java.util.*
class BookFactory {
private val parser get() = SimpleDateFormat(FORMAT, Locale.getDefault())
fun fromListResponseToBook(response: BookListResponse.Book, isNew: Boolean = false): Book {
fun error(name: String) =
MandatoryFieldMissingException("#fromListResponseToBook()", name, response)
val id: Int? = response.book_id
val title: String? = response.book_title
val sort: String? = response.book_sort
val authorId: List<String>? = response.author_id
val authorName: List<String>? = response.author_name
val authorSort: List<String>? = response.author_sort
val authorIdSize = authorId?.size ?: 0
val authorNameSize = authorName?.size ?: 0
val authorSortSize = authorSort?.size ?: 0
val author: List<Author>? =
if (authorId != null && authorName != null && authorSort != null) {
if (authorIdSize == authorNameSize && authorNameSize == authorSortSize) {
(authorId.indices).map { index ->
Author(
authorId[index],
authorName[index],
authorSort[index]
)
}
} else if (authorIdSize == 1) {
listOf(
Author(
authorId.first(),
authorName.joinToString { it },
authorSort.joinToString { it })
)
} else {
null
}
} else {
null
}
val cover: Boolean? = response.book_has_cover?.toBoolean()
val releaseDate = response.book_date?.let { parser.parse(it) }
val languageId = response.lang_id
val languageCode = response.lang_code
val language = if (languageId != null && languageCode != null) {
Language(languageId, languageCode)
} else {
null
}
val rating = response.rating?.toIntOrNull()
val seriesName = response.series_name
val seriesSort = response.series_sort
val seriesIndex: Int? = response.book_series_index?.toInt()
val series: Series? = if (seriesName != null && seriesSort != null) {
Series(id = null, name = seriesName, sort = seriesSort, index = seriesIndex)
} else {
null
}
return Book(
id = id ?: throw error("id"),
title = title ?: throw error("title"),
sort = sort ?: throw error("sort"),
author = author ?: throw error("author"),
haveCover = cover ?: throw error("haveCover"),
releaseDate = releaseDate ?: throw error("releaseDate"),
series = series,
language = language,
rating = rating,
isNew = isNew
)
}
fun fromDetailResponseToBook(response: BookDetailResponse, isNew: Boolean = false): Book {
fun error(name: String) =
MandatoryFieldMissingException("#fromDetailResponseToBook()", name, response)
val id: Int? = response.book_id
val title: String? = response.book_title
val sort: String? = response.book_sort
val authorId: List<String>? = response.author_id
val authorName: List<String>? = response.author_name
val authorSort: List<String>? = response.author_sort
val authorIdSize = authorId?.size
val authorNameSize = authorName?.size
val authorSortSize = authorSort?.size
val author: List<Author>? =
if (authorId != null && authorName != null && authorSort != null && authorIdSize == authorNameSize && authorNameSize == authorSortSize) {
authorId.indices.map { index ->
Author(
authorId[index],
authorName[index],
authorSort[index]
)
}
} else {
null
}
val cover: Boolean? = response.book_has_cover?.toBoolean()
val releaseDate = response.book_date?.let { parser.parse(it) }
val languageId = response.lang_id
val languageCode = response.lang_code
val language = if (languageId != null && languageCode != null) {
Language(languageId, languageCode)
} else {
null
}
val rating = response.rating?.toIntOrNull()
val seriesId = response.series_id
val seriesName = response.series_name
val seriesSort = response.series_sort
val seriesIndex: Int? = response.book_series_index?.toInt()
val series: Series? = if (seriesId != null && seriesName != null && seriesSort != null) {
Series(id = seriesId, name = seriesName, sort = seriesSort, seriesIndex)
} else {
null
}
val tagId: List<String>? = response.tag_id
val tagName: List<String>? = response.tag_name
val tagIdSize: Int? = response.tag_id?.size
val tagNameSize: Int? = response.tag_name?.size
val tag: List<Genre>? = if (tagId != null && tagName != null && tagIdSize == tagNameSize) {
tagId.indices.map { Genre(tagId[it], tagName[it]) }
} else {
null
}
val synopsis: String? = response.comment
return Book(
id = id ?: throw error("id"),
title = title ?: throw error("title"),
sort = sort ?: throw error("sort"),
author = author ?: throw error("author"),
haveCover = cover ?: throw error("cover"),
releaseDate = releaseDate ?: throw error("releaseDate"),
language = language ?: throw error("language"),
rating = rating,
series = series,
genre = tag,
synopsis = synopsis,
isNew = isNew
)
}
companion object {
private const val FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
}
}

View file

@ -0,0 +1,33 @@
package com.pixelized.biblib.repository.apiCache
import android.content.SharedPreferences
import androidx.core.content.edit
import com.google.gson.Gson
import com.pixelized.biblib.network.data.response.BookListResponse
import com.pixelized.biblib.utils.injection.inject
class APICacheRepository : IAPICacheRepository {
private val gson: Gson by inject()
private val preferences: SharedPreferences by inject()
override var new: BookListResponse?
get() = preferences.new?.let { gson.fromJson(it, BookListResponse::class.java) }
set(value) = gson.toJson(value).let { preferences.new = it }
override var list: BookListResponse?
get() = preferences.list?.let { gson.fromJson(it, BookListResponse::class.java) }
set(value) = gson.toJson(value).let { preferences.list = it }
private var SharedPreferences.new: String?
get() = getString(NEW, null)
set(value) = edit { putString(NEW, value) }
private var SharedPreferences.list: String?
get() = getString(LIST, null)
set(value) = edit { putString(LIST, value) }
companion object {
const val NEW = "NEW"
const val LIST = "LIST"
}
}

View file

@ -0,0 +1,8 @@
package com.pixelized.biblib.repository.apiCache
import com.pixelized.biblib.network.data.response.BookListResponse
interface IAPICacheRepository {
var new: BookListResponse?
var list: BookListResponse?
}

View file

@ -1,12 +1,12 @@
package com.pixelized.biblib.repository.credential
import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.edit
import com.pixelized.biblib.utils.injection.inject
class CredentialRepository(application: Application) : ICredentialRepository {
private val preferences = application.getSharedPreferences(SHARED_PREF, Context.MODE_PRIVATE)
class CredentialRepository : ICredentialRepository {
private val preferences: SharedPreferences by inject()
override var login
get() = preferences.login
@ -41,7 +41,6 @@ class CredentialRepository(application: Application) : ICredentialRepository {
set(value) = edit { putString(BEARER_TOKEN, value) }
companion object {
private const val SHARED_PREF = "BIB_LIB_SHARED_PREF"
private const val REMEMBER_CREDENTIAL = "REMEMBER_CREDENTIAL"
private const val REMEMBER_USER = "REMEMBER_USER"
private const val REMEMBER_PASSWORD = "REMEMBER_PASSWORD"

View file

@ -1,4 +1,4 @@
package com.pixelized.biblib.repository.googlesignin
package com.pixelized.biblib.repository.googleSignIn
import android.app.Application
import com.google.android.gms.auth.api.signin.GoogleSignIn

View file

@ -1,4 +1,4 @@
package com.pixelized.biblib.repository.googlesignin
package com.pixelized.biblib.repository.googleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInClient
import com.google.android.gms.auth.api.signin.GoogleSignInOptions

View file

@ -15,7 +15,7 @@ import com.google.android.gms.common.api.ApiException
import com.pixelized.biblib.network.data.query.AuthLoginQuery
import com.pixelized.biblib.network.client.IBibLibClient
import com.pixelized.biblib.repository.credential.ICredentialRepository
import com.pixelized.biblib.repository.googlesignin.IGoogleSingInRepository
import com.pixelized.biblib.repository.googleSignIn.IGoogleSingInRepository
import com.pixelized.biblib.ui.viewmodel.authentication.IAuthentication.State
import com.pixelized.biblib.utils.exception.MissingTokenException
import com.pixelized.biblib.utils.injection.inject

View file

@ -4,8 +4,9 @@ import androidx.compose.runtime.*
import androidx.lifecycle.ViewModel
import com.pixelized.biblib.network.client.IBibLibClient
import com.pixelized.biblib.network.data.query.AuthLoginQuery
import com.pixelized.biblib.repository.apiCache.IAPICacheRepository
import com.pixelized.biblib.repository.credential.ICredentialRepository
import com.pixelized.biblib.repository.googlesignin.IGoogleSingInRepository
import com.pixelized.biblib.repository.googleSignIn.IGoogleSingInRepository
import com.pixelized.biblib.ui.viewmodel.initialisation.IInitialisation.State.*
import com.pixelized.biblib.utils.injection.inject
import kotlinx.coroutines.delay
@ -14,6 +15,7 @@ class InitialisationViewModel : ViewModel(), IInitialisation {
private val credentialRepository: ICredentialRepository by inject()
private val googleSignIn: IGoogleSingInRepository by inject()
private val client: IBibLibClient by inject()
private val apiCache: IAPICacheRepository by inject()
@Composable
override fun LoadApplication(content: @Composable (IInitialisation.State) -> Unit) {
@ -24,6 +26,9 @@ class InitialisationViewModel : ViewModel(), IInitialisation {
delay(2000)
val loggedIn = loginWithGoogle() || loginWithCredential()
if (loggedIn) {
loadNewBooks() && loadAllBooks()
}
state.value = Finished(needLogin = loggedIn.not())
}
@ -71,4 +76,20 @@ class InitialisationViewModel : ViewModel(), IInitialisation {
false
}
}
private suspend fun loadNewBooks(): Boolean {
val cached = apiCache.new
val updated = client.service.new()
return if (cached != updated) {
apiCache.new = updated
true
} else {
false
}
}
private suspend fun loadAllBooks(): Boolean {
apiCache.list = client.service.list()
return true
}
}

View file

@ -0,0 +1,4 @@
package com.pixelized.biblib.utils.exception
class MandatoryFieldMissingException(method: String, name: String, json: Any) :
RuntimeException("Parse exception: Mandatory field:'$name' is missing from:'$method', with data:$json")

View file

@ -0,0 +1,5 @@
package com.pixelized.biblib.utils.extention
fun Int.toBoolean(): Boolean = this != 0
fun Int?.toBoolean(): Boolean? = if (this == null) null else this != 0