diff --git a/app/schemas/ua.acclorite.book_story.data.local.room.BookDatabase/14.json b/app/schemas/ua.acclorite.book_story.data.local.room.BookDatabase/14.json new file mode 100644 index 00000000..efbbada4 --- /dev/null +++ b/app/schemas/ua.acclorite.book_story.data.local.room.BookDatabase/14.json @@ -0,0 +1,220 @@ +{ + "formatVersion": 1, + "database": { + "version": 14, + "identityHash": "cd99cc3f6288826f0ad96bc8fe7aca3a", + "entities": [ + { + "tableName": "BookEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `author` TEXT, `description` TEXT, `filePath` TEXT NOT NULL, `scrollIndex` INTEGER NOT NULL, `scrollOffset` INTEGER NOT NULL, `progress` REAL NOT NULL, `image` TEXT, `categories` TEXT NOT NULL DEFAULT '[]')", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "author", + "columnName": "author", + "affinity": "TEXT" + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT" + }, + { + "fieldPath": "filePath", + "columnName": "filePath", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "scrollIndex", + "columnName": "scrollIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "scrollOffset", + "columnName": "scrollOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "progress", + "columnName": "progress", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "image", + "columnName": "image", + "affinity": "TEXT" + }, + { + "fieldPath": "categories", + "columnName": "categories", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'[]'" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "HistoryEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `bookId` INTEGER NOT NULL, `time` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "bookId", + "columnName": "bookId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "time", + "columnName": "time", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "ColorPresetEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `backgroundColor` INTEGER NOT NULL, `fontColor` INTEGER NOT NULL, `isSelected` INTEGER NOT NULL, `order` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "backgroundColor", + "columnName": "backgroundColor", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fontColor", + "columnName": "fontColor", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isSelected", + "columnName": "isSelected", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "order", + "columnName": "order", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "CategoryEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "order", + "columnName": "order", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "CategorySortEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`categoryId` INTEGER NOT NULL, `sortOrder` TEXT NOT NULL, `sortOrderDescending` INTEGER NOT NULL, PRIMARY KEY(`categoryId`))", + "fields": [ + { + "fieldPath": "categoryId", + "columnName": "categoryId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sortOrder", + "columnName": "sortOrder", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sortOrderDescending", + "columnName": "sortOrderDescending", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "categoryId" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'cd99cc3f6288826f0ad96bc8fe7aca3a')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/core/Typealias.kt b/app/src/main/java/ua/acclorite/book_story/core/Typealias.kt index 32f11642..6d9c52d9 100644 --- a/app/src/main/java/ua/acclorite/book_story/core/Typealias.kt +++ b/app/src/main/java/ua/acclorite/book_story/core/Typealias.kt @@ -9,7 +9,6 @@ package ua.acclorite.book_story.core import android.graphics.Bitmap typealias CoverImage = Bitmap -typealias Selected = Boolean typealias BottomSheet = String typealias Dialog = String typealias Drawer = String \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/di/AppModule.kt b/app/src/main/java/ua/acclorite/book_story/data/di/AppModule.kt index 0cf658d8..60ac3010 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/di/AppModule.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/di/AppModule.kt @@ -19,7 +19,6 @@ import org.commonmark.node.HtmlBlock import org.commonmark.node.IndentedCodeBlock import org.commonmark.node.ThematicBreak import org.commonmark.parser.Parser -import ua.acclorite.book_story.data.local.room.BookDao import ua.acclorite.book_story.data.local.room.BookDatabase import ua.acclorite.book_story.data.local.room.DatabaseHelper import javax.inject.Singleton @@ -49,7 +48,7 @@ object AppModule { @Provides @Singleton - fun provideBookDao(app: Application): BookDao { + fun provideBookDatabase(app: Application): BookDatabase { // Additional Migrations DatabaseHelper.MIGRATION_7_8.removeBooksDir(app) @@ -57,14 +56,11 @@ object AppModule { app, BookDatabase::class.java, "book_db" - ) - .addMigrations( - DatabaseHelper.MIGRATION_2_3, // creates LanguageHistoryEntity table(if does not exist) - DatabaseHelper.MIGRATION_4_5, // creates ColorPresetEntity table(if does not exist) - DatabaseHelper.MIGRATION_5_6, // creates FavoriteDirectoryEntity table(if does not exist) - ) - .allowMainThreadQueries() - .build() - .dao + ).addMigrations( + DatabaseHelper.MIGRATION_2_3, // creates LanguageHistoryEntity table(if does not exist) + DatabaseHelper.MIGRATION_4_5, // creates ColorPresetEntity table(if does not exist) + DatabaseHelper.MIGRATION_5_6, // creates FavoriteDirectoryEntity table(if does not exist) + DatabaseHelper.MIGRATION_13_14, // remove nullability from ColorPresetEntity + ).allowMainThreadQueries().build() } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/local/dto/ColorPresetEntity.kt b/app/src/main/java/ua/acclorite/book_story/data/local/dto/ColorPresetEntity.kt index 4ebd4afe..c01c3c54 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/local/dto/ColorPresetEntity.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/local/dto/ColorPresetEntity.kt @@ -13,7 +13,7 @@ import androidx.room.PrimaryKey data class ColorPresetEntity( @PrimaryKey(true) val id: Int? = null, - val name: String?, + val name: String, val backgroundColor: Long, val fontColor: Long, val isSelected: Boolean, diff --git a/app/src/main/java/ua/acclorite/book_story/data/local/room/BookDao.kt b/app/src/main/java/ua/acclorite/book_story/data/local/room/BookDao.kt index f510431f..58f1859e 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/local/room/BookDao.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/local/room/BookDao.kt @@ -16,12 +16,8 @@ import androidx.room.Upsert import ua.acclorite.book_story.data.local.dto.BookEntity import ua.acclorite.book_story.data.local.dto.CategoryEntity import ua.acclorite.book_story.data.local.dto.CategorySortEntity -import ua.acclorite.book_story.data.local.dto.ColorPresetEntity import ua.acclorite.book_story.data.local.dto.HistoryEntity -/** - * Class to manipulate Room database. - */ @Dao interface BookDao { @@ -73,27 +69,6 @@ interface BookDao { /* - - - - - - - - - - - - - - - - - - - - - - */ - /* ------ ColorPresetEntity ----------------- */ - @Upsert - suspend fun updateColorPreset(colorPreset: ColorPresetEntity) - - @Query("SELECT `order` FROM colorpresetentity WHERE :id=id") - suspend fun getColorPresetOrder(id: Int): Int - - @Query("SELECT COUNT(*) FROM colorpresetentity") - suspend fun getColorPresetsSize(): Int - - @Query("SELECT * FROM colorpresetentity") - suspend fun getColorPresets(): List - - @Delete - suspend fun deleteColorPreset(colorPreset: ColorPresetEntity) - - @Query("DELETE FROM colorpresetentity") - suspend fun deleteColorPresets() - /* - - - - - - - - - - - - - - - - - - - - - - */ - - /* ------ CategoryEntity ----------------- */ @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertCategory( diff --git a/app/src/main/java/ua/acclorite/book_story/data/local/room/BookDatabase.kt b/app/src/main/java/ua/acclorite/book_story/data/local/room/BookDatabase.kt index 815f3c3e..0dc4aa37 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/local/room/BookDatabase.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/local/room/BookDatabase.kt @@ -30,7 +30,7 @@ import java.io.File CategoryEntity::class, CategorySortEntity::class ], - version = 13, + version = 14, autoMigrations = [ AutoMigration(1, 2), AutoMigration(2, 3), @@ -44,11 +44,13 @@ import java.io.File AutoMigration(10, 11), AutoMigration(11, 12), AutoMigration(12, 13), + AutoMigration(13, 14), ], exportSchema = true ) abstract class BookDatabase : RoomDatabase() { - abstract val dao: BookDao + abstract val bookDao: BookDao + abstract val colorPresetDao: ColorPresetDao } @Suppress("ClassName") @@ -124,4 +126,36 @@ object DatabaseHelper { @DeleteColumn("BookEntity", "category") class MIGRATION_9_10 : AutoMigrationSpec + + val MIGRATION_13_14 = object : Migration(13, 14) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS ColorPresetEntity ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + backgroundColor INTEGER NOT NULL, + fontColor INTEGER NOT NULL, + isSelected INTEGER NOT NULL, + `order` INTEGER NOT NULL + ) + """ + ) + database.execSQL( + """ + INSERT INTO ColorPresetEntity_new (id, name, backgroundColor, fontColor, isSelected, `order`) + SELECT + id, + COALESCE(name, ''), + backgroundColor, + fontColor, + isSelected, + `order` + FROM ColorPresetEntity + """ + ) + database.execSQL("DROP TABLE ColorPresetEntity") + database.execSQL("ALTER TABLE ColorPresetEntity_new RENAME TO ColorPresetEntity") + } + } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/local/room/ColorPresetDao.kt b/app/src/main/java/ua/acclorite/book_story/data/local/room/ColorPresetDao.kt new file mode 100644 index 00000000..216b99f2 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/data/local/room/ColorPresetDao.kt @@ -0,0 +1,34 @@ +/* + * Book's Story — free and open-source Material You eBook reader. + * Copyright (C) 2024-2025 Acclorite + * SPDX-License-Identifier: GPL-3.0-only + */ + +package ua.acclorite.book_story.data.local.room + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Query +import androidx.room.Upsert +import ua.acclorite.book_story.data.local.dto.ColorPresetEntity + +@Dao +interface ColorPresetDao { + @Upsert + suspend fun updateColorPreset(colorPreset: ColorPresetEntity) + + @Query("SELECT `order` FROM colorpresetentity WHERE id = :id") + suspend fun getColorPresetOrder(id: Int): Int + + @Query("SELECT COUNT(*) FROM colorpresetentity") + suspend fun getColorPresetsSize(): Int + + @Query("SELECT * FROM colorpresetentity ORDER BY `order` ASC") + suspend fun getColorPresets(): List + + @Delete + suspend fun deleteColorPreset(colorPreset: ColorPresetEntity) + + @Query("DELETE FROM colorpresetentity") + suspend fun deleteColorPresets() +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/repository/BookRepositoryImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/repository/BookRepositoryImpl.kt index 814d8f82..89930859 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/repository/BookRepositoryImpl.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/repository/BookRepositoryImpl.kt @@ -6,8 +6,10 @@ package ua.acclorite.book_story.data.repository +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import ua.acclorite.book_story.core.CoverImage -import ua.acclorite.book_story.data.local.room.BookDao +import ua.acclorite.book_story.data.local.room.BookDatabase import ua.acclorite.book_story.data.mapper.book.BookMapper import ua.acclorite.book_story.data.mapper.file.FileMapper import ua.acclorite.book_story.data.parser.FileParser @@ -22,7 +24,7 @@ import javax.inject.Singleton @Singleton class BookRepositoryImpl @Inject constructor( - private val database: BookDao, + private val database: BookDatabase, private val bookMapper: BookMapper, private val fileMapper: FileMapper, private val fileParser: FileParser, @@ -31,46 +33,62 @@ class BookRepositoryImpl @Inject constructor( ) : BookRepository { override suspend fun searchBooks(query: String): Result> = runCatching { - database.searchBooks(query).map { bookMapper.toBook(it) } + withContext(Dispatchers.IO) { + database.bookDao.searchBooks(query).map { bookMapper.toBook(it) } + } } override suspend fun getBook(bookId: Int): Result = runCatching { - database.findBookById(bookId).let { - if (it == null) throw NoSuchElementException("Couldn't get book [$bookId].") - else bookMapper.toBook(it) + withContext(Dispatchers.IO) { + database.bookDao.findBookById(bookId).let { + if (it == null) throw NoSuchElementException("Couldn't get book [$bookId].") + else bookMapper.toBook(it) + } } } override suspend fun getText(bookId: Int): Result> { - return getBook(bookId) - .mapCatching { fileProvider.getFileFromBook(it).getOrThrow() } - .mapCatching { textParser.parse(it) } + return withContext(Dispatchers.IO) { + getBook(bookId) + .mapCatching { fileProvider.getFileFromBook(it).getOrThrow() } + .mapCatching { textParser.parse(it) } + } } override suspend fun getFileFromBook(bookId: Int): Result { - return getBook(bookId) - .mapCatching { fileProvider.getFileFromBook(it).getOrThrow() } - .mapCatching { fileMapper.toFile(it) } + return withContext(Dispatchers.IO) { + getBook(bookId) + .mapCatching { fileProvider.getFileFromBook(it).getOrThrow() } + .mapCatching { fileMapper.toFile(it) } + } } override suspend fun addBook(book: Book): Result = runCatching { - database.insertBook(bookMapper.toBookEntity(book)) + withContext(Dispatchers.IO) { + database.bookDao.insertBook(bookMapper.toBookEntity(book)) + } } override suspend fun updateBook(book: Book): Result = runCatching { - database.updateBook(bookMapper.toBookEntity(book)).also { - if (it == 0) throw Exception("Could not update book in database.") + withContext(Dispatchers.IO) { + database.bookDao.updateBook(bookMapper.toBookEntity(book)).also { + if (it == 0) throw Exception("Could not update book in database.") + } } } override suspend fun deleteBook(book: Book): Result = runCatching { - database.deleteBook(bookMapper.toBookEntity(book)).also { - if (it == 0) throw Exception("Could not delete book in database.") + withContext(Dispatchers.IO) { + database.bookDao.deleteBook(bookMapper.toBookEntity(book)).also { + if (it == 0) throw Exception("Could not delete book in database.") + } } } override suspend fun getDefaultCover(book: Book): Result = runCatching { - return fileProvider.getFileFromBook(book) - .mapCatching { fileParser.parse(it)?.coverImage } + return withContext(Dispatchers.IO) { + fileProvider.getFileFromBook(book) + .mapCatching { fileParser.parse(it)?.coverImage } + } } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/repository/CategoryRepositoryImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/repository/CategoryRepositoryImpl.kt index 50dfce92..dfb43dbf 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/repository/CategoryRepositoryImpl.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/repository/CategoryRepositoryImpl.kt @@ -6,7 +6,9 @@ package ua.acclorite.book_story.data.repository -import ua.acclorite.book_story.data.local.room.BookDao +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import ua.acclorite.book_story.data.local.room.BookDatabase import ua.acclorite.book_story.data.mapper.category.CategoryMapper import ua.acclorite.book_story.data.mapper.category_sort.CategorySortMapper import ua.acclorite.book_story.domain.model.library.Category @@ -17,57 +19,72 @@ import javax.inject.Singleton @Singleton class CategoryRepositoryImpl @Inject constructor( - private val database: BookDao, + private val database: BookDatabase, private val categoryMapper: CategoryMapper, private val categorySortMapper: CategorySortMapper, ) : CategoryRepository { override suspend fun addCategory(category: Category): Result = runCatching { - database.insertCategory( - categoryMapper.toCategoryEntity( - category.copy( - order = database.getCategoriesCount() + withContext(Dispatchers.IO) { + database.bookDao.insertCategory( + categoryMapper.toCategoryEntity( + category.copy( + order = database.bookDao.getCategoriesCount() + ) ) ) - ) + } } override suspend fun getCategories(): Result> = runCatching { - database.getCategories().map { categoryMapper.toCategory(it) }.sortedBy { it.order } + withContext(Dispatchers.IO) { + database.bookDao.getCategories().map { categoryMapper.toCategory(it) } + .sortedBy { it.order } + } } override suspend fun updateCategory(category: Category): Result = runCatching { - database.updateCategoryTitle( - id = category.id, - title = category.title - ) - updateOrder(getCategories().getOrThrow()) + withContext(Dispatchers.IO) { + database.bookDao.updateCategoryTitle( + id = category.id, + title = category.title + ) + updateOrder(getCategories().getOrThrow()) + } } override suspend fun updateOrder(categories: List): Result = runCatching { - categories.forEachIndexed { index, category -> - database.updateCategoryOrder(category.id, index) + withContext(Dispatchers.IO) { + categories.forEachIndexed { index, category -> + database.bookDao.updateCategoryOrder(category.id, index) + } } } override suspend fun deleteCategory(category: Category): Result = runCatching { - database.deleteCategory(categoryMapper.toCategoryEntity(category)) - database.deleteCategorySortEntity(category.id) + withContext(Dispatchers.IO) { + database.bookDao.deleteCategory(categoryMapper.toCategoryEntity(category)) + database.bookDao.deleteCategorySortEntity(category.id) - updateOrder(getCategories().getOrThrow()) + updateOrder(getCategories().getOrThrow()) + } } override suspend fun updateCategorySorting( categorySort: CategorySort ): Result = runCatching { - database.updateCategorySort( - categorySortMapper.toCategorySortEntity(categorySort) - ) + withContext(Dispatchers.IO) { + database.bookDao.updateCategorySort( + categorySortMapper.toCategorySortEntity(categorySort) + ) + } } override suspend fun getCategorySorting(): Result> = runCatching { - database.getCategorySortEntities().map { - categorySortMapper.toCategorySort(it) + withContext(Dispatchers.IO) { + database.bookDao.getCategorySortEntities().map { + categorySortMapper.toCategorySort(it) + } } } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/repository/ColorPresetRepositoryImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/repository/ColorPresetRepositoryImpl.kt index 96c59f8f..49c5c127 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/repository/ColorPresetRepositoryImpl.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/repository/ColorPresetRepositoryImpl.kt @@ -6,7 +6,9 @@ package ua.acclorite.book_story.data.repository -import ua.acclorite.book_story.data.local.room.BookDao +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import ua.acclorite.book_story.data.local.room.BookDatabase import ua.acclorite.book_story.data.mapper.color_preset.ColorPresetMapper import ua.acclorite.book_story.domain.model.reader.ColorPreset import ua.acclorite.book_story.domain.repository.ColorPresetRepository @@ -15,56 +17,67 @@ import javax.inject.Singleton @Singleton class ColorPresetRepositoryImpl @Inject constructor( - private val database: BookDao, + private val database: BookDatabase, private val colorPresetMapper: ColorPresetMapper ) : ColorPresetRepository { override suspend fun getColorPresets(): Result> = runCatching { - database.getColorPresets() - .sortedBy { it.order } - .map { colorPresetMapper.toColorPreset(it) } + withContext(Dispatchers.IO) { + database.colorPresetDao.getColorPresets().map { + colorPresetMapper.toColorPreset(it) + } + } } override suspend fun updateColorPreset( colorPreset: ColorPreset ): Result = runCatching { - database.updateColorPreset( - colorPresetMapper.toColorPresetEntity( - colorPreset = colorPreset, - order = if (colorPreset.id != -1) database.getColorPresetOrder(colorPreset.id) - else database.getColorPresetsSize() + withContext(Dispatchers.IO) { + database.colorPresetDao.updateColorPreset( + colorPresetMapper.toColorPresetEntity( + colorPreset = colorPreset, + order = if (colorPreset.id != -1) { + database.colorPresetDao.getColorPresetOrder(colorPreset.id) + } else database.colorPresetDao.getColorPresetsSize() + ) ) - ) + } } override suspend fun selectColorPreset( colorPreset: ColorPreset ): Result = runCatching { - database.getColorPresets().map { - it.copy(isSelected = it.id == colorPreset.id) - }.forEach { - database.updateColorPreset(it) + withContext(Dispatchers.IO) { + database.colorPresetDao.getColorPresets().map { + it.copy(isSelected = it.id == colorPreset.id) + }.forEach { + database.colorPresetDao.updateColorPreset(it) + } } } override suspend fun reorderColorPresets( colorPresets: List ): Result = runCatching { - database.deleteColorPresets() - colorPresets.forEachIndexed { index, colorPreset -> - database.updateColorPreset( - colorPresetMapper.toColorPresetEntity(colorPreset, order = index) - ) + withContext(Dispatchers.IO) { + database.colorPresetDao.deleteColorPresets() + colorPresets.forEachIndexed { index, colorPreset -> + database.colorPresetDao.updateColorPreset( + colorPresetMapper.toColorPresetEntity(colorPreset, order = index) + ) + } } } override suspend fun deleteColorPreset( colorPreset: ColorPreset ): Result = runCatching { - database.deleteColorPreset( - colorPresetMapper.toColorPresetEntity( - colorPreset, -1 + withContext(Dispatchers.IO) { + database.colorPresetDao.deleteColorPreset( + colorPresetMapper.toColorPresetEntity( + colorPreset, -1 + ) ) - ) + } } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/repository/FileSystemRepositoryImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/repository/FileSystemRepositoryImpl.kt index a81e3112..3c7b935d 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/repository/FileSystemRepositoryImpl.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/repository/FileSystemRepositoryImpl.kt @@ -6,8 +6,10 @@ package ua.acclorite.book_story.data.repository +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import ua.acclorite.book_story.core.data.ExtensionsData -import ua.acclorite.book_story.data.local.room.BookDao +import ua.acclorite.book_story.data.local.room.BookDatabase import ua.acclorite.book_story.data.mapper.file.FileMapper import ua.acclorite.book_story.data.model.common.BookWithCover import ua.acclorite.book_story.data.model.file.CachedFile @@ -18,28 +20,26 @@ import ua.acclorite.book_story.domain.service.FileProvider import javax.inject.Inject import javax.inject.Singleton -/** - * File System repository. - * Manages all File System related work. - */ @Singleton class FileSystemRepositoryImpl @Inject constructor( - private val database: BookDao, + private val database: BookDatabase, private val fileMapper: FileMapper, private val fileParser: FileParser, private val fileProvider: FileProvider ) : FileSystemRepository { override suspend fun searchFiles(query: String): Result> { - return fileProvider.getStorageFiles().mapCatching { storages -> - val existingFiles = database.searchBooks("").map { it.filePath } + return withContext(Dispatchers.IO) { + fileProvider.getStorageFiles().mapCatching { storages -> + val existingFiles = database.bookDao.searchBooks("").map { it.filePath } - storages.map { storage -> - storage.getFilesFromStorage( - query = query, - existingFiles = existingFiles - ) - }.flatten() + storages.map { storage -> + storage.getFilesFromStorage( + query = query, + existingFiles = existingFiles + ) + }.flatten() + } } } @@ -74,7 +74,9 @@ class FileSystemRepositoryImpl @Inject constructor( } override suspend fun getBookFromFile(file: File): Result = runCatching { - fileParser.parse(fileMapper.toCachedFile(file)) - ?: throw Exception("Could not parse ${file.name}.") + withContext(Dispatchers.IO) { + fileParser.parse(fileMapper.toCachedFile(file)) + ?: throw Exception("Could not parse ${file.name}.") + } } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/repository/HistoryRepositoryImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/repository/HistoryRepositoryImpl.kt index 5b0d7b6b..eefcc4d0 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/repository/HistoryRepositoryImpl.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/repository/HistoryRepositoryImpl.kt @@ -6,7 +6,9 @@ package ua.acclorite.book_story.data.repository -import ua.acclorite.book_story.data.local.room.BookDao +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import ua.acclorite.book_story.data.local.room.BookDatabase import ua.acclorite.book_story.data.mapper.history.HistoryMapper import ua.acclorite.book_story.domain.model.history.History import ua.acclorite.book_story.domain.repository.HistoryRepository @@ -15,40 +17,52 @@ import javax.inject.Singleton @Singleton class HistoryRepositoryImpl @Inject constructor( - private val database: BookDao, + private val database: BookDatabase, private val historyMapper: HistoryMapper ) : HistoryRepository { override suspend fun getHistoryForBook(bookId: Int): Result = runCatching { - database.getHistoryForBook(bookId).let { - if (it == null) throw NoSuchElementException("Could not get history from [$bookId].") - else historyMapper.toHistory(it) + withContext(Dispatchers.IO) { + database.bookDao.getHistoryForBook(bookId).let { + if (it == null) throw NoSuchElementException("Could not get history from [$bookId].") + else historyMapper.toHistory(it) + } } } override suspend fun addHistory(history: History): Result = runCatching { - database.insertHistory(historyMapper.toHistoryEntity(history)) + withContext(Dispatchers.IO) { + database.bookDao.insertHistory(historyMapper.toHistoryEntity(history)) + } } override suspend fun getHistory(): Result> = runCatching { - database.getHistory().map { historyMapper.toHistory(it) } + withContext(Dispatchers.IO) { + database.bookDao.getHistory().map { historyMapper.toHistory(it) } + } } override suspend fun deleteWholeHistory(): Result = runCatching { - database.deleteWholeHistory().also { - if (it == 0) throw Exception("Could not delete whole history in database.") + withContext(Dispatchers.IO) { + database.bookDao.deleteWholeHistory().also { + if (it == 0) throw Exception("Could not delete whole history in database.") + } } } override suspend fun deleteHistoryForBook(bookId: Int): Result = runCatching { - database.deleteHistoryForBook(bookId = bookId).also { - if (it == 0) throw Exception("Could not delete history for book [$bookId] in database.") + withContext(Dispatchers.IO) { + database.bookDao.deleteHistoryForBook(bookId = bookId).also { + if (it == 0) throw Exception("Could not delete history for book [$bookId] in database.") + } } } override suspend fun deleteHistory(history: History): Result = runCatching { - database.deleteHistory(historyMapper.toHistoryEntity(history)).also { - if (it == 0) throw Exception("Could not delete history in database.") + withContext(Dispatchers.IO) { + database.bookDao.deleteHistory(historyMapper.toHistoryEntity(history)).also { + if (it == 0) throw Exception("Could not delete history in database.") + } } } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/model/reader/ColorPreset.kt b/app/src/main/java/ua/acclorite/book_story/domain/model/reader/ColorPreset.kt index 48c3773f..5c1caea9 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/model/reader/ColorPreset.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/model/reader/ColorPreset.kt @@ -8,20 +8,19 @@ package ua.acclorite.book_story.domain.model.reader import androidx.compose.runtime.Immutable import androidx.compose.ui.graphics.Color -import ua.acclorite.book_story.core.Selected @Immutable data class ColorPreset( val id: Int, - val name: String?, + val name: String, val backgroundColor: Color, val fontColor: Color, - val isSelected: Selected + val isSelected: Boolean ) { companion object { val default = ColorPreset( id = -1, - name = null, + name = "", backgroundColor = Color(0xFFFAF8FF), // Blue Light Surface (hardcoded) fontColor = Color(0xFF44464F), // Blue Light OnSurfaceVariant (hardcoded) isSelected = false diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/library/model/SelectableBook.kt b/app/src/main/java/ua/acclorite/book_story/presentation/library/model/SelectableBook.kt index e57802e3..3abb5df0 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/library/model/SelectableBook.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/library/model/SelectableBook.kt @@ -7,11 +7,10 @@ package ua.acclorite.book_story.presentation.library.model import androidx.compose.runtime.Immutable -import ua.acclorite.book_story.core.Selected import ua.acclorite.book_story.domain.model.library.Book @Immutable data class SelectableBook( val data: Book, - val selected: Selected + val selected: Boolean ) \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/library/model/SelectableNullableBook.kt b/app/src/main/java/ua/acclorite/book_story/presentation/library/model/SelectableNullableBook.kt index 7126840f..1c2b9f21 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/library/model/SelectableNullableBook.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/library/model/SelectableNullableBook.kt @@ -7,11 +7,10 @@ package ua.acclorite.book_story.presentation.library.model import androidx.compose.runtime.Immutable -import ua.acclorite.book_story.core.Selected import ua.acclorite.book_story.data.model.common.NullableBook @Immutable data class SelectableNullableBook( val data: NullableBook, - val selected: Selected + val selected: Boolean ) \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/ui/common/components/modal_drawer/ModalDrawerSelectableItem.kt b/app/src/main/java/ua/acclorite/book_story/ui/common/components/modal_drawer/ModalDrawerSelectableItem.kt index f86f3003..8f862351 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/common/components/modal_drawer/ModalDrawerSelectableItem.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/common/components/modal_drawer/ModalDrawerSelectableItem.kt @@ -23,7 +23,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp -import ua.acclorite.book_story.core.Selected /** * Modal Drawer Selectable Item. @@ -38,7 +37,7 @@ import ua.acclorite.book_story.core.Selected @Composable fun ModalDrawerSelectableItem( modifier: Modifier = Modifier, - selected: Selected, + selected: Boolean, enabled: Boolean = true, onClick: () -> Unit, content: @Composable RowScope.() -> Unit diff --git a/app/src/main/java/ua/acclorite/book_story/ui/settings/SettingsEffects.kt b/app/src/main/java/ua/acclorite/book_story/ui/settings/SettingsEffects.kt index 52235d74..a6f1ec9f 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/settings/SettingsEffects.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/settings/SettingsEffects.kt @@ -28,7 +28,7 @@ fun SettingsEffects(effects: SharedFlow) { is SettingsEffect.OnSwitchedColorPreset -> { context.getString( R.string.color_preset_selected_query, - if (effect.newColorPreset.name.isNullOrBlank()) { + if (effect.newColorPreset.name.isBlank()) { context.getString( R.string.color_preset_query, effect.newColorPreset.id.toString() diff --git a/app/src/main/java/ua/acclorite/book_story/ui/settings/appearance/colors/components/ColorPresetOption.kt b/app/src/main/java/ua/acclorite/book_story/ui/settings/appearance/colors/components/ColorPresetOption.kt index 83b257ca..720b08f1 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/settings/appearance/colors/components/ColorPresetOption.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/settings/appearance/colors/components/ColorPresetOption.kt @@ -63,7 +63,6 @@ import sh.calvin.reorderable.ReorderableCollectionItemScope import sh.calvin.reorderable.ReorderableItem import sh.calvin.reorderable.rememberReorderableLazyListState import ua.acclorite.book_story.R -import ua.acclorite.book_story.core.Selected import ua.acclorite.book_story.domain.model.reader.ColorPreset import ua.acclorite.book_story.presentation.settings.SettingsEvent import ua.acclorite.book_story.presentation.settings.SettingsModel @@ -233,7 +232,7 @@ fun ColorPresetOption(backgroundColor: Color) { @Composable private fun ReorderableCollectionItemScope.ColorPresetOptionRowItem( colorPreset: ColorPreset, - isSelected: Selected, + isSelected: Boolean, canDrag: Boolean, enableAnimation: Boolean, onDragStopped: () -> Unit, @@ -241,14 +240,14 @@ private fun ReorderableCollectionItemScope.ColorPresetOptionRowItem( ) { val context = LocalContext.current val title = remember(colorPreset) { - if ((colorPreset.name ?: "").isBlank()) { + if (colorPreset.name.isBlank()) { return@remember context.getString( R.string.color_preset_query, colorPreset.id.toString() ) } - colorPreset.name!! + colorPreset.name } val borderColor = remember(isSelected, colorPreset.fontColor) { @@ -344,7 +343,7 @@ private fun ColorPresetOptionConfigurationItem( onAdd: () -> Unit ) { val title = remember(selectedColorPreset.id) { - mutableStateOf(selectedColorPreset.name ?: "") + mutableStateOf(selectedColorPreset.name) } LaunchedEffect(title) {