diff --git a/app/build.gradle b/app/build.gradle index 217a492..3f4a8b6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,7 @@ plugins { id 'com.android.application' id 'kotlin-android' + id 'kotlin-kapt' id 'org.jetbrains.kotlin.android' } @@ -102,6 +103,14 @@ dependencies { implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'com.google.code.gson:gson:2.8.6' + // Room + implementation "androidx.room:room-runtime:2.3.0" + implementation "androidx.room:room-ktx:2.3.0" + kapt "androidx.room:room-compiler:2.3.0" + + // Paging + implementation "androidx.paging:paging-runtime-ktx:3.0.0" + // Test testImplementation 'junit:junit:4.13.2' androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.0.0-beta06" diff --git a/app/src/main/java/com/pixelized/biblib/BibLibApplication.kt b/app/src/main/java/com/pixelized/biblib/BibLibApplication.kt index 83358ec..663c5da 100644 --- a/app/src/main/java/com/pixelized/biblib/BibLibApplication.kt +++ b/app/src/main/java/com/pixelized/biblib/BibLibApplication.kt @@ -3,8 +3,10 @@ package com.pixelized.biblib import android.app.Application import android.content.Context import android.content.SharedPreferences +import androidx.room.Room import com.google.gson.Gson import com.google.gson.GsonBuilder +import com.pixelized.biblib.database.BibLibDatabase import com.pixelized.biblib.network.client.BibLibClient import com.pixelized.biblib.network.client.IBibLibClient import com.pixelized.biblib.repository.apiCache.APICacheRepository @@ -21,18 +23,23 @@ class BibLibApplication : Application() { override fun onCreate() { super.onCreate() + // Android. + Bob[BibLibDatabase::class] = + Room.databaseBuilder(this, BibLibDatabase::class.java, BibLibDatabase.DATABASE_NAME) + .build() Bob[SharedPreferences::class] = getSharedPreferences(SHARED_PREF, Context.MODE_PRIVATE) + // Web service. Bob[Gson::class] = GsonBuilder().create() - Bob[BitmapCache::class] = BitmapCache(this) - Bob[IBibLibClient::class] = BibLibClient() - + // Bitmap cache. + Bob[BitmapCache::class] = BitmapCache(this) + // Repositories. Bob[IGoogleSingInRepository::class] = GoogleSingInRepository(this) Bob[ICredentialRepository::class] = CredentialRepository() Bob[IAPICacheRepository::class] = APICacheRepository() } companion object { - const val SHARED_PREF = "BIB_LIB_SHARED_PREF" + private const val SHARED_PREF = "BIB_LIB_SHARED_PREF" } } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/database/BibLibDatabase.kt b/app/src/main/java/com/pixelized/biblib/database/BibLibDatabase.kt new file mode 100644 index 0000000..df852ac --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/database/BibLibDatabase.kt @@ -0,0 +1,37 @@ +package com.pixelized.biblib.database + +import androidx.room.Database +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import com.pixelized.biblib.database.converter.DateConverter +import com.pixelized.biblib.database.crossref.BookAuthorCrossRef +import com.pixelized.biblib.database.crossref.BookGenreCrossRef +import com.pixelized.biblib.database.dao.* +import com.pixelized.biblib.database.data.* + +@Database( + entities = [ + AuthorDbo::class, + BookDbo::class, + GenreDbo::class, + LanguageDbo::class, + SeriesDbo::class, + BookAuthorCrossRef::class, + BookGenreCrossRef::class, + ], + version = BibLibDatabase.VERSION +) +@TypeConverters(DateConverter::class) +abstract class BibLibDatabase : RoomDatabase() { + + abstract fun authorDao(): AuthorDao + abstract fun bookDao(): BookDao + abstract fun genreDao(): GenreDao + abstract fun languageDao(): LanguageDao + abstract fun seriesDao(): SeriesDao + + companion object { + const val VERSION = 1 + const val DATABASE_NAME = "BibLibDataBase" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/database/converter/DateConverter.kt b/app/src/main/java/com/pixelized/biblib/database/converter/DateConverter.kt new file mode 100644 index 0000000..a7ce6ec --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/database/converter/DateConverter.kt @@ -0,0 +1,12 @@ +package com.pixelized.biblib.database.converter + +import androidx.room.TypeConverter +import java.util.* + +class DateConverter { + @TypeConverter + fun fromTimestamp(value: Long?): Date? = value?.let { Date(it) } + + @TypeConverter + fun dateToTimestamp(date: Date?): Long? = date?.time +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/database/crossref/BookAuthorCrossRef.kt b/app/src/main/java/com/pixelized/biblib/database/crossref/BookAuthorCrossRef.kt new file mode 100644 index 0000000..10e588a --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/database/crossref/BookAuthorCrossRef.kt @@ -0,0 +1,14 @@ +package com.pixelized.biblib.database.crossref + +import androidx.room.ColumnInfo +import androidx.room.Entity +import com.pixelized.biblib.database.data.AuthorDbo +import com.pixelized.biblib.database.data.BookDbo + +@Entity(primaryKeys = [BookDbo.ID, AuthorDbo.ID]) +data class BookAuthorCrossRef( + @ColumnInfo(name = BookDbo.ID) + val bookId: String, + @ColumnInfo(name = AuthorDbo.ID) + val authorId: String +) diff --git a/app/src/main/java/com/pixelized/biblib/database/crossref/BookGenreCrossRef.kt b/app/src/main/java/com/pixelized/biblib/database/crossref/BookGenreCrossRef.kt new file mode 100644 index 0000000..478532c --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/database/crossref/BookGenreCrossRef.kt @@ -0,0 +1,14 @@ +package com.pixelized.biblib.database.crossref + +import androidx.room.ColumnInfo +import androidx.room.Entity +import com.pixelized.biblib.database.data.BookDbo +import com.pixelized.biblib.database.data.GenreDbo + +@Entity(primaryKeys = [BookDbo.ID, GenreDbo.ID]) +data class BookGenreCrossRef( + @ColumnInfo(name = BookDbo.ID) + val bookId: String, + @ColumnInfo(name = GenreDbo.ID) + val genreId: String +) \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/database/dao/AuthorDao.kt b/app/src/main/java/com/pixelized/biblib/database/dao/AuthorDao.kt new file mode 100644 index 0000000..7428d69 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/database/dao/AuthorDao.kt @@ -0,0 +1,16 @@ +package com.pixelized.biblib.database.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import com.pixelized.biblib.database.data.AuthorDbo + +@Dao +interface AuthorDao { + @Query("SELECT * FROM ${AuthorDbo.TABLE} WHERE ${AuthorDbo.ID} LIKE :id") + fun get(id: String?): AuthorDbo? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insert(vararg authors: AuthorDbo) +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/database/dao/BookDao.kt b/app/src/main/java/com/pixelized/biblib/database/dao/BookDao.kt new file mode 100644 index 0000000..5a6501d --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/database/dao/BookDao.kt @@ -0,0 +1,18 @@ +package com.pixelized.biblib.database.dao + +import androidx.room.* +import com.pixelized.biblib.database.data.BookDbo +import com.pixelized.biblib.database.relation.BookRelation + +@Dao +interface BookDao { + @Transaction + @Query("SELECT * FROM ${BookDbo.TABLE}") + fun getAll(): List + + @Insert + fun update(vararg books: BookDbo) + + @Delete + fun delete(book: BookDbo) +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/database/dao/GenreDao.kt b/app/src/main/java/com/pixelized/biblib/database/dao/GenreDao.kt new file mode 100644 index 0000000..4d97df4 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/database/dao/GenreDao.kt @@ -0,0 +1,16 @@ +package com.pixelized.biblib.database.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import com.pixelized.biblib.database.data.GenreDbo + +@Dao +interface GenreDao { + @Query("SELECT * FROM ${GenreDbo.TABLE} WHERE ${GenreDbo.ID} LIKE :id") + fun get(id: String?): GenreDbo? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insert(vararg genres: GenreDbo) +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/database/dao/LanguageDao.kt b/app/src/main/java/com/pixelized/biblib/database/dao/LanguageDao.kt new file mode 100644 index 0000000..b427c65 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/database/dao/LanguageDao.kt @@ -0,0 +1,16 @@ +package com.pixelized.biblib.database.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import com.pixelized.biblib.database.data.LanguageDbo + +@Dao +interface LanguageDao { + @Query("SELECT * FROM ${LanguageDbo.TABLE} WHERE ${LanguageDbo.ID} LIKE :id") + fun get(id: String?): LanguageDbo? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insert(vararg languages: LanguageDbo) +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/database/dao/SeriesDao.kt b/app/src/main/java/com/pixelized/biblib/database/dao/SeriesDao.kt new file mode 100644 index 0000000..7eb2450 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/database/dao/SeriesDao.kt @@ -0,0 +1,16 @@ +package com.pixelized.biblib.database.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import com.pixelized.biblib.database.data.SeriesDbo + +@Dao +interface SeriesDao { + @Query("SELECT * FROM ${SeriesDbo.TABLE} WHERE ${SeriesDbo.ID} LIKE :id") + fun get(id: String?): SeriesDbo? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insert(vararg series: SeriesDbo) +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/database/data/AuthorDbo.kt b/app/src/main/java/com/pixelized/biblib/database/data/AuthorDbo.kt new file mode 100644 index 0000000..fa91a63 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/database/data/AuthorDbo.kt @@ -0,0 +1,23 @@ +package com.pixelized.biblib.database.data + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = AuthorDbo.TABLE) +data class AuthorDbo( + @PrimaryKey + @ColumnInfo(name = ID) + val id: String, + @ColumnInfo(name = NAME) + val name: String, + @ColumnInfo(name = SORT) + val sort: String, +) { + companion object { + const val TABLE = "AUTHOR" + const val ID = "${TABLE}_ID" + const val NAME = "${TABLE}_NAME" + const val SORT = "${TABLE}_SORT" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/database/data/BookDbo.kt b/app/src/main/java/com/pixelized/biblib/database/data/BookDbo.kt new file mode 100644 index 0000000..fbabe9c --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/database/data/BookDbo.kt @@ -0,0 +1,58 @@ +package com.pixelized.biblib.database.data + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import java.util.* + +@Entity(tableName = BookDbo.TABLE) +data class BookDbo( + @PrimaryKey + @ColumnInfo(name = ID) + val id: Int, + @ColumnInfo(name = TITLE) + val title: String, + @ColumnInfo(name = SORT) + val sort: String, + @ColumnInfo(name = HAVE_COVER) + val haveCover: Boolean, + @ColumnInfo(name = RELEASE_DATE) + val releaseDate: Date, + @ColumnInfo(name = LANGUAGE_ID) + val language: String? = null, // one-to-many + @ColumnInfo(name = RATING) + val rating: Int? = null, + // details + @ColumnInfo(name = SERIES_ID) + val series: String? = null, // one-to-many + @ColumnInfo(name = SYNOPSIS) + val synopsis: String? = null, + // source + @ColumnInfo(name = IS_NEW) + val isNew: Boolean = false, +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is BookDbo) return false + if (id != other.id) return false + return true + } + + override fun hashCode(): Int { + return id + } + + companion object { + const val TABLE = "BOOK" + const val ID = "${TABLE}_ID" + const val TITLE = "${TABLE}_TITLE" + const val SORT = "${TABLE}_SORT" + const val HAVE_COVER = "${TABLE}_HAVE_COVER" + const val RELEASE_DATE = "${TABLE}_RELEASE_DATE" + const val LANGUAGE_ID = "${TABLE}_LANGUAGE_ID" + const val RATING = "${TABLE}_RATING" + const val SERIES_ID = "${TABLE}_SERIES_ID" + const val SYNOPSIS = "${TABLE}_SYNOPSIS" + const val IS_NEW = "${TABLE}_ISNEW" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/database/data/GenreDbo.kt b/app/src/main/java/com/pixelized/biblib/database/data/GenreDbo.kt new file mode 100644 index 0000000..2ec091f --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/database/data/GenreDbo.kt @@ -0,0 +1,20 @@ +package com.pixelized.biblib.database.data + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = GenreDbo.TABLE) +data class GenreDbo( + @PrimaryKey + @ColumnInfo(name = ID) + val id: String, + @ColumnInfo(name = NAME) + val name: String, +) { + companion object { + const val TABLE = "GENRE" + const val ID = "${TABLE}_ID" + const val NAME = "${TABLE}_NAME" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/database/data/LanguageDbo.kt b/app/src/main/java/com/pixelized/biblib/database/data/LanguageDbo.kt new file mode 100644 index 0000000..5f58a18 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/database/data/LanguageDbo.kt @@ -0,0 +1,21 @@ +package com.pixelized.biblib.database.data + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = LanguageDbo.TABLE) +data class LanguageDbo( + @PrimaryKey + @ColumnInfo(name = ID) + val id: String, + @ColumnInfo(name = NAME) + val code: String, +) { + companion object { + const val TABLE = "LANGUAGE" + const val ID = "${TABLE}_ID" + const val NAME = "${TABLE}_NAME" + } +} + diff --git a/app/src/main/java/com/pixelized/biblib/database/data/SeriesDbo.kt b/app/src/main/java/com/pixelized/biblib/database/data/SeriesDbo.kt new file mode 100644 index 0000000..cbdc57c --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/database/data/SeriesDbo.kt @@ -0,0 +1,26 @@ +package com.pixelized.biblib.database.data + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = SeriesDbo.TABLE) +data class SeriesDbo( + @PrimaryKey + @ColumnInfo(name = ID) + val id: String, + @ColumnInfo(name = NAME) + val name: String, + @ColumnInfo(name = SORT) + val sort: String, + @ColumnInfo(name = INDEX) + val index: Int?, +) { + companion object { + const val TABLE = "SERIES" + const val ID = "${TABLE}_ID" + const val NAME = "${TABLE}_NAME" + const val SORT = "${TABLE}_SORT" + const val INDEX = "${TABLE}_INDEX" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/database/relation/BookRelation.kt b/app/src/main/java/com/pixelized/biblib/database/relation/BookRelation.kt new file mode 100644 index 0000000..0a50a66 --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/database/relation/BookRelation.kt @@ -0,0 +1,35 @@ +package com.pixelized.biblib.database.relation + +import androidx.room.Embedded +import androidx.room.Junction +import androidx.room.Relation +import com.pixelized.biblib.database.crossref.BookAuthorCrossRef +import com.pixelized.biblib.database.crossref.BookGenreCrossRef +import com.pixelized.biblib.database.data.* + +data class BookRelation( + @Embedded + val bookDbo: BookDbo, + @Relation( + parentColumn = BookDbo.ID, + entityColumn = AuthorDbo.ID, + associateBy = Junction(BookAuthorCrossRef::class) + ) + val authors: List, + @Relation( + parentColumn = BookDbo.ID, + entityColumn = GenreDbo.ID, + associateBy = Junction(BookGenreCrossRef::class) + ) + val genres: List, + @Relation( + parentColumn = BookDbo.LANGUAGE_ID, + entityColumn = LanguageDbo.ID + ) + val language: LanguageDbo, + @Relation( + parentColumn = BookDbo.SERIES_ID, + entityColumn = SeriesDbo.ID + ) + val series: SeriesDbo, +) \ No newline at end of file