From 2ad8d5953abf9915239cf7da47a0d0cde4e1e8b7 Mon Sep 17 00:00:00 2001 From: Thomas Andres Gomez Date: Sun, 9 May 2021 14:45:37 +0200 Subject: [PATCH] Add APICache + splashscreen init. --- .../com/pixelized/biblib/BibLibApplication.kt | 21 ++- .../java/com/pixelized/biblib/model/Author.kt | 7 + .../java/com/pixelized/biblib/model/Book.kt | 31 ++++ .../java/com/pixelized/biblib/model/Genre.kt | 6 + .../com/pixelized/biblib/model/Language.kt | 6 + .../java/com/pixelized/biblib/model/Series.kt | 8 + .../biblib/network/factory/BookFactory.kt | 157 ++++++++++++++++++ .../repository/apiCache/APICacheRepository.kt | 33 ++++ .../apiCache/IAPICacheRepository.kt | 8 + .../credential/CredentialRepository.kt | 9 +- .../GoogleSingInRepository.kt | 2 +- .../IGoogleSingInRepository.kt | 2 +- .../authentication/AuthenticationViewModel.kt | 2 +- .../initialisation/InitialisationViewModel.kt | 23 ++- .../MandatoryFieldMissingException.kt | 4 + .../pixelized/biblib/utils/extention/IntEx.kt | 5 + 16 files changed, 309 insertions(+), 15 deletions(-) create mode 100644 app/src/main/java/com/pixelized/biblib/model/Author.kt create mode 100644 app/src/main/java/com/pixelized/biblib/model/Book.kt create mode 100644 app/src/main/java/com/pixelized/biblib/model/Genre.kt create mode 100644 app/src/main/java/com/pixelized/biblib/model/Language.kt create mode 100644 app/src/main/java/com/pixelized/biblib/model/Series.kt create mode 100644 app/src/main/java/com/pixelized/biblib/network/factory/BookFactory.kt create mode 100644 app/src/main/java/com/pixelized/biblib/repository/apiCache/APICacheRepository.kt create mode 100644 app/src/main/java/com/pixelized/biblib/repository/apiCache/IAPICacheRepository.kt rename app/src/main/java/com/pixelized/biblib/repository/{googlesignin => googleSignIn}/GoogleSingInRepository.kt (94%) rename app/src/main/java/com/pixelized/biblib/repository/{googlesignin => googleSignIn}/IGoogleSingInRepository.kt (83%) create mode 100644 app/src/main/java/com/pixelized/biblib/utils/exception/MandatoryFieldMissingException.kt create mode 100644 app/src/main/java/com/pixelized/biblib/utils/extention/IntEx.kt diff --git a/app/src/main/java/com/pixelized/biblib/BibLibApplication.kt b/app/src/main/java/com/pixelized/biblib/BibLibApplication.kt index 7b3a996..83358ec 100644 --- a/app/src/main/java/com/pixelized/biblib/BibLibApplication.kt +++ b/app/src/main/java/com/pixelized/biblib/BibLibApplication.kt @@ -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" } } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/model/Author.kt b/app/src/main/java/com/pixelized/biblib/model/Author.kt new file mode 100644 index 0000000..dbb0cb0 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/model/Author.kt @@ -0,0 +1,7 @@ +package com.pixelized.biblib.model + +data class Author( + val id: String, + val name: String, + val sort: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/model/Book.kt b/app/src/main/java/com/pixelized/biblib/model/Book.kt new file mode 100644 index 0000000..d2bd3bc --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/model/Book.kt @@ -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, + val haveCover: Boolean, + val releaseDate: Date, + val language: Language? = null, + val rating: Int? = null, + val genre: List? = 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/model/Genre.kt b/app/src/main/java/com/pixelized/biblib/model/Genre.kt new file mode 100644 index 0000000..723abd9 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/model/Genre.kt @@ -0,0 +1,6 @@ +package com.pixelized.biblib.model + +data class Genre( + val id: String, + val name: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/model/Language.kt b/app/src/main/java/com/pixelized/biblib/model/Language.kt new file mode 100644 index 0000000..43d5807 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/model/Language.kt @@ -0,0 +1,6 @@ +package com.pixelized.biblib.model + +data class Language ( + val id : String, + val code: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/model/Series.kt b/app/src/main/java/com/pixelized/biblib/model/Series.kt new file mode 100644 index 0000000..9692b5d --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/model/Series.kt @@ -0,0 +1,8 @@ +package com.pixelized.biblib.model + +data class Series( + val id: String?, + val name: String, + val sort: String, + val index: Int?, +) \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/network/factory/BookFactory.kt b/app/src/main/java/com/pixelized/biblib/network/factory/BookFactory.kt new file mode 100644 index 0000000..9942fab --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/network/factory/BookFactory.kt @@ -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? = response.author_id + val authorName: List? = response.author_name + val authorSort: List? = response.author_sort + val authorIdSize = authorId?.size ?: 0 + val authorNameSize = authorName?.size ?: 0 + val authorSortSize = authorSort?.size ?: 0 + val author: List? = + 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? = response.author_id + val authorName: List? = response.author_name + val authorSort: List? = response.author_sort + val authorIdSize = authorId?.size + val authorNameSize = authorName?.size + val authorSortSize = authorSort?.size + val author: List? = + 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? = response.tag_id + val tagName: List? = response.tag_name + val tagIdSize: Int? = response.tag_id?.size + val tagNameSize: Int? = response.tag_name?.size + val tag: List? = 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'" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/repository/apiCache/APICacheRepository.kt b/app/src/main/java/com/pixelized/biblib/repository/apiCache/APICacheRepository.kt new file mode 100644 index 0000000..baaf092 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/repository/apiCache/APICacheRepository.kt @@ -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" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/repository/apiCache/IAPICacheRepository.kt b/app/src/main/java/com/pixelized/biblib/repository/apiCache/IAPICacheRepository.kt new file mode 100644 index 0000000..0e9b365 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/repository/apiCache/IAPICacheRepository.kt @@ -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? +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/repository/credential/CredentialRepository.kt b/app/src/main/java/com/pixelized/biblib/repository/credential/CredentialRepository.kt index 6eb0a27..ac08dfb 100644 --- a/app/src/main/java/com/pixelized/biblib/repository/credential/CredentialRepository.kt +++ b/app/src/main/java/com/pixelized/biblib/repository/credential/CredentialRepository.kt @@ -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" diff --git a/app/src/main/java/com/pixelized/biblib/repository/googlesignin/GoogleSingInRepository.kt b/app/src/main/java/com/pixelized/biblib/repository/googleSignIn/GoogleSingInRepository.kt similarity index 94% rename from app/src/main/java/com/pixelized/biblib/repository/googlesignin/GoogleSingInRepository.kt rename to app/src/main/java/com/pixelized/biblib/repository/googleSignIn/GoogleSingInRepository.kt index 4e5a362..09f8172 100644 --- a/app/src/main/java/com/pixelized/biblib/repository/googlesignin/GoogleSingInRepository.kt +++ b/app/src/main/java/com/pixelized/biblib/repository/googleSignIn/GoogleSingInRepository.kt @@ -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 diff --git a/app/src/main/java/com/pixelized/biblib/repository/googlesignin/IGoogleSingInRepository.kt b/app/src/main/java/com/pixelized/biblib/repository/googleSignIn/IGoogleSingInRepository.kt similarity index 83% rename from app/src/main/java/com/pixelized/biblib/repository/googlesignin/IGoogleSingInRepository.kt rename to app/src/main/java/com/pixelized/biblib/repository/googleSignIn/IGoogleSingInRepository.kt index 09272f0..b205cc2 100644 --- a/app/src/main/java/com/pixelized/biblib/repository/googlesignin/IGoogleSingInRepository.kt +++ b/app/src/main/java/com/pixelized/biblib/repository/googleSignIn/IGoogleSingInRepository.kt @@ -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 diff --git a/app/src/main/java/com/pixelized/biblib/ui/viewmodel/authentication/AuthenticationViewModel.kt b/app/src/main/java/com/pixelized/biblib/ui/viewmodel/authentication/AuthenticationViewModel.kt index 63b986a..6ba1927 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/viewmodel/authentication/AuthenticationViewModel.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/viewmodel/authentication/AuthenticationViewModel.kt @@ -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 diff --git a/app/src/main/java/com/pixelized/biblib/ui/viewmodel/initialisation/InitialisationViewModel.kt b/app/src/main/java/com/pixelized/biblib/ui/viewmodel/initialisation/InitialisationViewModel.kt index cbf2326..0f516c4 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/viewmodel/initialisation/InitialisationViewModel.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/viewmodel/initialisation/InitialisationViewModel.kt @@ -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 + } } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/utils/exception/MandatoryFieldMissingException.kt b/app/src/main/java/com/pixelized/biblib/utils/exception/MandatoryFieldMissingException.kt new file mode 100644 index 0000000..f32f61d --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/utils/exception/MandatoryFieldMissingException.kt @@ -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") \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/utils/extention/IntEx.kt b/app/src/main/java/com/pixelized/biblib/utils/extention/IntEx.kt new file mode 100644 index 0000000..6af1595 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/utils/extention/IntEx.kt @@ -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 \ No newline at end of file