refactor: refactor use case & repository
This commit is contained in:
parent
378975bf3e
commit
72b032cabf
107 changed files with 1904 additions and 1655 deletions
25
app/src/main/java/ua/acclorite/book_story/core/Log.kt
Normal file
25
app/src/main/java/ua/acclorite/book_story/core/Log.kt
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* 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.core
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
|
fun logI(message: String) {
|
||||||
|
Log.i("INFO", message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun logD(message: String) {
|
||||||
|
Log.d("DEBUG", message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun logW(message: String) {
|
||||||
|
Log.w("WARNING", message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun logE(message: String) {
|
||||||
|
Log.e("ERROR", message)
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,8 @@ import ua.acclorite.book_story.data.mapper.category_sort.CategorySortMapper
|
||||||
import ua.acclorite.book_story.data.mapper.category_sort.CategorySortSortMapperImpl
|
import ua.acclorite.book_story.data.mapper.category_sort.CategorySortSortMapperImpl
|
||||||
import ua.acclorite.book_story.data.mapper.color_preset.ColorPresetMapper
|
import ua.acclorite.book_story.data.mapper.color_preset.ColorPresetMapper
|
||||||
import ua.acclorite.book_story.data.mapper.color_preset.ColorPresetMapperImpl
|
import ua.acclorite.book_story.data.mapper.color_preset.ColorPresetMapperImpl
|
||||||
|
import ua.acclorite.book_story.data.mapper.file.FileMapper
|
||||||
|
import ua.acclorite.book_story.data.mapper.file.FileMapperImpl
|
||||||
import ua.acclorite.book_story.data.mapper.history.HistoryMapper
|
import ua.acclorite.book_story.data.mapper.history.HistoryMapper
|
||||||
import ua.acclorite.book_story.data.mapper.history.HistoryMapperImpl
|
import ua.acclorite.book_story.data.mapper.history.HistoryMapperImpl
|
||||||
import ua.acclorite.book_story.data.parser.FileParser
|
import ua.acclorite.book_story.data.parser.FileParser
|
||||||
|
|
@ -123,6 +125,12 @@ abstract class RepositoryModule {
|
||||||
categorySortSortMapperImpl: CategorySortSortMapperImpl
|
categorySortSortMapperImpl: CategorySortSortMapperImpl
|
||||||
): CategorySortMapper
|
): CategorySortMapper
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
abstract fun bindFileMapper(
|
||||||
|
fileMapperImpl: FileMapperImpl
|
||||||
|
): FileMapper
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@Singleton
|
@Singleton
|
||||||
abstract fun bindFileParser(
|
abstract fun bindFileParser(
|
||||||
|
|
|
||||||
|
|
@ -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.di
|
||||||
|
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import ua.acclorite.book_story.data.service.CoverImageHandlerImpl
|
||||||
|
import ua.acclorite.book_story.data.service.FileProviderImpl
|
||||||
|
import ua.acclorite.book_story.domain.service.CoverImageHandler
|
||||||
|
import ua.acclorite.book_story.domain.service.FileProvider
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
abstract class ServiceModule {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
abstract fun bindFileProvider(
|
||||||
|
fileProviderImpl: FileProviderImpl
|
||||||
|
): FileProvider
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
abstract fun bindCoverImageHandler(
|
||||||
|
coverImageHandlerImpl: CoverImageHandlerImpl
|
||||||
|
): CoverImageHandler
|
||||||
|
}
|
||||||
|
|
@ -40,16 +40,13 @@ interface BookDao {
|
||||||
suspend fun searchBooks(query: String): List<BookEntity>
|
suspend fun searchBooks(query: String): List<BookEntity>
|
||||||
|
|
||||||
@Query("SELECT * FROM bookentity WHERE id=:id")
|
@Query("SELECT * FROM bookentity WHERE id=:id")
|
||||||
suspend fun findBookById(id: Int): BookEntity
|
suspend fun findBookById(id: Int): BookEntity?
|
||||||
|
|
||||||
@Query("SELECT * FROM bookentity WHERE id IN (:ids)")
|
|
||||||
suspend fun findBooksById(ids: List<Int>): List<BookEntity>
|
|
||||||
|
|
||||||
@Delete
|
@Delete
|
||||||
suspend fun deleteBooks(books: List<BookEntity>)
|
suspend fun deleteBook(book: BookEntity): Int
|
||||||
|
|
||||||
@Update
|
@Update
|
||||||
suspend fun updateBooks(books: List<BookEntity>)
|
suspend fun updateBook(book: BookEntity): Int
|
||||||
/* - - - - - - - - - - - - - - - - - - - - - - */
|
/* - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -58,21 +55,21 @@ interface BookDao {
|
||||||
suspend fun getHistory(): List<HistoryEntity>
|
suspend fun getHistory(): List<HistoryEntity>
|
||||||
|
|
||||||
@Query("SELECT * FROM historyentity WHERE bookId = :bookId ORDER BY time DESC LIMIT 1")
|
@Query("SELECT * FROM historyentity WHERE bookId = :bookId ORDER BY time DESC LIMIT 1")
|
||||||
fun getLatestHistoryForBook(bookId: Int): HistoryEntity?
|
fun getHistoryForBook(bookId: Int): HistoryEntity?
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
suspend fun insertHistory(
|
suspend fun insertHistory(
|
||||||
history: List<HistoryEntity>
|
history: HistoryEntity
|
||||||
)
|
)
|
||||||
|
|
||||||
@Query("DELETE FROM historyentity")
|
@Query("DELETE FROM historyentity")
|
||||||
suspend fun deleteWholeHistory()
|
suspend fun deleteWholeHistory(): Int
|
||||||
|
|
||||||
@Query("DELETE FROM historyentity WHERE bookId = :bookId")
|
@Query("DELETE FROM historyentity WHERE bookId = :bookId")
|
||||||
suspend fun deleteBookHistory(bookId: Int)
|
suspend fun deleteHistoryForBook(bookId: Int): Int
|
||||||
|
|
||||||
@Delete
|
@Delete
|
||||||
suspend fun deleteHistory(history: List<HistoryEntity>)
|
suspend fun deleteHistory(history: HistoryEntity): Int
|
||||||
/* - - - - - - - - - - - - - - - - - - - - - - */
|
/* - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import ua.acclorite.book_story.data.local.dto.BookEntity
|
||||||
import ua.acclorite.book_story.domain.library.Book
|
import ua.acclorite.book_story.domain.library.Book
|
||||||
|
|
||||||
interface BookMapper {
|
interface BookMapper {
|
||||||
suspend fun toBookEntity(book: Book): BookEntity
|
fun toBookEntity(book: Book): BookEntity
|
||||||
|
fun toBook(bookEntity: BookEntity): Book
|
||||||
suspend fun toBook(bookEntity: BookEntity): Book
|
|
||||||
}
|
}
|
||||||
|
|
@ -14,7 +14,7 @@ import ua.acclorite.book_story.domain.library.Book
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class BookMapperImpl @Inject constructor() : BookMapper {
|
class BookMapperImpl @Inject constructor() : BookMapper {
|
||||||
override suspend fun toBookEntity(book: Book): BookEntity {
|
override fun toBookEntity(book: Book): BookEntity {
|
||||||
return BookEntity(
|
return BookEntity(
|
||||||
id = book.id,
|
id = book.id,
|
||||||
title = book.title,
|
title = book.title,
|
||||||
|
|
@ -29,7 +29,7 @@ class BookMapperImpl @Inject constructor() : BookMapper {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun toBook(bookEntity: BookEntity): Book {
|
override fun toBook(bookEntity: BookEntity): Book {
|
||||||
return Book(
|
return Book(
|
||||||
id = bookEntity.id,
|
id = bookEntity.id,
|
||||||
title = bookEntity.title,
|
title = bookEntity.title,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import ua.acclorite.book_story.data.local.dto.CategoryEntity
|
||||||
import ua.acclorite.book_story.domain.library.Category
|
import ua.acclorite.book_story.domain.library.Category
|
||||||
|
|
||||||
interface CategoryMapper {
|
interface CategoryMapper {
|
||||||
suspend fun toCategoryEntity(category: Category): CategoryEntity
|
fun toCategoryEntity(category: Category): CategoryEntity
|
||||||
|
fun toCategory(categoryEntity: CategoryEntity): Category
|
||||||
suspend fun toCategory(categoryEntity: CategoryEntity): Category
|
|
||||||
}
|
}
|
||||||
|
|
@ -11,7 +11,7 @@ import ua.acclorite.book_story.domain.library.Category
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class CategoryMapperImpl @Inject constructor() : CategoryMapper {
|
class CategoryMapperImpl @Inject constructor() : CategoryMapper {
|
||||||
override suspend fun toCategoryEntity(category: Category): CategoryEntity {
|
override fun toCategoryEntity(category: Category): CategoryEntity {
|
||||||
return CategoryEntity(
|
return CategoryEntity(
|
||||||
id = category.id,
|
id = category.id,
|
||||||
title = category.title,
|
title = category.title,
|
||||||
|
|
@ -19,7 +19,7 @@ class CategoryMapperImpl @Inject constructor() : CategoryMapper {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun toCategory(categoryEntity: CategoryEntity): Category {
|
override fun toCategory(categoryEntity: CategoryEntity): Category {
|
||||||
return Category(
|
return Category(
|
||||||
id = categoryEntity.id,
|
id = categoryEntity.id,
|
||||||
title = categoryEntity.title,
|
title = categoryEntity.title,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import ua.acclorite.book_story.data.local.dto.CategorySortEntity
|
||||||
import ua.acclorite.book_story.domain.library.CategorySort
|
import ua.acclorite.book_story.domain.library.CategorySort
|
||||||
|
|
||||||
interface CategorySortMapper {
|
interface CategorySortMapper {
|
||||||
suspend fun toCategorySortEntity(categorySort: CategorySort): CategorySortEntity
|
fun toCategorySortEntity(categorySort: CategorySort): CategorySortEntity
|
||||||
|
fun toCategorySort(categorySortEntity: CategorySortEntity): CategorySort
|
||||||
suspend fun toCategorySort(categorySortEntity: CategorySortEntity): CategorySort
|
|
||||||
}
|
}
|
||||||
|
|
@ -11,7 +11,7 @@ import ua.acclorite.book_story.domain.library.CategorySort
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class CategorySortSortMapperImpl @Inject constructor() : CategorySortMapper {
|
class CategorySortSortMapperImpl @Inject constructor() : CategorySortMapper {
|
||||||
override suspend fun toCategorySortEntity(categorySort: CategorySort): CategorySortEntity {
|
override fun toCategorySortEntity(categorySort: CategorySort): CategorySortEntity {
|
||||||
return CategorySortEntity(
|
return CategorySortEntity(
|
||||||
categoryId = categorySort.categoryId,
|
categoryId = categorySort.categoryId,
|
||||||
sortOrder = categorySort.sortOrder,
|
sortOrder = categorySort.sortOrder,
|
||||||
|
|
@ -19,7 +19,7 @@ class CategorySortSortMapperImpl @Inject constructor() : CategorySortMapper {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun toCategorySort(categorySortEntity: CategorySortEntity): CategorySort {
|
override fun toCategorySort(categorySortEntity: CategorySortEntity): CategorySort {
|
||||||
return CategorySort(
|
return CategorySort(
|
||||||
categoryId = categorySortEntity.categoryId,
|
categoryId = categorySortEntity.categoryId,
|
||||||
sortOrder = categorySortEntity.sortOrder,
|
sortOrder = categorySortEntity.sortOrder,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import ua.acclorite.book_story.data.local.dto.ColorPresetEntity
|
||||||
import ua.acclorite.book_story.domain.reader.ColorPreset
|
import ua.acclorite.book_story.domain.reader.ColorPreset
|
||||||
|
|
||||||
interface ColorPresetMapper {
|
interface ColorPresetMapper {
|
||||||
suspend fun toColorPresetEntity(colorPreset: ColorPreset, order: Int): ColorPresetEntity
|
fun toColorPresetEntity(colorPreset: ColorPreset, order: Int): ColorPresetEntity
|
||||||
|
fun toColorPreset(colorPresetEntity: ColorPresetEntity): ColorPreset
|
||||||
suspend fun toColorPreset(colorPresetEntity: ColorPresetEntity): ColorPreset
|
|
||||||
}
|
}
|
||||||
|
|
@ -12,7 +12,7 @@ import ua.acclorite.book_story.domain.reader.ColorPreset
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ColorPresetMapperImpl @Inject constructor() : ColorPresetMapper {
|
class ColorPresetMapperImpl @Inject constructor() : ColorPresetMapper {
|
||||||
override suspend fun toColorPresetEntity(
|
override fun toColorPresetEntity(
|
||||||
colorPreset: ColorPreset,
|
colorPreset: ColorPreset,
|
||||||
order: Int
|
order: Int
|
||||||
): ColorPresetEntity {
|
): ColorPresetEntity {
|
||||||
|
|
@ -27,7 +27,7 @@ class ColorPresetMapperImpl @Inject constructor() : ColorPresetMapper {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun toColorPreset(colorPresetEntity: ColorPresetEntity): ColorPreset {
|
override fun toColorPreset(colorPresetEntity: ColorPresetEntity): ColorPreset {
|
||||||
return ColorPreset(
|
return ColorPreset(
|
||||||
id = colorPresetEntity.id!!,
|
id = colorPresetEntity.id!!,
|
||||||
name = colorPresetEntity.name,
|
name = colorPresetEntity.name,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
* 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.mapper.file
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.data.model.file.CachedFile
|
||||||
|
import ua.acclorite.book_story.domain.file.File
|
||||||
|
|
||||||
|
interface FileMapper {
|
||||||
|
fun toCachedFile(file: File): CachedFile
|
||||||
|
fun toFile(cachedFile: CachedFile): File
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* 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.mapper.file
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import ua.acclorite.book_story.data.model.file.CachedFile
|
||||||
|
import ua.acclorite.book_story.data.model.file.CachedFileCompat
|
||||||
|
import ua.acclorite.book_story.domain.file.File
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class FileMapperImpl @Inject constructor(
|
||||||
|
private val application: Application
|
||||||
|
) : FileMapper {
|
||||||
|
override fun toCachedFile(file: File): CachedFile {
|
||||||
|
return CachedFileCompat.fromUri(
|
||||||
|
context = application,
|
||||||
|
uri = file.uri.toUri(),
|
||||||
|
builder = CachedFileCompat.build(
|
||||||
|
name = file.name,
|
||||||
|
path = file.path,
|
||||||
|
size = file.size,
|
||||||
|
lastModified = file.lastModified,
|
||||||
|
isDirectory = file.isDirectory
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toFile(cachedFile: CachedFile): File {
|
||||||
|
return File(
|
||||||
|
name = cachedFile.name,
|
||||||
|
uri = cachedFile.uri.toString(),
|
||||||
|
path = cachedFile.path,
|
||||||
|
size = cachedFile.size,
|
||||||
|
lastModified = cachedFile.lastModified,
|
||||||
|
isDirectory = cachedFile.isDirectory
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,399 +6,62 @@
|
||||||
|
|
||||||
package ua.acclorite.book_story.data.repository
|
package ua.acclorite.book_story.data.repository
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.graphics.BitmapFactory
|
|
||||||
import android.net.Uri
|
|
||||||
import android.provider.MediaStore
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.core.net.toUri
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import ua.acclorite.book_story.core.CoverImage
|
import ua.acclorite.book_story.core.CoverImage
|
||||||
import ua.acclorite.book_story.data.local.room.BookDao
|
import ua.acclorite.book_story.data.local.room.BookDao
|
||||||
import ua.acclorite.book_story.data.mapper.book.BookMapper
|
import ua.acclorite.book_story.data.mapper.book.BookMapper
|
||||||
import ua.acclorite.book_story.data.model.file.CachedFileCompat
|
|
||||||
import ua.acclorite.book_story.data.model.library.BookWithCover
|
|
||||||
import ua.acclorite.book_story.data.parser.FileParser
|
import ua.acclorite.book_story.data.parser.FileParser
|
||||||
import ua.acclorite.book_story.data.parser.TextParser
|
import ua.acclorite.book_story.data.parser.TextParser
|
||||||
import ua.acclorite.book_story.domain.library.Book
|
import ua.acclorite.book_story.domain.library.Book
|
||||||
import ua.acclorite.book_story.domain.reader.ReaderText
|
import ua.acclorite.book_story.domain.reader.ReaderText
|
||||||
import ua.acclorite.book_story.domain.repository.BookRepository
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
import java.io.BufferedOutputStream
|
import ua.acclorite.book_story.domain.service.FileProvider
|
||||||
import java.io.ByteArrayOutputStream
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.util.UUID
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
private const val GET_TEXT = "GET TEXT, REPO"
|
|
||||||
private const val GET_BOOKS = "GET BOOKS, REPO"
|
|
||||||
private const val GET_BOOKS_BY_ID = "GET BOOKS, REPO"
|
|
||||||
private const val INSERT_BOOK = "INSERT BOOK, REPO"
|
|
||||||
private const val UPDATE_BOOK = "UPDATE BOOK, REPO"
|
|
||||||
private const val DELETE_BOOKS = "DELETE BOOKS, REPO"
|
|
||||||
private const val CAN_RESET_COVER = "CAN RESET COVER, REPO"
|
|
||||||
private const val RESET_COVER = "RESET COVER, REPO"
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class BookRepositoryImpl @Inject constructor(
|
class BookRepositoryImpl @Inject constructor(
|
||||||
private val application: Application,
|
|
||||||
private val database: BookDao,
|
private val database: BookDao,
|
||||||
|
|
||||||
private val bookMapper: BookMapper,
|
private val bookMapper: BookMapper,
|
||||||
|
|
||||||
private val fileParser: FileParser,
|
private val fileParser: FileParser,
|
||||||
private val textParser: TextParser
|
private val textParser: TextParser,
|
||||||
|
private val fileProvider: FileProvider
|
||||||
) : BookRepository {
|
) : BookRepository {
|
||||||
|
|
||||||
/**
|
override suspend fun searchBooks(query: String): Result<List<Book>> = runCatching {
|
||||||
* Get all books matching [query] from database.
|
database.searchBooks(query).map { bookMapper.toBook(it) }
|
||||||
* Empty [query] equals to all books.
|
}
|
||||||
*/
|
|
||||||
override suspend fun getBooks(query: String): List<Book> {
|
|
||||||
Log.i(GET_BOOKS, "Searching for books with query: \"$query\".")
|
|
||||||
val books = database.searchBooks(query)
|
|
||||||
|
|
||||||
Log.i(GET_BOOKS, "Found ${books.size} books.")
|
override suspend fun getBook(bookId: Int): Result<Book> = runCatching {
|
||||||
return books.map { entity ->
|
database.findBookById(bookId).let {
|
||||||
val book = bookMapper.toBook(entity)
|
if (it == null) throw NoSuchElementException("Couldn't get book [$bookId].")
|
||||||
val lastHistory = database.getLatestHistoryForBook(
|
else bookMapper.toBook(it)
|
||||||
book.id
|
|
||||||
)
|
|
||||||
|
|
||||||
book.copy(
|
|
||||||
lastOpened = lastHistory?.time
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override suspend fun getText(bookId: Int): Result<List<ReaderText>> {
|
||||||
* Get all books that match given [ids].
|
return getBook(bookId)
|
||||||
*/
|
.mapCatching { fileProvider.getFileFromBook(it).getOrThrow() }
|
||||||
override suspend fun getBooksById(ids: List<Int>): List<Book> {
|
.mapCatching { textParser.parse(it) }
|
||||||
Log.i(GET_BOOKS_BY_ID, "Getting books with ids: $ids.")
|
}
|
||||||
val books = database.findBooksById(ids)
|
|
||||||
|
|
||||||
return books.map { entity ->
|
override suspend fun addBook(book: Book): Result<Unit> = runCatching {
|
||||||
val book = bookMapper.toBook(entity)
|
database.insertBook(bookMapper.toBookEntity(book))
|
||||||
val lastHistory = database.getLatestHistoryForBook(
|
}
|
||||||
book.id
|
|
||||||
)
|
|
||||||
|
|
||||||
book.copy(
|
override suspend fun updateBook(book: Book): Result<Unit> = runCatching {
|
||||||
lastOpened = lastHistory?.time
|
database.updateBook(bookMapper.toBookEntity(book)).also {
|
||||||
)
|
if (it == 0) throw Exception("Could not update book in database.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override suspend fun deleteBook(book: Book): Result<Unit> = runCatching {
|
||||||
* Loads text from the book. Already formatted.
|
database.deleteBook(bookMapper.toBookEntity(book)).also {
|
||||||
*/
|
if (it == 0) throw Exception("Could not delete book in database.")
|
||||||
override suspend fun getBookText(bookId: Int): List<ReaderText> {
|
|
||||||
if (bookId == -1) return emptyList()
|
|
||||||
|
|
||||||
val book = database.findBookById(bookId)
|
|
||||||
val cachedFile = CachedFileCompat.fromFullPath(
|
|
||||||
context = application,
|
|
||||||
path = book.filePath,
|
|
||||||
builder = CachedFileCompat.build(
|
|
||||||
name = book.filePath.substringAfterLast(File.separator),
|
|
||||||
path = book.filePath,
|
|
||||||
isDirectory = false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (cachedFile == null || !cachedFile.canAccess()) {
|
|
||||||
Log.e(GET_TEXT, "File [$bookId] does not exist")
|
|
||||||
return emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
val readerText = textParser.parse(cachedFile)
|
|
||||||
|
|
||||||
if (
|
|
||||||
readerText.filterIsInstance<ReaderText.Text>().isEmpty() ||
|
|
||||||
readerText.filterIsInstance<ReaderText.Chapter>().isEmpty()
|
|
||||||
) {
|
|
||||||
Log.e(GET_TEXT, "Could not load text from [$bookId].")
|
|
||||||
return emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.i(GET_TEXT, "Successfully loaded text of [$bookId] with markdown.")
|
|
||||||
return readerText
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts book in database.
|
|
||||||
* Creates covers folder, which contains cover.
|
|
||||||
*/
|
|
||||||
override suspend fun insertBook(
|
|
||||||
bookWithCover: BookWithCover
|
|
||||||
) {
|
|
||||||
Log.i(INSERT_BOOK, "Inserting ${bookWithCover.book.title}.")
|
|
||||||
|
|
||||||
val filesDir = application.filesDir
|
|
||||||
val coversDir = File(filesDir, "covers")
|
|
||||||
|
|
||||||
if (!coversDir.exists()) {
|
|
||||||
Log.i(INSERT_BOOK, "Created covers folder.")
|
|
||||||
coversDir.mkdirs()
|
|
||||||
}
|
|
||||||
|
|
||||||
var coverUri = ""
|
|
||||||
|
|
||||||
if (bookWithCover.coverImage != null) {
|
|
||||||
try {
|
|
||||||
coverUri = "${UUID.randomUUID()}.webp"
|
|
||||||
val cover = File(coversDir, coverUri)
|
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
BufferedOutputStream(FileOutputStream(cover)).use { output ->
|
|
||||||
bookWithCover.coverImage
|
|
||||||
.copy(Bitmap.Config.RGB_565, false)
|
|
||||||
.compress(Bitmap.CompressFormat.WEBP, 20, output)
|
|
||||||
.let { success ->
|
|
||||||
if (success) return@let
|
|
||||||
throw Exception("Couldn't save cover image")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(INSERT_BOOK, "Could not save cover.")
|
|
||||||
coverUri = ""
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val updatedBook = bookWithCover.book.copy(
|
override suspend fun getDefaultCover(book: Book): Result<CoverImage?> = runCatching {
|
||||||
coverImage = if (coverUri.isNotBlank()) {
|
return fileProvider.getFileFromBook(book)
|
||||||
Uri.fromFile(File("$coversDir/$coverUri"))
|
.mapCatching { fileParser.parse(it)?.coverImage }
|
||||||
} else null
|
|
||||||
)
|
|
||||||
|
|
||||||
val bookToInsert = bookMapper.toBookEntity(updatedBook)
|
|
||||||
database.insertBook(bookToInsert)
|
|
||||||
Log.i(INSERT_BOOK, "Successfully inserted book.")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update book without cover image.
|
|
||||||
*/
|
|
||||||
override suspend fun updateBook(book: Book) {
|
|
||||||
val entity = database.findBookById(book.id)
|
|
||||||
database.updateBooks(
|
|
||||||
listOf(
|
|
||||||
bookMapper.toBookEntity(
|
|
||||||
book.copy(
|
|
||||||
coverImage = entity.image?.toUri()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update cover image of the book. Deletes old cover and replaces with new.
|
|
||||||
*/
|
|
||||||
override suspend fun updateCoverImageOfBook(
|
|
||||||
bookWithOldCover: Book,
|
|
||||||
newCoverImage: CoverImage?
|
|
||||||
) {
|
|
||||||
Log.i(UPDATE_BOOK, "Updating cover image: ${bookWithOldCover.title}.")
|
|
||||||
|
|
||||||
val book = database.findBookById(bookWithOldCover.id)
|
|
||||||
var uri: String? = null
|
|
||||||
|
|
||||||
val filesDir = application.filesDir
|
|
||||||
val coversDir = File(filesDir, "covers")
|
|
||||||
|
|
||||||
if (!coversDir.exists()) {
|
|
||||||
Log.i(UPDATE_BOOK, "Created covers folder.")
|
|
||||||
coversDir.mkdirs()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newCoverImage != null) {
|
|
||||||
try {
|
|
||||||
uri = "${UUID.randomUUID()}.webp"
|
|
||||||
val cover = File(coversDir, uri)
|
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
BufferedOutputStream(FileOutputStream(cover)).use { output ->
|
|
||||||
newCoverImage
|
|
||||||
.copy(Bitmap.Config.RGB_565, false)
|
|
||||||
.compress(Bitmap.CompressFormat.WEBP, 20, output)
|
|
||||||
.let { success ->
|
|
||||||
if (success) return@let
|
|
||||||
throw Exception("Couldn't save cover image")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(UPDATE_BOOK, "Could not save new cover.")
|
|
||||||
e.printStackTrace()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (book.image != null) {
|
|
||||||
try {
|
|
||||||
val fileToDelete = File(
|
|
||||||
"$coversDir${File.separator}${book.image.substringAfterLast(File.separator)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if (fileToDelete.exists()) {
|
|
||||||
fileToDelete.delete()
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(UPDATE_BOOK, "Could not delete old cover.")
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val newCoverImageUri = if (uri != null) {
|
|
||||||
Uri.fromFile(File("$coversDir/$uri"))
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
val bookWithNewCover = bookWithOldCover.copy(
|
|
||||||
coverImage = newCoverImageUri
|
|
||||||
)
|
|
||||||
|
|
||||||
database.updateBooks(
|
|
||||||
listOf(
|
|
||||||
bookMapper.toBookEntity(
|
|
||||||
bookWithNewCover
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
Log.i(UPDATE_BOOK, "Successfully updated cover image.")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete books.
|
|
||||||
* Also deletes cover image and text from internal storage.
|
|
||||||
*/
|
|
||||||
override suspend fun deleteBooks(books: List<Book>) {
|
|
||||||
Log.i(DELETE_BOOKS, "Deleting books.")
|
|
||||||
|
|
||||||
val filesDir = application.filesDir
|
|
||||||
val coversDir = File(filesDir, "covers")
|
|
||||||
|
|
||||||
if (!coversDir.exists()) {
|
|
||||||
Log.i(DELETE_BOOKS, "Created covers folder.")
|
|
||||||
coversDir.mkdirs()
|
|
||||||
}
|
|
||||||
|
|
||||||
database.deleteBooks(
|
|
||||||
books.map {
|
|
||||||
val book = database.findBookById(it.id)
|
|
||||||
|
|
||||||
if (book.image != null) {
|
|
||||||
try {
|
|
||||||
val fileToDelete = File(
|
|
||||||
"$coversDir${File.separator}${book.image.substringAfterLast(File.separator)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if (fileToDelete.exists()) {
|
|
||||||
fileToDelete.delete()
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(DELETE_BOOKS, "Could not delete cover image.")
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
book
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
Log.i(DELETE_BOOKS, "Successfully deleted books.")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Whether can reset cover image (restore default).
|
|
||||||
*/
|
|
||||||
override suspend fun canResetCover(bookId: Int): Boolean {
|
|
||||||
val book = database.findBookById(bookId)
|
|
||||||
|
|
||||||
val cachedFile = CachedFileCompat.fromFullPath(
|
|
||||||
application,
|
|
||||||
book.filePath,
|
|
||||||
builder = CachedFileCompat.build(
|
|
||||||
name = book.filePath.substringAfterLast(File.separator),
|
|
||||||
path = book.filePath,
|
|
||||||
isDirectory = false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (cachedFile == null || !cachedFile.canAccess()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
val defaultCoverUncompressed = fileParser.parse(cachedFile)?.coverImage
|
|
||||||
?: return false
|
|
||||||
|
|
||||||
if (book.image == null) {
|
|
||||||
Log.i(CAN_RESET_COVER, "Can reset cover image. (current is null)")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
val stream = ByteArrayOutputStream()
|
|
||||||
defaultCoverUncompressed.copy(Bitmap.Config.RGB_565, false).compress(
|
|
||||||
Bitmap.CompressFormat.WEBP,
|
|
||||||
20,
|
|
||||||
stream
|
|
||||||
)
|
|
||||||
val byteArray = stream.toByteArray()
|
|
||||||
val defaultCover = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
|
|
||||||
|
|
||||||
val currentCover = try {
|
|
||||||
MediaStore.Images.Media.getBitmap(
|
|
||||||
application.contentResolver,
|
|
||||||
book.image.toUri()
|
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.i(CAN_RESET_COVER, "Can reset cover image. (could not get current)")
|
|
||||||
e.printStackTrace()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return !defaultCover.sameAs(currentCover)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset cover image to default.
|
|
||||||
* If there is no default cover, returns false.
|
|
||||||
*/
|
|
||||||
override suspend fun resetCoverImage(bookId: Int): Boolean {
|
|
||||||
if (!canResetCover(bookId)) {
|
|
||||||
Log.w(RESET_COVER, "Cannot reset cover image.")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
val book = database.findBookById(bookId)
|
|
||||||
val cachedFile = CachedFileCompat.fromFullPath(
|
|
||||||
application,
|
|
||||||
book.filePath,
|
|
||||||
builder = CachedFileCompat.build(
|
|
||||||
name = book.filePath.substringAfterLast(File.separator),
|
|
||||||
path = book.filePath,
|
|
||||||
isDirectory = false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (cachedFile == null || !cachedFile.canAccess()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
val defaultCover = fileParser.parse(cachedFile)?.coverImage
|
|
||||||
?: return false
|
|
||||||
updateCoverImageOfBook(bookMapper.toBook(book), defaultCover)
|
|
||||||
|
|
||||||
Log.i(RESET_COVER, "Successfully reset cover image.")
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -22,7 +22,7 @@ class CategoryRepositoryImpl @Inject constructor(
|
||||||
private val categorySortMapper: CategorySortMapper,
|
private val categorySortMapper: CategorySortMapper,
|
||||||
) : CategoryRepository {
|
) : CategoryRepository {
|
||||||
|
|
||||||
override suspend fun insertCategory(category: Category) {
|
override suspend fun addCategory(category: Category): Result<Unit> = runCatching {
|
||||||
database.insertCategory(
|
database.insertCategory(
|
||||||
categoryMapper.toCategoryEntity(
|
categoryMapper.toCategoryEntity(
|
||||||
category.copy(
|
category.copy(
|
||||||
|
|
@ -32,39 +32,41 @@ class CategoryRepositoryImpl @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getCategories(): List<Category> {
|
override suspend fun getCategories(): Result<List<Category>> = runCatching {
|
||||||
return database.getCategories().map { categoryMapper.toCategory(it) }.sortedBy { it.order }
|
database.getCategories().map { categoryMapper.toCategory(it) }.sortedBy { it.order }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun updateCategoryTitle(id: Int, title: String) {
|
override suspend fun updateCategory(category: Category): Result<Unit> = runCatching {
|
||||||
database.updateCategoryTitle(
|
database.updateCategoryTitle(
|
||||||
id = id,
|
id = category.id,
|
||||||
title = title
|
title = category.title
|
||||||
)
|
)
|
||||||
updateCategoriesOrder(getCategories())
|
updateOrder(getCategories().getOrThrow())
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun updateCategoriesOrder(categories: List<Category>) {
|
override suspend fun updateOrder(categories: List<Category>): Result<Unit> = runCatching {
|
||||||
categories.mapIndexed { index, category ->
|
categories.forEachIndexed { index, category ->
|
||||||
database.updateCategoryOrder(category.id, index)
|
database.updateCategoryOrder(category.id, index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun deleteCategory(category: Category) {
|
override suspend fun deleteCategory(category: Category): Result<Unit> = runCatching {
|
||||||
database.deleteCategory(categoryMapper.toCategoryEntity(category))
|
database.deleteCategory(categoryMapper.toCategoryEntity(category))
|
||||||
database.deleteCategorySortEntity(category.id)
|
database.deleteCategorySortEntity(category.id)
|
||||||
|
|
||||||
updateCategoriesOrder(getCategories())
|
updateOrder(getCategories().getOrThrow())
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun updateCategorySortEntity(categorySort: CategorySort) {
|
override suspend fun updateCategorySorting(
|
||||||
|
categorySort: CategorySort
|
||||||
|
): Result<Unit> = runCatching {
|
||||||
database.updateCategorySort(
|
database.updateCategorySort(
|
||||||
categorySortMapper.toCategorySortEntity(categorySort)
|
categorySortMapper.toCategorySortEntity(categorySort)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getCategorySortEntities(): List<CategorySort> {
|
override suspend fun getCategorySorting(): Result<List<CategorySort>> = runCatching {
|
||||||
return database.getCategorySortEntities().map {
|
database.getCategorySortEntities().map {
|
||||||
categorySortMapper.toCategorySort(it)
|
categorySortMapper.toCategorySort(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,64 +20,51 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
class ColorPresetRepositoryImpl @Inject constructor(
|
class ColorPresetRepositoryImpl @Inject constructor(
|
||||||
private val database: BookDao,
|
private val database: BookDao,
|
||||||
|
|
||||||
private val colorPresetMapper: ColorPresetMapper
|
private val colorPresetMapper: ColorPresetMapper
|
||||||
) : ColorPresetRepository {
|
) : ColorPresetRepository {
|
||||||
|
|
||||||
/**
|
override suspend fun getColorPresets(): Result<List<ColorPreset>> = runCatching {
|
||||||
* Update color preset.
|
database.getColorPresets()
|
||||||
*/
|
.sortedBy { it.order }
|
||||||
override suspend fun updateColorPreset(colorPreset: ColorPreset) {
|
.map { colorPresetMapper.toColorPreset(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateColorPreset(
|
||||||
|
colorPreset: ColorPreset
|
||||||
|
): Result<Unit> = runCatching {
|
||||||
database.updateColorPreset(
|
database.updateColorPreset(
|
||||||
colorPresetMapper.toColorPresetEntity(
|
colorPresetMapper.toColorPresetEntity(
|
||||||
colorPreset,
|
colorPreset = colorPreset,
|
||||||
if (colorPreset.id != -1) database.getColorPresetOrder(colorPreset.id)
|
order = if (colorPreset.id != -1) database.getColorPresetOrder(colorPreset.id)
|
||||||
else database.getColorPresetsSize()
|
else database.getColorPresetsSize()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override suspend fun selectColorPreset(
|
||||||
* Select color preset. Only one can be selected at time.
|
colorPreset: ColorPreset
|
||||||
*/
|
): Result<Unit> = runCatching {
|
||||||
override suspend fun selectColorPreset(colorPreset: ColorPreset) {
|
|
||||||
database.getColorPresets().map {
|
database.getColorPresets().map {
|
||||||
it.copy(
|
it.copy(isSelected = it.id == colorPreset.id)
|
||||||
isSelected = it.id == colorPreset.id
|
|
||||||
)
|
|
||||||
}.forEach {
|
}.forEach {
|
||||||
database.updateColorPreset(it)
|
database.updateColorPreset(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override suspend fun reorderColorPresets(
|
||||||
* Get all color presets.
|
colorPresets: List<ColorPreset>
|
||||||
* Sorted by order (either manual or newest ones at the end).
|
): Result<Unit> = runCatching {
|
||||||
*/
|
|
||||||
override suspend fun getColorPresets(): List<ColorPreset> {
|
|
||||||
return database.getColorPresets()
|
|
||||||
.sortedBy { it.order }
|
|
||||||
.map { colorPresetMapper.toColorPreset(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reorder color presets.
|
|
||||||
* Changes the order of the color presets.
|
|
||||||
*/
|
|
||||||
override suspend fun reorderColorPresets(orderedColorPresets: List<ColorPreset>) {
|
|
||||||
database.deleteColorPresets()
|
database.deleteColorPresets()
|
||||||
|
colorPresets.forEachIndexed { index, colorPreset ->
|
||||||
orderedColorPresets.forEachIndexed { index, colorPreset ->
|
|
||||||
database.updateColorPreset(
|
database.updateColorPreset(
|
||||||
colorPresetMapper.toColorPresetEntity(colorPreset, order = index)
|
colorPresetMapper.toColorPresetEntity(colorPreset, order = index)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override suspend fun deleteColorPreset(
|
||||||
* Delete color preset.
|
colorPreset: ColorPreset
|
||||||
*/
|
): Result<Unit> = runCatching {
|
||||||
override suspend fun deleteColorPreset(colorPreset: ColorPreset) {
|
|
||||||
database.deleteColorPreset(
|
database.deleteColorPreset(
|
||||||
colorPresetMapper.toColorPresetEntity(
|
colorPresetMapper.toColorPresetEntity(
|
||||||
colorPreset, -1
|
colorPreset, -1
|
||||||
|
|
|
||||||
|
|
@ -6,22 +6,12 @@
|
||||||
|
|
||||||
package ua.acclorite.book_story.data.repository
|
package ua.acclorite.book_story.data.repository
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.datastore.preferences.core.Preferences
|
import androidx.datastore.preferences.core.Preferences
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.async
|
|
||||||
import kotlinx.coroutines.awaitAll
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import ua.acclorite.book_story.data.local.data_store.DataStore
|
import ua.acclorite.book_story.data.local.data_store.DataStore
|
||||||
import ua.acclorite.book_story.domain.repository.DataStoreRepository
|
import ua.acclorite.book_story.domain.repository.DataStoreRepository
|
||||||
import ua.acclorite.book_story.presentation.main.MainState
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
private const val GET_ALL_SETTINGS = "GET SETTINGS, REPO"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data Store repository.
|
* Data Store repository.
|
||||||
* Manages all [DataStore] related work.
|
* Manages all [DataStore] related work.
|
||||||
|
|
@ -31,42 +21,20 @@ class DataStoreRepositoryImpl @Inject constructor(
|
||||||
private val dataStore: DataStore
|
private val dataStore: DataStore
|
||||||
) : DataStoreRepository {
|
) : DataStoreRepository {
|
||||||
|
|
||||||
/**
|
override suspend fun <T> putPreference(
|
||||||
* Puts DataStore constant to [DataStore].
|
key: Preferences.Key<T>,
|
||||||
*/
|
value: T
|
||||||
override suspend fun <T> putDataToDataStore(key: Preferences.Key<T>, value: T) {
|
): Result<Unit> = runCatching {
|
||||||
dataStore.putData(key, value)
|
dataStore.putData(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override suspend fun <T> getPreference(
|
||||||
* Gets all settings from DataStore and returns [MainState].
|
key: Preferences.Key<T>
|
||||||
*/
|
): Result<T> = runCatching {
|
||||||
override suspend fun getAllSettings(): MainState {
|
dataStore.getNullableData(key) ?: throw NoSuchElementException("Could not get preference.")
|
||||||
Log.i(GET_ALL_SETTINGS, "Getting all settings.")
|
|
||||||
val result = CompletableDeferred<MainState>()
|
|
||||||
|
|
||||||
withContext(Dispatchers.Default) {
|
|
||||||
val keys = dataStore.getAllData()
|
|
||||||
val data = ConcurrentHashMap<String, Any>()
|
|
||||||
|
|
||||||
Log.i(GET_ALL_SETTINGS, "Got ${keys?.size} settings keys.")
|
|
||||||
|
|
||||||
val jobs = keys?.map { key ->
|
|
||||||
async {
|
|
||||||
val nullableData = dataStore.getNullableData(key)
|
|
||||||
|
|
||||||
if (nullableData == null) {
|
|
||||||
data.remove(key.name)
|
|
||||||
} else {
|
|
||||||
data[key.name] = nullableData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
jobs?.awaitAll()
|
|
||||||
|
|
||||||
result.complete(MainState.initialize(data))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.await()
|
override suspend fun getAllPreferences(): Result<Set<Preferences.Key<*>>> = runCatching {
|
||||||
|
dataStore.getAllData() ?: throw NoSuchElementException("Could not get all preferences.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6,165 +6,68 @@
|
||||||
|
|
||||||
package ua.acclorite.book_story.data.repository
|
package ua.acclorite.book_story.data.repository
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.util.Log
|
|
||||||
import ua.acclorite.book_story.R
|
|
||||||
import ua.acclorite.book_story.core.ui.UIText
|
|
||||||
import ua.acclorite.book_story.data.local.room.BookDao
|
import ua.acclorite.book_story.data.local.room.BookDao
|
||||||
import ua.acclorite.book_story.data.model.common.NullableBook
|
import ua.acclorite.book_story.data.mapper.file.FileMapper
|
||||||
import ua.acclorite.book_story.data.model.common.NullableBook.NotNull
|
|
||||||
import ua.acclorite.book_story.data.model.common.NullableBook.Null
|
|
||||||
import ua.acclorite.book_story.data.model.file.CachedFile
|
import ua.acclorite.book_story.data.model.file.CachedFile
|
||||||
import ua.acclorite.book_story.data.model.file.CachedFileCompat
|
import ua.acclorite.book_story.data.model.library.BookWithCover
|
||||||
import ua.acclorite.book_story.data.parser.FileParser
|
import ua.acclorite.book_story.data.parser.FileParser
|
||||||
import ua.acclorite.book_story.domain.file.File
|
import ua.acclorite.book_story.domain.file.File
|
||||||
import ua.acclorite.book_story.domain.repository.FileSystemRepository
|
import ua.acclorite.book_story.domain.repository.FileSystemRepository
|
||||||
|
import ua.acclorite.book_story.domain.service.FileProvider
|
||||||
import ua.acclorite.book_story.ui.common.constants.provideExtensions
|
import ua.acclorite.book_story.ui.common.constants.provideExtensions
|
||||||
import java.util.UUID
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
private const val GET_BOOK_FROM_FILE = "BOOK FROM FILE, REPO"
|
|
||||||
private const val GET_FILES = "FILES, REPO"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* File System repository.
|
* File System repository.
|
||||||
* Manages all File System related work.
|
* Manages all File System related work.
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
class FileSystemRepositoryImpl @Inject constructor(
|
class FileSystemRepositoryImpl @Inject constructor(
|
||||||
private val application: Application,
|
|
||||||
private val database: BookDao,
|
private val database: BookDao,
|
||||||
private val fileParser: FileParser
|
private val fileMapper: FileMapper,
|
||||||
|
private val fileParser: FileParser,
|
||||||
|
private val fileProvider: FileProvider
|
||||||
) : FileSystemRepository {
|
) : FileSystemRepository {
|
||||||
|
|
||||||
/**
|
override suspend fun searchFiles(query: String): Result<List<File>> {
|
||||||
* Gets all matching files from device.
|
return fileProvider.getStorageFiles().mapCatching { storages ->
|
||||||
* Filters by [query] and sorts out not supported file formats and already added files.
|
val existingFiles = database.searchBooks("").map { it.filePath }
|
||||||
*/
|
|
||||||
override suspend fun getFiles(query: String): List<File> {
|
|
||||||
Log.i(GET_FILES, "Getting files from device, query: \"$query\".")
|
|
||||||
|
|
||||||
val existingPaths = database
|
storages.map { storage ->
|
||||||
.searchBooks("")
|
storage.getFilesFromStorage(
|
||||||
.map { it.filePath }
|
query = query,
|
||||||
val supportedExtensions = provideExtensions()
|
existingFiles = existingFiles
|
||||||
|
)
|
||||||
/**
|
}.flatten()
|
||||||
* Verify that [CachedFile] is valid and can be shown correctly.
|
|
||||||
*/
|
|
||||||
fun CachedFile.isValid(): Boolean {
|
|
||||||
// First: Ensuring supported extension
|
|
||||||
supportedExtensions.any { ext ->
|
|
||||||
name.endsWith(ext, ignoreCase = true)
|
|
||||||
}.let { if (!it) return false }
|
|
||||||
|
|
||||||
// Second: Ensuring query to match
|
|
||||||
if (query.isNotBlank()) {
|
|
||||||
name.contains(query.trim(), ignoreCase = true).let {
|
|
||||||
if (!it) return false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Third: Ensuring that a file is not added already
|
private fun CachedFile.isValid(
|
||||||
existingPaths.none { existingPath ->
|
query: String,
|
||||||
existingPath.equals(path, ignoreCase = true)
|
existingFiles: List<String>
|
||||||
}.let { if (!it) return false }
|
): Boolean {
|
||||||
|
if (provideExtensions().none { name.endsWith(it, ignoreCase = true) }) return false
|
||||||
|
if (query.isNotBlank() && !name.contains(query.trim(), ignoreCase = true)) return false
|
||||||
|
if (existingFiles.any { it.equals(path, ignoreCase = true) }) return false
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun CachedFile.getSelectableFilesFromStorage(): List<File> {
|
private fun CachedFile.getFilesFromStorage(
|
||||||
|
query: String,
|
||||||
|
existingFiles: List<String>
|
||||||
|
): List<File> {
|
||||||
val files = mutableListOf<File>()
|
val files = mutableListOf<File>()
|
||||||
|
walk { cachedFile ->
|
||||||
walk { file ->
|
if (cachedFile.isValid(query, existingFiles)) {
|
||||||
if (!file.isValid()) return@walk
|
files.add(fileMapper.toFile(cachedFile))
|
||||||
|
}
|
||||||
files.add(
|
|
||||||
File(
|
|
||||||
name = file.name,
|
|
||||||
uri = file.uri,
|
|
||||||
path = file.path,
|
|
||||||
size = file.size,
|
|
||||||
lastModified = file.lastModified,
|
|
||||||
isDirectory = file.isDirectory
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return files
|
return files
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAllStorages(): List<CachedFile> {
|
override suspend fun getBookFromFile(file: File): Result<BookWithCover> = runCatching {
|
||||||
return application.contentResolver.persistedUriPermissions.mapNotNull { permission ->
|
fileParser.parse(fileMapper.toCachedFile(file))
|
||||||
val storage = CachedFileCompat.fromUri(
|
?: throw Exception("Could not parse ${file.name}.")
|
||||||
application,
|
|
||||||
permission.uri,
|
|
||||||
builder = CachedFileCompat.build(
|
|
||||||
name = UUID.randomUUID().toString(),
|
|
||||||
size = 0,
|
|
||||||
lastModified = 0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if (!storage.isDirectory) return@mapNotNull null
|
|
||||||
|
|
||||||
storage
|
|
||||||
}.let { storages ->
|
|
||||||
storages.filter { storage ->
|
|
||||||
storages.none {
|
|
||||||
it.path != storage.path && storage.path.startsWith(
|
|
||||||
it.path,
|
|
||||||
ignoreCase = true
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return try {
|
|
||||||
val storages = getAllStorages()
|
|
||||||
val files = mutableListOf<File>()
|
|
||||||
|
|
||||||
for (storage in storages) {
|
|
||||||
files.addAll(storage.getSelectableFilesFromStorage())
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.i(GET_FILES, "Successfully got all matching files.")
|
|
||||||
files
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
Log.e(GET_FILES, "Couldn't get all matching files.")
|
|
||||||
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets book from given file. If error happened, returns [NullableBook.Null].
|
|
||||||
*/
|
|
||||||
override suspend fun getBookFromFile(file: File): NullableBook {
|
|
||||||
val cachedFile = CachedFileCompat.fromUri(
|
|
||||||
context = application,
|
|
||||||
uri = file.uri,
|
|
||||||
builder = CachedFileCompat.build(
|
|
||||||
name = file.name,
|
|
||||||
path = file.path,
|
|
||||||
size = file.size,
|
|
||||||
lastModified = file.lastModified,
|
|
||||||
isDirectory = file.isDirectory
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
val parsedBook = fileParser.parse(cachedFile)
|
|
||||||
if (parsedBook == null) {
|
|
||||||
Log.e(GET_BOOK_FROM_FILE, "Parsed file(${cachedFile.name}) is null.")
|
|
||||||
return Null(
|
|
||||||
cachedFile.name,
|
|
||||||
UIText.StringResource(R.string.error_something_went_wrong)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.i(GET_BOOK_FROM_FILE, "Successfully got book from file.")
|
|
||||||
return NotNull(bookWithCover = parsedBook)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -13,73 +13,42 @@ import ua.acclorite.book_story.domain.repository.HistoryRepository
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
/**
|
|
||||||
* History repository.
|
|
||||||
* Manages all [History] related work.
|
|
||||||
*/
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class HistoryRepositoryImpl @Inject constructor(
|
class HistoryRepositoryImpl @Inject constructor(
|
||||||
private val database: BookDao,
|
private val database: BookDao,
|
||||||
|
private val historyMapper: HistoryMapper
|
||||||
private val historyMapper: HistoryMapper,
|
|
||||||
) : HistoryRepository {
|
) : HistoryRepository {
|
||||||
|
|
||||||
/**
|
override suspend fun getHistoryForBook(bookId: Int): Result<History> = runCatching {
|
||||||
* Insert history in database.
|
database.getHistoryForBook(bookId).let {
|
||||||
*/
|
if (it == null) throw NoSuchElementException("Could not get history from [$bookId].")
|
||||||
override suspend fun insertHistory(history: History) {
|
else historyMapper.toHistory(it)
|
||||||
database.insertHistory(
|
|
||||||
listOf(
|
|
||||||
historyMapper.toHistoryEntity(
|
|
||||||
history
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all history from database.
|
|
||||||
*/
|
|
||||||
override suspend fun getHistory(): List<History> {
|
|
||||||
return database.getHistory().map {
|
|
||||||
historyMapper.toHistory(
|
|
||||||
it
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override suspend fun addHistory(history: History): Result<Unit> = runCatching {
|
||||||
* Get latest history of the matching [bookId].
|
database.insertHistory(historyMapper.toHistoryEntity(history))
|
||||||
*/
|
|
||||||
override suspend fun getLatestBookHistory(bookId: Int): History? {
|
|
||||||
val history = database.getLatestHistoryForBook(bookId)
|
|
||||||
return history?.let { historyMapper.toHistory(it) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override suspend fun getHistory(): Result<List<History>> = runCatching {
|
||||||
* Delete whole history.
|
database.getHistory().map { historyMapper.toHistory(it) }
|
||||||
*/
|
|
||||||
override suspend fun deleteWholeHistory() {
|
|
||||||
database.deleteWholeHistory()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override suspend fun deleteWholeHistory(): Result<Unit> = runCatching {
|
||||||
* Delete all history of the matching [bookId].
|
database.deleteWholeHistory().also {
|
||||||
*/
|
if (it == 0) throw Exception("Could not delete whole history in database.")
|
||||||
override suspend fun deleteBookHistory(bookId: Int) {
|
}
|
||||||
database.deleteBookHistory(bookId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override suspend fun deleteHistoryForBook(bookId: Int): Result<Unit> = runCatching {
|
||||||
* Delete specific history item.
|
database.deleteHistoryForBook(bookId = bookId).also {
|
||||||
*/
|
if (it == 0) throw Exception("Could not delete history for book [$bookId] in database.")
|
||||||
override suspend fun deleteHistory(history: History) {
|
}
|
||||||
database.deleteHistory(
|
}
|
||||||
listOf(
|
|
||||||
historyMapper.toHistoryEntity(
|
override suspend fun deleteHistory(history: History): Result<Unit> = runCatching {
|
||||||
history
|
database.deleteHistory(historyMapper.toHistoryEntity(history)).also {
|
||||||
)
|
if (it == 0) throw Exception("Could not delete history in database.")
|
||||||
)
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8,45 +8,27 @@ package ua.acclorite.book_story.data.repository
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import androidx.core.net.toUri
|
||||||
import android.util.Log
|
|
||||||
import ua.acclorite.book_story.domain.repository.PermissionRepository
|
import ua.acclorite.book_story.domain.repository.PermissionRepository
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
private const val GRANT_URI = "GRANT URI PERM, REPO"
|
|
||||||
private const val RELEASE_URI = "RELEASE URI PERM, REPO"
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class PermissionRepositoryImpl @Inject constructor(
|
class PermissionRepositoryImpl @Inject constructor(
|
||||||
private val application: Application
|
private val application: Application
|
||||||
) : PermissionRepository {
|
) : PermissionRepository {
|
||||||
|
|
||||||
override suspend fun grantPersistableUriPermission(uri: Uri) {
|
override suspend fun grantPersistableUriPermission(uri: String): Result<Unit> = runCatching {
|
||||||
Log.i(GRANT_URI, "Granting persistable uri permission to \"${uri.path}\" URI.")
|
|
||||||
|
|
||||||
try {
|
|
||||||
application.contentResolver.takePersistableUriPermission(
|
application.contentResolver.takePersistableUriPermission(
|
||||||
uri,
|
uri.toUri(),
|
||||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
Log.e(GRANT_URI, "Could not grant URI permission.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun releasePersistableUriPermission(uri: Uri) {
|
override suspend fun releasePersistableUriPermission(uri: String): Result<Unit> = runCatching {
|
||||||
Log.i(RELEASE_URI, "Releasing persistable uri permission from \"${uri.path}\" URI.")
|
|
||||||
|
|
||||||
try {
|
|
||||||
application.contentResolver.releasePersistableUriPermission(
|
application.contentResolver.releasePersistableUriPermission(
|
||||||
uri,
|
uri.toUri(),
|
||||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
Log.w(RELEASE_URI, "No granted URI permission found.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* 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.service
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.core.net.toFile
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import ua.acclorite.book_story.core.CoverImage
|
||||||
|
import ua.acclorite.book_story.domain.service.CoverImageHandler
|
||||||
|
import java.io.BufferedOutputStream
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.util.UUID
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class CoverImageHandlerImpl @Inject constructor(
|
||||||
|
application: Application
|
||||||
|
) : CoverImageHandler {
|
||||||
|
|
||||||
|
private val filesDir: File = application.filesDir
|
||||||
|
private val coversDir = File(filesDir, "covers")
|
||||||
|
|
||||||
|
override suspend fun saveCover(coverImage: CoverImage): Result<File> = runCatching {
|
||||||
|
if (!coversDir.exists()) {
|
||||||
|
coversDir.mkdirs()
|
||||||
|
}
|
||||||
|
|
||||||
|
val coverUri = "${UUID.randomUUID()}.webp"
|
||||||
|
val cover = File(coversDir, coverUri)
|
||||||
|
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
BufferedOutputStream(FileOutputStream(cover)).use { output ->
|
||||||
|
coverImage
|
||||||
|
.copy(Bitmap.Config.RGB_565, false)
|
||||||
|
.compress(Bitmap.CompressFormat.WEBP, 20, output)
|
||||||
|
.let { success ->
|
||||||
|
if (success) return@let
|
||||||
|
throw Exception("Couldn't save cover image.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cover
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteCover(coverImage: Uri): Result<Unit> = runCatching {
|
||||||
|
coverImage.toFile().apply {
|
||||||
|
if (exists() && !delete()) throw Exception("Couldn't delete cover image.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun compressCover(coverImage: CoverImage): Result<CoverImage> = runCatching {
|
||||||
|
val stream = ByteArrayOutputStream()
|
||||||
|
coverImage.copy(Bitmap.Config.RGB_565, false)
|
||||||
|
.compress(Bitmap.CompressFormat.WEBP, 20, stream)
|
||||||
|
val byteArray = stream.toByteArray()
|
||||||
|
BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* 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.service
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import ua.acclorite.book_story.data.model.file.CachedFile
|
||||||
|
import ua.acclorite.book_story.data.model.file.CachedFileCompat
|
||||||
|
import ua.acclorite.book_story.domain.library.Book
|
||||||
|
import ua.acclorite.book_story.domain.service.FileProvider
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class FileProviderImpl @Inject constructor(
|
||||||
|
private val application: Application
|
||||||
|
) : FileProvider {
|
||||||
|
|
||||||
|
override fun getFileFromBook(book: Book): Result<CachedFile> = runCatching {
|
||||||
|
CachedFileCompat.fromFullPath(
|
||||||
|
context = application,
|
||||||
|
path = book.filePath,
|
||||||
|
builder = CachedFileCompat.build(
|
||||||
|
name = book.filePath.substringAfterLast(java.io.File.separator),
|
||||||
|
path = book.filePath,
|
||||||
|
isDirectory = false
|
||||||
|
)
|
||||||
|
).let { file ->
|
||||||
|
if (file == null || !file.canAccess()) {
|
||||||
|
throw NoSuchElementException("Could not load CachedFile.")
|
||||||
|
}
|
||||||
|
|
||||||
|
file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getStorageFiles(): Result<List<CachedFile>> = runCatching {
|
||||||
|
application.contentResolver.persistedUriPermissions.mapNotNull { permission ->
|
||||||
|
val storage = CachedFileCompat.fromUri(
|
||||||
|
application,
|
||||||
|
permission.uri
|
||||||
|
)
|
||||||
|
if (!storage.isDirectory) return@mapNotNull null
|
||||||
|
|
||||||
|
storage
|
||||||
|
}.let { storages ->
|
||||||
|
storages.filter { storage ->
|
||||||
|
storages.none {
|
||||||
|
it.path != storage.path && storage.path.startsWith(
|
||||||
|
it.path,
|
||||||
|
ignoreCase = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,13 +6,12 @@
|
||||||
|
|
||||||
package ua.acclorite.book_story.domain.file
|
package ua.acclorite.book_story.domain.file
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class File(
|
data class File(
|
||||||
val name: String,
|
val name: String,
|
||||||
val uri: Uri,
|
val uri: String,
|
||||||
val path: String,
|
val path: String,
|
||||||
val size: Long,
|
val size: Long,
|
||||||
val lastModified: Long,
|
val lastModified: Long,
|
||||||
|
|
|
||||||
|
|
@ -7,46 +7,35 @@
|
||||||
package ua.acclorite.book_story.domain.repository
|
package ua.acclorite.book_story.domain.repository
|
||||||
|
|
||||||
import ua.acclorite.book_story.core.CoverImage
|
import ua.acclorite.book_story.core.CoverImage
|
||||||
import ua.acclorite.book_story.data.model.library.BookWithCover
|
|
||||||
import ua.acclorite.book_story.domain.library.Book
|
import ua.acclorite.book_story.domain.library.Book
|
||||||
import ua.acclorite.book_story.domain.reader.ReaderText
|
import ua.acclorite.book_story.domain.reader.ReaderText
|
||||||
|
|
||||||
interface BookRepository {
|
interface BookRepository {
|
||||||
|
suspend fun searchBooks(
|
||||||
suspend fun getBooks(
|
|
||||||
query: String
|
query: String
|
||||||
): List<Book>
|
): Result<List<Book>>
|
||||||
|
|
||||||
suspend fun getBooksById(
|
suspend fun getBook(
|
||||||
ids: List<Int>
|
|
||||||
): List<Book>
|
|
||||||
|
|
||||||
suspend fun getBookText(
|
|
||||||
bookId: Int
|
bookId: Int
|
||||||
): List<ReaderText>
|
): Result<Book>
|
||||||
|
|
||||||
suspend fun insertBook(
|
suspend fun getText(
|
||||||
bookWithCover: BookWithCover
|
bookId: Int
|
||||||
)
|
): Result<List<ReaderText>>
|
||||||
|
|
||||||
|
suspend fun addBook(
|
||||||
|
book: Book
|
||||||
|
): Result<Unit>
|
||||||
|
|
||||||
suspend fun updateBook(
|
suspend fun updateBook(
|
||||||
book: Book
|
book: Book
|
||||||
)
|
): Result<Unit>
|
||||||
|
|
||||||
suspend fun updateCoverImageOfBook(
|
suspend fun deleteBook(
|
||||||
bookWithOldCover: Book,
|
book: Book
|
||||||
newCoverImage: CoverImage?
|
): Result<Unit>
|
||||||
)
|
|
||||||
|
|
||||||
suspend fun deleteBooks(
|
suspend fun getDefaultCover(
|
||||||
books: List<Book>
|
book: Book
|
||||||
)
|
): Result<CoverImage?>
|
||||||
|
|
||||||
suspend fun canResetCover(
|
|
||||||
bookId: Int
|
|
||||||
): Boolean
|
|
||||||
|
|
||||||
suspend fun resetCoverImage(
|
|
||||||
bookId: Int
|
|
||||||
): Boolean
|
|
||||||
}
|
}
|
||||||
|
|
@ -10,29 +10,27 @@ import ua.acclorite.book_story.domain.library.Category
|
||||||
import ua.acclorite.book_story.domain.library.CategorySort
|
import ua.acclorite.book_story.domain.library.CategorySort
|
||||||
|
|
||||||
interface CategoryRepository {
|
interface CategoryRepository {
|
||||||
|
suspend fun addCategory(
|
||||||
suspend fun insertCategory(
|
|
||||||
category: Category
|
category: Category
|
||||||
)
|
): Result<Unit>
|
||||||
|
|
||||||
suspend fun getCategories(): List<Category>
|
suspend fun getCategories(): Result<List<Category>>
|
||||||
|
|
||||||
suspend fun updateCategoryTitle(
|
suspend fun updateCategory(
|
||||||
id: Int,
|
category: Category
|
||||||
title: String
|
): Result<Unit>
|
||||||
)
|
|
||||||
|
|
||||||
suspend fun updateCategoriesOrder(
|
suspend fun updateOrder(
|
||||||
categories: List<Category>
|
categories: List<Category>
|
||||||
)
|
): Result<Unit>
|
||||||
|
|
||||||
suspend fun deleteCategory(
|
suspend fun deleteCategory(
|
||||||
category: Category
|
category: Category
|
||||||
)
|
): Result<Unit>
|
||||||
|
|
||||||
suspend fun updateCategorySortEntity(
|
suspend fun updateCategorySorting(
|
||||||
categorySort: CategorySort
|
categorySort: CategorySort
|
||||||
)
|
): Result<Unit>
|
||||||
|
|
||||||
suspend fun getCategorySortEntities(): List<CategorySort>
|
suspend fun getCategorySorting(): Result<List<CategorySort>>
|
||||||
}
|
}
|
||||||
|
|
@ -9,22 +9,21 @@ package ua.acclorite.book_story.domain.repository
|
||||||
import ua.acclorite.book_story.domain.reader.ColorPreset
|
import ua.acclorite.book_story.domain.reader.ColorPreset
|
||||||
|
|
||||||
interface ColorPresetRepository {
|
interface ColorPresetRepository {
|
||||||
|
suspend fun getColorPresets(): Result<List<ColorPreset>>
|
||||||
|
|
||||||
suspend fun updateColorPreset(
|
suspend fun updateColorPreset(
|
||||||
colorPreset: ColorPreset
|
colorPreset: ColorPreset
|
||||||
)
|
): Result<Unit>
|
||||||
|
|
||||||
suspend fun selectColorPreset(
|
suspend fun selectColorPreset(
|
||||||
colorPreset: ColorPreset
|
colorPreset: ColorPreset
|
||||||
)
|
): Result<Unit>
|
||||||
|
|
||||||
suspend fun getColorPresets(): List<ColorPreset>
|
|
||||||
|
|
||||||
suspend fun reorderColorPresets(
|
suspend fun reorderColorPresets(
|
||||||
orderedColorPresets: List<ColorPreset>
|
colorPresets: List<ColorPreset>
|
||||||
)
|
): Result<Unit>
|
||||||
|
|
||||||
suspend fun deleteColorPreset(
|
suspend fun deleteColorPreset(
|
||||||
colorPreset: ColorPreset
|
colorPreset: ColorPreset
|
||||||
)
|
): Result<Unit>
|
||||||
}
|
}
|
||||||
|
|
@ -7,14 +7,16 @@
|
||||||
package ua.acclorite.book_story.domain.repository
|
package ua.acclorite.book_story.domain.repository
|
||||||
|
|
||||||
import androidx.datastore.preferences.core.Preferences
|
import androidx.datastore.preferences.core.Preferences
|
||||||
import ua.acclorite.book_story.presentation.main.MainState
|
|
||||||
|
|
||||||
interface DataStoreRepository {
|
interface DataStoreRepository {
|
||||||
|
suspend fun <T> putPreference(
|
||||||
suspend fun <T> putDataToDataStore(
|
|
||||||
key: Preferences.Key<T>,
|
key: Preferences.Key<T>,
|
||||||
value: T
|
value: T
|
||||||
)
|
): Result<Unit>
|
||||||
|
|
||||||
suspend fun getAllSettings(): MainState
|
suspend fun <T> getPreference(
|
||||||
|
key: Preferences.Key<T>,
|
||||||
|
): Result<T>
|
||||||
|
|
||||||
|
suspend fun getAllPreferences(): Result<Set<Preferences.Key<*>>>
|
||||||
}
|
}
|
||||||
|
|
@ -6,16 +6,15 @@
|
||||||
|
|
||||||
package ua.acclorite.book_story.domain.repository
|
package ua.acclorite.book_story.domain.repository
|
||||||
|
|
||||||
import ua.acclorite.book_story.data.model.common.NullableBook
|
import ua.acclorite.book_story.data.model.library.BookWithCover
|
||||||
import ua.acclorite.book_story.domain.file.File
|
import ua.acclorite.book_story.domain.file.File
|
||||||
|
|
||||||
interface FileSystemRepository {
|
interface FileSystemRepository {
|
||||||
|
suspend fun searchFiles(
|
||||||
suspend fun getFiles(
|
|
||||||
query: String = ""
|
query: String = ""
|
||||||
): List<File>
|
): Result<List<File>>
|
||||||
|
|
||||||
suspend fun getBookFromFile(
|
suspend fun getBookFromFile(
|
||||||
file: File
|
file: File
|
||||||
): NullableBook
|
): Result<BookWithCover>
|
||||||
}
|
}
|
||||||
|
|
@ -9,24 +9,23 @@ package ua.acclorite.book_story.domain.repository
|
||||||
import ua.acclorite.book_story.domain.history.History
|
import ua.acclorite.book_story.domain.history.History
|
||||||
|
|
||||||
interface HistoryRepository {
|
interface HistoryRepository {
|
||||||
|
suspend fun getHistoryForBook(
|
||||||
|
bookId: Int
|
||||||
|
): Result<History>
|
||||||
|
|
||||||
suspend fun insertHistory(
|
suspend fun addHistory(
|
||||||
history: History
|
history: History
|
||||||
)
|
): Result<Unit>
|
||||||
|
|
||||||
suspend fun getHistory(): List<History>
|
suspend fun getHistory(): Result<List<History>>
|
||||||
|
|
||||||
suspend fun getLatestBookHistory(
|
suspend fun deleteWholeHistory(): Result<Unit>
|
||||||
|
|
||||||
|
suspend fun deleteHistoryForBook(
|
||||||
bookId: Int
|
bookId: Int
|
||||||
): History?
|
): Result<Unit>
|
||||||
|
|
||||||
suspend fun deleteWholeHistory()
|
|
||||||
|
|
||||||
suspend fun deleteBookHistory(
|
|
||||||
bookId: Int
|
|
||||||
)
|
|
||||||
|
|
||||||
suspend fun deleteHistory(
|
suspend fun deleteHistory(
|
||||||
history: History
|
history: History
|
||||||
)
|
): Result<Unit>
|
||||||
}
|
}
|
||||||
|
|
@ -6,15 +6,12 @@
|
||||||
|
|
||||||
package ua.acclorite.book_story.domain.repository
|
package ua.acclorite.book_story.domain.repository
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
|
|
||||||
interface PermissionRepository {
|
interface PermissionRepository {
|
||||||
|
|
||||||
suspend fun grantPersistableUriPermission(
|
suspend fun grantPersistableUriPermission(
|
||||||
uri: Uri
|
uri: String
|
||||||
)
|
): Result<Unit>
|
||||||
|
|
||||||
suspend fun releasePersistableUriPermission(
|
suspend fun releasePersistableUriPermission(
|
||||||
uri: Uri
|
uri: String
|
||||||
)
|
): Result<Unit>
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.service
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import ua.acclorite.book_story.core.CoverImage
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
interface CoverImageHandler {
|
||||||
|
suspend fun saveCover(coverImage: CoverImage): Result<File>
|
||||||
|
suspend fun deleteCover(coverImage: Uri): Result<Unit>
|
||||||
|
suspend fun compressCover(coverImage: CoverImage): Result<CoverImage>
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.service
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.data.model.file.CachedFile
|
||||||
|
import ua.acclorite.book_story.domain.library.Book
|
||||||
|
|
||||||
|
interface FileProvider {
|
||||||
|
fun getFileFromBook(book: Book): Result<CachedFile>
|
||||||
|
fun getStorageFiles(): Result<List<CachedFile>>
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.book
|
||||||
|
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import ua.acclorite.book_story.core.CoverImage
|
||||||
|
import ua.acclorite.book_story.core.logE
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.core.logW
|
||||||
|
import ua.acclorite.book_story.domain.library.Book
|
||||||
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
|
import ua.acclorite.book_story.domain.service.CoverImageHandler
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class AddBookUseCase @Inject constructor(
|
||||||
|
private val bookRepository: BookRepository,
|
||||||
|
private val coverImageHandler: CoverImageHandler
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(book: Book, coverImage: CoverImage?) {
|
||||||
|
logI("Inserting [${book.title}].")
|
||||||
|
|
||||||
|
val coverImageUri = coverImage?.let {
|
||||||
|
coverImageHandler.saveCover(it).fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully saved cover image of [${book.title}].")
|
||||||
|
it.toUri()
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logW("Could not save cover image with error: ${it.message}")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
bookRepository.addBook(book = book.copy(coverImage = coverImageUri)).fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully inserted [${book.title}].")
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE("Could not insert [${book.title}] with error: ${it.message}")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.book
|
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.repository.BookRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class CanResetCover @Inject constructor(
|
|
||||||
private val repository: BookRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(bookId: Int): Boolean {
|
|
||||||
return repository.canResetCover(bookId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.book
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import ua.acclorite.book_story.core.logE
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
|
import ua.acclorite.book_story.domain.service.CoverImageHandler
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class CanResetCoverImageUseCase @Inject constructor(
|
||||||
|
private val application: Application,
|
||||||
|
private val bookRepository: BookRepository,
|
||||||
|
private val coverImageHandler: CoverImageHandler
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(bookId: Int): Boolean {
|
||||||
|
logI("Checking if can reset cover image of [$bookId].")
|
||||||
|
|
||||||
|
bookRepository.getBook(bookId).mapCatching { book ->
|
||||||
|
// Getting default cover image
|
||||||
|
val defaultCoverImage = bookRepository.getDefaultCover(book).getOrThrow()
|
||||||
|
if (defaultCoverImage == null) {
|
||||||
|
return@mapCatching true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return true if current cover is null (and default is not)
|
||||||
|
if (book.coverImage == null) {
|
||||||
|
return@mapCatching true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getting compressed cover images
|
||||||
|
val compressedDefaultCoverImage =
|
||||||
|
coverImageHandler.compressCover(defaultCoverImage).getOrThrow()
|
||||||
|
val compressedCoverImage = application.contentResolver
|
||||||
|
.openInputStream(book.coverImage)
|
||||||
|
?.use { BitmapFactory.decodeStream(it) }
|
||||||
|
|
||||||
|
!compressedDefaultCoverImage.sameAs(compressedCoverImage)
|
||||||
|
}.fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Can reset cover image of [$bookId]: $it.")
|
||||||
|
return it
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE("Could not check if can reset cover image of [$bookId] with error: ${it.message}")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.book
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.core.logE
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.core.logW
|
||||||
|
import ua.acclorite.book_story.domain.library.Book
|
||||||
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
|
import ua.acclorite.book_story.domain.repository.HistoryRepository
|
||||||
|
import ua.acclorite.book_story.domain.service.CoverImageHandler
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class DeleteBookUseCase @Inject constructor(
|
||||||
|
private val bookRepository: BookRepository,
|
||||||
|
private val historyRepository: HistoryRepository,
|
||||||
|
private val coverImageHandler: CoverImageHandler
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(book: Book) {
|
||||||
|
logI("Deleting [${book.title}].")
|
||||||
|
|
||||||
|
// Deleting cover image
|
||||||
|
book.coverImage?.let { coverImageHandler.deleteCover(it) }?.onFailure {
|
||||||
|
logW("Could not delete cover image with error: ${it.message}")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deleting history
|
||||||
|
historyRepository.deleteHistoryForBook(bookId = book.id).onFailure {
|
||||||
|
logW("Could not delete history for [${book.title}] with error: ${it.message}")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deleting book
|
||||||
|
bookRepository.deleteBook(book).fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully deleted [${book.title}].")
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE("Could not delete [${book.title}] with error: ${it.message}")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.book
|
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.library.Book
|
|
||||||
import ua.acclorite.book_story.domain.repository.BookRepository
|
|
||||||
import ua.acclorite.book_story.domain.repository.HistoryRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class DeleteBooks @Inject constructor(
|
|
||||||
private val bookRepository: BookRepository,
|
|
||||||
private val historyRepository: HistoryRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(books: List<Book>) {
|
|
||||||
bookRepository.deleteBooks(books)
|
|
||||||
books.forEach {
|
|
||||||
historyRepository.deleteBookHistory(it.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.book
|
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.library.Book
|
|
||||||
import ua.acclorite.book_story.domain.repository.BookRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class GetBookById @Inject constructor(
|
|
||||||
private val repository: BookRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(id: Int): Book? {
|
|
||||||
return repository.getBooksById(listOf(id)).firstOrNull()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.book
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.core.logE
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.domain.library.Book
|
||||||
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
|
import ua.acclorite.book_story.domain.repository.HistoryRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetBookUseCase @Inject constructor(
|
||||||
|
private val bookRepository: BookRepository,
|
||||||
|
private val historyRepository: HistoryRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(bookId: Int): Book? {
|
||||||
|
logI("Getting book: [$bookId].")
|
||||||
|
|
||||||
|
bookRepository.getBook(bookId).fold(
|
||||||
|
onSuccess = { book ->
|
||||||
|
val history = historyRepository.getHistoryForBook(book.id).getOrNull()
|
||||||
|
|
||||||
|
logI("Successfully got book: [$bookId].")
|
||||||
|
return book.copy(
|
||||||
|
lastOpened = history?.time
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE("Could not get book [$bookId] with error: ${it.message}")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.book
|
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.library.Book
|
|
||||||
import ua.acclorite.book_story.domain.repository.BookRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class GetBooks @Inject constructor(
|
|
||||||
private val repository: BookRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(query: String): List<Book> {
|
|
||||||
return repository.getBooks(query)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.book
|
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.library.Book
|
|
||||||
import ua.acclorite.book_story.domain.repository.BookRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class GetBooksById @Inject constructor(
|
|
||||||
private val repository: BookRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(ids: List<Int>): List<Book> {
|
|
||||||
return repository.getBooksById(ids)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.book
|
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.reader.ReaderText
|
|
||||||
import ua.acclorite.book_story.domain.repository.BookRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class GetText @Inject constructor(
|
|
||||||
private val repository: BookRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(bookId: Int): List<ReaderText> {
|
|
||||||
return repository.getBookText(bookId = bookId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.book
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.core.logE
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.domain.reader.ReaderText
|
||||||
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetTextUseCase @Inject constructor(
|
||||||
|
private val bookRepository: BookRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(bookId: Int): List<ReaderText> {
|
||||||
|
logI("Getting text from: [$bookId].")
|
||||||
|
if (bookId == -1) return emptyList()
|
||||||
|
|
||||||
|
bookRepository.getText(bookId).mapCatching { readerText ->
|
||||||
|
if (
|
||||||
|
readerText.filterIsInstance<ReaderText.Text>().isEmpty() ||
|
||||||
|
readerText.filterIsInstance<ReaderText.Chapter>().isEmpty()
|
||||||
|
) {
|
||||||
|
throw Exception("ReaderText is empty.")
|
||||||
|
}
|
||||||
|
|
||||||
|
readerText
|
||||||
|
}.fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully loaded text from [$bookId] with markdown.")
|
||||||
|
return it
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE("Could not load text with exception: ${it.message}")
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.book
|
|
||||||
|
|
||||||
import ua.acclorite.book_story.data.model.library.BookWithCover
|
|
||||||
import ua.acclorite.book_story.domain.repository.BookRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class InsertBook @Inject constructor(
|
|
||||||
private val repository: BookRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(bookWithCover: BookWithCover) {
|
|
||||||
repository.insertBook(bookWithCover)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.book
|
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.repository.BookRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class ResetCoverImage @Inject constructor(
|
|
||||||
private val repository: BookRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(bookId: Int): Boolean {
|
|
||||||
return repository.resetCoverImage(bookId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.book
|
||||||
|
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import ua.acclorite.book_story.core.logE
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.core.logW
|
||||||
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
|
import ua.acclorite.book_story.domain.service.CoverImageHandler
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class ResetCoverImageUseCase @Inject constructor(
|
||||||
|
private val bookRepository: BookRepository,
|
||||||
|
private val coverImageHandler: CoverImageHandler
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(bookId: Int): Boolean {
|
||||||
|
logI("Resetting cover image of [$bookId].")
|
||||||
|
|
||||||
|
bookRepository.getBook(bookId).mapCatching { book ->
|
||||||
|
// Getting default cover image
|
||||||
|
val defaultCoverImage = bookRepository.getDefaultCover(book).getOrThrow()
|
||||||
|
if (defaultCoverImage == null) {
|
||||||
|
throw NoSuchElementException("Could not find default cover image")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deleting old cover
|
||||||
|
book.coverImage?.let { coverImageHandler.deleteCover(it) }?.onFailure {
|
||||||
|
logW("Could not delete old cover image with error: ${it.message}")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resetting cover
|
||||||
|
val newCoverImage = defaultCoverImage.let {
|
||||||
|
coverImageHandler.saveCover(it).getOrThrow()
|
||||||
|
}
|
||||||
|
|
||||||
|
book.copy(coverImage = newCoverImage.toUri())
|
||||||
|
}.mapCatching { newBook ->
|
||||||
|
bookRepository.updateBook(newBook).getOrThrow()
|
||||||
|
}.fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully reset cover image of [$bookId].")
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE("Could not reset cover image of [$bookId] with error: ${it.message}")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.book
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.core.logE
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.domain.library.Book
|
||||||
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
|
import ua.acclorite.book_story.domain.repository.HistoryRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class SearchBooksUseCase @Inject constructor(
|
||||||
|
private val bookRepository: BookRepository,
|
||||||
|
private val historyRepository: HistoryRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(query: String): List<Book> {
|
||||||
|
logI("Searching for books with query: \"$query\".")
|
||||||
|
|
||||||
|
return bookRepository.searchBooks(query).fold(
|
||||||
|
onSuccess = { books ->
|
||||||
|
logI("Successfully found [${books.size}] books.")
|
||||||
|
books.map { book ->
|
||||||
|
val history = historyRepository.getHistoryForBook(book.id).getOrNull()
|
||||||
|
|
||||||
|
book.copy(
|
||||||
|
lastOpened = history?.time
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE("Could not find books with error: ${it.message}")
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.book
|
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.library.Book
|
|
||||||
import ua.acclorite.book_story.domain.repository.BookRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class UpdateBook @Inject constructor(
|
|
||||||
private val repository: BookRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(book: Book) {
|
|
||||||
repository.updateBook(book)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.book
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.core.logE
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.domain.library.Book
|
||||||
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class UpdateBookUseCase @Inject constructor(
|
||||||
|
private val bookRepository: BookRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(book: Book) {
|
||||||
|
logI("Updating [${book.title}].")
|
||||||
|
|
||||||
|
bookRepository.updateBook(book).fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully updated [${book.title}].")
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE("Could not update [${book.title}] with error: ${it.message}")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.book
|
|
||||||
|
|
||||||
import ua.acclorite.book_story.core.CoverImage
|
|
||||||
import ua.acclorite.book_story.domain.library.Book
|
|
||||||
import ua.acclorite.book_story.domain.repository.BookRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class UpdateCoverImageOfBook @Inject constructor(
|
|
||||||
private val repository: BookRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(bookWithOldCover: Book, newCoverImage: CoverImage?) {
|
|
||||||
repository.updateCoverImageOfBook(bookWithOldCover, newCoverImage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.book
|
||||||
|
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import ua.acclorite.book_story.core.CoverImage
|
||||||
|
import ua.acclorite.book_story.core.logE
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.core.logW
|
||||||
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
|
import ua.acclorite.book_story.domain.service.CoverImageHandler
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class UpdateCoverImageUseCase @Inject constructor(
|
||||||
|
private val bookRepository: BookRepository,
|
||||||
|
private val coverImageHandler: CoverImageHandler
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(bookId: Int, coverImage: CoverImage?) {
|
||||||
|
logI("Updating cover image of [$bookId].")
|
||||||
|
|
||||||
|
bookRepository.getBook(bookId).mapCatching { book ->
|
||||||
|
// Deleting old cover
|
||||||
|
book.coverImage?.let { coverImageHandler.deleteCover(it) }?.onFailure {
|
||||||
|
logW("Could not delete old cover image with error: ${it.message}")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Saving new cover
|
||||||
|
val newCoverImage = coverImage?.let {
|
||||||
|
coverImageHandler.saveCover(it).getOrThrow()
|
||||||
|
}
|
||||||
|
|
||||||
|
book.copy(coverImage = newCoverImage?.toUri())
|
||||||
|
}.mapCatching { newBook ->
|
||||||
|
bookRepository.updateBook(newBook).getOrThrow()
|
||||||
|
}.fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully updated cover image of [$bookId].")
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE("Could not update cover image of [$bookId] with error: ${it.message}")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.category
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.core.logE
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.domain.library.Category
|
||||||
|
import ua.acclorite.book_story.domain.repository.CategoryRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class AddCategoryUseCase @Inject constructor(
|
||||||
|
private val categoryRepository: CategoryRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(title: String) {
|
||||||
|
logI("Inserting [${title}] category.")
|
||||||
|
|
||||||
|
categoryRepository.addCategory(Category(title = title)).fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully inserted [${title}] category.")
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE("Could not insert [${title}] category with error: ${it.message}")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.category
|
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.library.Category
|
|
||||||
import ua.acclorite.book_story.domain.repository.CategoryRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class DeleteCategory @Inject constructor(
|
|
||||||
private val repository: CategoryRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(category: Category) {
|
|
||||||
repository.deleteCategory(category)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.category
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.domain.library.Category
|
||||||
|
import ua.acclorite.book_story.domain.repository.CategoryRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class DeleteCategoryUseCase @Inject constructor(
|
||||||
|
private val categoryRepository: CategoryRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(category: Category) {
|
||||||
|
logI("Deleting category [${category.id}].")
|
||||||
|
|
||||||
|
categoryRepository.deleteCategory(category).fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully deleted category [${category.id}].")
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logI("Could not delete category [${category.id}] with error: ${it.message}")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.category
|
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.library.Category
|
|
||||||
import ua.acclorite.book_story.domain.repository.CategoryRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class GetCategories @Inject constructor(
|
|
||||||
private val repository: CategoryRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(): List<Category> {
|
|
||||||
return repository.getCategories()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.category
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.core.logE
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.domain.library.Category
|
||||||
|
import ua.acclorite.book_story.domain.repository.CategoryRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetCategoriesUseCase @Inject constructor(
|
||||||
|
private val categoryRepository: CategoryRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(): List<Category> {
|
||||||
|
logI("Getting all categories.")
|
||||||
|
|
||||||
|
return categoryRepository.getCategories().fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully got [${it.size}] categories.")
|
||||||
|
it
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE("Could not get categories with error: ${it.message}")
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.category
|
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.library.CategorySort
|
|
||||||
import ua.acclorite.book_story.domain.repository.CategoryRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class GetCategorySort @Inject constructor(
|
|
||||||
private val repository: CategoryRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(): List<CategorySort> {
|
|
||||||
return repository.getCategorySortEntities()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.category
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.core.logE
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.domain.library.CategorySort
|
||||||
|
import ua.acclorite.book_story.domain.repository.CategoryRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetCategorySortingUseCase @Inject constructor(
|
||||||
|
private val categoryRepository: CategoryRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(): List<CategorySort> {
|
||||||
|
logI("Getting category sorting.")
|
||||||
|
|
||||||
|
return categoryRepository.getCategorySorting().fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully got category sorting.")
|
||||||
|
it
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE("Could not get category sorting with error: ${it.message}")
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.category
|
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.library.Category
|
|
||||||
import ua.acclorite.book_story.domain.repository.CategoryRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class InsertCategory @Inject constructor(
|
|
||||||
private val repository: CategoryRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(category: Category) {
|
|
||||||
repository.insertCategory(category)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.category
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.domain.library.Category
|
||||||
|
import ua.acclorite.book_story.domain.repository.CategoryRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class UpdateCategoriesOrderUseCase @Inject constructor(
|
||||||
|
private val categoryRepository: CategoryRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(categories: List<Category>) {
|
||||||
|
logI("Updating order of [${categories.size}] categories.")
|
||||||
|
|
||||||
|
categoryRepository.updateOrder(categories).fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully updated order of [${categories.size}] categories.")
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logI("Could not update order of [${categories.size}] categories with error: ${it.message}")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.category
|
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.library.Category
|
|
||||||
import ua.acclorite.book_story.domain.repository.CategoryRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class UpdateCategoryOrder @Inject constructor(
|
|
||||||
private val repository: CategoryRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(categories: List<Category>) {
|
|
||||||
repository.updateCategoriesOrder(categories)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.category
|
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.library.CategorySort
|
|
||||||
import ua.acclorite.book_story.domain.repository.CategoryRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class UpdateCategorySort @Inject constructor(
|
|
||||||
private val repository: CategoryRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(categorySort: CategorySort) {
|
|
||||||
repository.updateCategorySortEntity(categorySort)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.category
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.core.logE
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.domain.library.CategorySort
|
||||||
|
import ua.acclorite.book_story.domain.repository.CategoryRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class UpdateCategorySortingUseCase @Inject constructor(
|
||||||
|
private val categoryRepository: CategoryRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(categorySort: CategorySort) {
|
||||||
|
logI("Updating category sorting of [${categorySort.categoryId}].")
|
||||||
|
|
||||||
|
categoryRepository.updateCategorySorting(categorySort).fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully updated category sorting of [${categorySort.categoryId}].")
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE(
|
||||||
|
"Could not update category sorting of [${categorySort.categoryId}] with error: ${it.message}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.category
|
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.repository.CategoryRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class UpdateCategoryTitle @Inject constructor(
|
|
||||||
private val repository: CategoryRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(id: Int, title: String) {
|
|
||||||
repository.updateCategoryTitle(id, title)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.category
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.domain.library.Category
|
||||||
|
import ua.acclorite.book_story.domain.repository.CategoryRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class UpdateCategoryUseCase @Inject constructor(
|
||||||
|
private val categoryRepository: CategoryRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(categoryId: Int, newTitle: String) {
|
||||||
|
logI("Updating category [$categoryId].")
|
||||||
|
|
||||||
|
categoryRepository.updateCategory(Category(id = categoryId, title = newTitle)).fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully updated [$categoryId].")
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logI("Could not update [$categoryId] with error: ${it.message}")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.color_preset
|
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.reader.ColorPreset
|
|
||||||
import ua.acclorite.book_story.domain.repository.ColorPresetRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class DeleteColorPreset @Inject constructor(
|
|
||||||
private val repository: ColorPresetRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(colorPreset: ColorPreset) {
|
|
||||||
repository.deleteColorPreset(colorPreset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.color_preset
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.core.logE
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.domain.reader.ColorPreset
|
||||||
|
import ua.acclorite.book_story.domain.repository.ColorPresetRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class DeleteColorPresetUseCase @Inject constructor(
|
||||||
|
private val colorPresetRepository: ColorPresetRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(colorPreset: ColorPreset) {
|
||||||
|
logI("Deleting [${colorPreset.id}] color preset.")
|
||||||
|
|
||||||
|
colorPresetRepository.deleteColorPreset(colorPreset).fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully deleted [${colorPreset.id}] color preset.")
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE("Could not delete [${colorPreset.id}] color preset with error: ${it.message}")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.color_preset
|
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.reader.ColorPreset
|
|
||||||
import ua.acclorite.book_story.domain.repository.ColorPresetRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class GetColorPresets @Inject constructor(
|
|
||||||
private val repository: ColorPresetRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(): List<ColorPreset> {
|
|
||||||
return repository.getColorPresets()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.color_preset
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.core.logE
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.domain.reader.ColorPreset
|
||||||
|
import ua.acclorite.book_story.domain.repository.ColorPresetRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetColorPresetsUseCase @Inject constructor(
|
||||||
|
private val colorPresetRepository: ColorPresetRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(): List<ColorPreset> {
|
||||||
|
logI("Getting color presets.")
|
||||||
|
|
||||||
|
return colorPresetRepository.getColorPresets().fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully got [${it.size}] color presets.")
|
||||||
|
it
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE("Could not get color presets with error: ${it.message}")
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.color_preset
|
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.reader.ColorPreset
|
|
||||||
import ua.acclorite.book_story.domain.repository.ColorPresetRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class ReorderColorPresets @Inject constructor(
|
|
||||||
private val repository: ColorPresetRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(reorderedColorPresets: List<ColorPreset>) {
|
|
||||||
repository.reorderColorPresets(reorderedColorPresets)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.color_preset
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.core.logE
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.domain.reader.ColorPreset
|
||||||
|
import ua.acclorite.book_story.domain.repository.ColorPresetRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class ReorderColorPresetsUseCase @Inject constructor(
|
||||||
|
private val colorPresetRepository: ColorPresetRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(colorPresets: List<ColorPreset>) {
|
||||||
|
logI("Reordering [${colorPresets.size}] color presets.")
|
||||||
|
|
||||||
|
colorPresetRepository.reorderColorPresets(colorPresets).fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully reordered [${colorPresets.size}] color presets.")
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE("Could not reorder [${colorPresets.size}] color presets with error: ${it.message}")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.color_preset
|
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.reader.ColorPreset
|
|
||||||
import ua.acclorite.book_story.domain.repository.ColorPresetRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class SelectColorPreset @Inject constructor(
|
|
||||||
private val repository: ColorPresetRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(colorPreset: ColorPreset) {
|
|
||||||
repository.selectColorPreset(colorPreset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.color_preset
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.core.logE
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.domain.reader.ColorPreset
|
||||||
|
import ua.acclorite.book_story.domain.repository.ColorPresetRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class SelectColorPresetUseCase @Inject constructor(
|
||||||
|
private val colorPresetRepository: ColorPresetRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(colorPreset: ColorPreset) {
|
||||||
|
logI("Selecting [${colorPreset.id}] color preset.")
|
||||||
|
|
||||||
|
colorPresetRepository.selectColorPreset(colorPreset).fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully selected [${colorPreset.id}] color preset.")
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE("Could not select [${colorPreset.id}] color preset with error: ${it.message}")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.color_preset
|
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.reader.ColorPreset
|
|
||||||
import ua.acclorite.book_story.domain.repository.ColorPresetRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class UpdateColorPreset @Inject constructor(
|
|
||||||
private val repository: ColorPresetRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(colorPreset: ColorPreset) {
|
|
||||||
repository.updateColorPreset(colorPreset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.color_preset
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.core.logE
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.domain.reader.ColorPreset
|
||||||
|
import ua.acclorite.book_story.domain.repository.ColorPresetRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class UpdateColorPresetUseCase @Inject constructor(
|
||||||
|
private val colorPresetRepository: ColorPresetRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(colorPreset: ColorPreset) {
|
||||||
|
logI("Updating [${colorPreset.id}] color preset.")
|
||||||
|
|
||||||
|
colorPresetRepository.updateColorPreset(colorPreset).fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully updated [${colorPreset.id}] color preset.")
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE("Could not update [${colorPreset.id}] color preset with error: ${it.message}")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.data_store
|
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
|
||||||
import androidx.core.os.LocaleListCompat
|
|
||||||
import ua.acclorite.book_story.domain.repository.DataStoreRepository
|
|
||||||
import ua.acclorite.book_story.ui.common.constants.DataStoreConstants
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class ChangeLanguage @Inject constructor(
|
|
||||||
private val repository: DataStoreRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(language: String) {
|
|
||||||
val appLocale = LocaleListCompat.forLanguageTags(language)
|
|
||||||
AppCompatDelegate.setApplicationLocales(appLocale)
|
|
||||||
|
|
||||||
repository.putDataToDataStore(
|
|
||||||
DataStoreConstants.LANGUAGE,
|
|
||||||
language
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.data_store
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
import androidx.core.os.LocaleListCompat
|
||||||
|
import ua.acclorite.book_story.core.logE
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.domain.repository.DataStoreRepository
|
||||||
|
import ua.acclorite.book_story.ui.common.constants.DataStoreConstants
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class ChangeLanguagePreferenceUseCase @Inject constructor(
|
||||||
|
private val dataStoreRepository: DataStoreRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(language: String) {
|
||||||
|
logI("Changing language preference.")
|
||||||
|
|
||||||
|
dataStoreRepository.putPreference(
|
||||||
|
DataStoreConstants.LANGUAGE,
|
||||||
|
language
|
||||||
|
).fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully changed language preference.")
|
||||||
|
|
||||||
|
val appLocale = LocaleListCompat.forLanguageTags(language)
|
||||||
|
AppCompatDelegate.setApplicationLocales(appLocale)
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE("Could not change language preference with error: ${it.message}")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.data_store
|
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.repository.DataStoreRepository
|
|
||||||
import ua.acclorite.book_story.presentation.main.MainState
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class GetAllSettings @Inject constructor(
|
|
||||||
private val repository: DataStoreRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(): MainState {
|
|
||||||
return repository.getAllSettings()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.data_store
|
||||||
|
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.awaitAll
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import ua.acclorite.book_story.core.logE
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.core.logW
|
||||||
|
import ua.acclorite.book_story.domain.repository.DataStoreRepository
|
||||||
|
import ua.acclorite.book_story.presentation.main.MainState
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetPreferencesUseCase @Inject constructor(
|
||||||
|
private val dataStoreRepository: DataStoreRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(): MainState {
|
||||||
|
logI("Getting all preferences (MainState).")
|
||||||
|
|
||||||
|
return withContext(Dispatchers.Default) {
|
||||||
|
dataStoreRepository.getAllPreferences().mapCatching { keys ->
|
||||||
|
logI("Got [${keys.size}] preference keys.")
|
||||||
|
|
||||||
|
val preferences = ConcurrentHashMap<String, Any>()
|
||||||
|
keys.map { key ->
|
||||||
|
async {
|
||||||
|
dataStoreRepository.getPreference(key).fold(
|
||||||
|
onSuccess = {
|
||||||
|
it?.let { preference ->
|
||||||
|
preferences[key.name] = preference
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logW("Could not get [${key.name}] preference.")
|
||||||
|
preferences.remove(key.name)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.awaitAll()
|
||||||
|
|
||||||
|
MainState.initialize(preferences)
|
||||||
|
}.fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully got all preferences (MainState).")
|
||||||
|
it
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE("Could not get all preferences (MainState) with error: ${it.message}")
|
||||||
|
MainState()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.data_store
|
||||||
|
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import ua.acclorite.book_story.core.logE
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.domain.repository.DataStoreRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class PutPreferenceUseCase @Inject constructor(
|
||||||
|
private val dataStoreRepository: DataStoreRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun <T> invoke(key: Preferences.Key<T>, value: T) {
|
||||||
|
logI("Putting preference [${key.name}].")
|
||||||
|
|
||||||
|
dataStoreRepository.putPreference(key, value).fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully put preference [${key.name}].")
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE("Could not put preference [${key.name}] with error: ${it.message}")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.data_store
|
|
||||||
|
|
||||||
import androidx.datastore.preferences.core.Preferences
|
|
||||||
import ua.acclorite.book_story.domain.repository.DataStoreRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class SetDatastore @Inject constructor(
|
|
||||||
private val repository: DataStoreRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun <T> execute(key: Preferences.Key<T>, value: T) {
|
|
||||||
repository.putDataToDataStore(key, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.file_system
|
|
||||||
|
|
||||||
import ua.acclorite.book_story.data.model.common.NullableBook
|
|
||||||
import ua.acclorite.book_story.domain.file.File
|
|
||||||
import ua.acclorite.book_story.domain.repository.FileSystemRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class GetBookFromFile @Inject constructor(
|
|
||||||
private val repository: FileSystemRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(file: File): NullableBook {
|
|
||||||
return repository.getBookFromFile(file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.file_system
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.core.logE
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.core.ui.UIText
|
||||||
|
import ua.acclorite.book_story.data.model.common.NullableBook
|
||||||
|
import ua.acclorite.book_story.data.model.common.NullableBook.NotNull
|
||||||
|
import ua.acclorite.book_story.data.model.common.NullableBook.Null
|
||||||
|
import ua.acclorite.book_story.domain.file.File
|
||||||
|
import ua.acclorite.book_story.domain.repository.FileSystemRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetBookFromFileUseCase @Inject constructor(
|
||||||
|
private val fileSystemRepository: FileSystemRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(file: File): NullableBook {
|
||||||
|
logI("Getting book from [${file.name}] file.")
|
||||||
|
|
||||||
|
return fileSystemRepository.getBookFromFile(file).fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully got [${it.book.title}] book from file.")
|
||||||
|
NotNull(it)
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE("Could not get book from file with error: ${it.message}")
|
||||||
|
Null(
|
||||||
|
file.name,
|
||||||
|
UIText.StringResource(R.string.error_something_went_wrong)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.file_system
|
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.file.File
|
|
||||||
import ua.acclorite.book_story.domain.repository.FileSystemRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class GetFiles @Inject constructor(
|
|
||||||
private val repository: FileSystemRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(query: String): List<File> {
|
|
||||||
return repository.getFiles(query)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.file_system
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.core.logE
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.domain.file.File
|
||||||
|
import ua.acclorite.book_story.domain.repository.FileSystemRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class SearchFilesUseCase @Inject constructor(
|
||||||
|
private val fileSystemRepository: FileSystemRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(query: String = ""): List<File> {
|
||||||
|
logI("Searching for files with query: \"$query\".")
|
||||||
|
|
||||||
|
return fileSystemRepository.searchFiles(query).fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully found [${it.size}] files.")
|
||||||
|
it
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE("Could not find files with error: ${it.message}")
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.history
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.core.logE
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.domain.history.History
|
||||||
|
import ua.acclorite.book_story.domain.repository.HistoryRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class AddHistoryUseCase @Inject constructor(
|
||||||
|
private val historyRepository: HistoryRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(history: History) {
|
||||||
|
logI("Inserting history for [${history.bookId}].")
|
||||||
|
|
||||||
|
historyRepository.addHistory(history = history).fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully inserted history for [${history.bookId}].")
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE("Could not insert history for [${history.bookId}] with error: ${it.message}")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.history
|
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.history.History
|
|
||||||
import ua.acclorite.book_story.domain.repository.HistoryRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class DeleteHistory @Inject constructor(
|
|
||||||
private val repository: HistoryRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(history: History) {
|
|
||||||
repository.deleteHistory(history)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.history
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.core.logE
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.domain.history.History
|
||||||
|
import ua.acclorite.book_story.domain.repository.HistoryRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class DeleteHistoryUseCase @Inject constructor(
|
||||||
|
private val historyRepository: HistoryRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(history: History) {
|
||||||
|
logI("Deleting history [${history.id}].")
|
||||||
|
|
||||||
|
return historyRepository.deleteHistory(history).fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully deleted history [${history.id}].")
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE("Could not delete history [${history.id}] with error: ${it.message}")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.history
|
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.repository.HistoryRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class DeleteWholeHistory @Inject constructor(
|
|
||||||
private val repository: HistoryRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute() {
|
|
||||||
repository.deleteWholeHistory()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.history
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.core.logE
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.domain.repository.HistoryRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class DeleteWholeHistoryUseCase @Inject constructor(
|
||||||
|
private val historyRepository: HistoryRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke() {
|
||||||
|
logI("Deleting whole history.")
|
||||||
|
|
||||||
|
return historyRepository.deleteWholeHistory().fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully deleted whole history.")
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE("Could not delete whole history with error: ${it.message}")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.history
|
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.history.History
|
|
||||||
import ua.acclorite.book_story.domain.repository.HistoryRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class GetHistory @Inject constructor(
|
|
||||||
private val repository: HistoryRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(): List<History> {
|
|
||||||
return repository.getHistory()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.history
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.core.logE
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.domain.history.History
|
||||||
|
import ua.acclorite.book_story.domain.repository.HistoryRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetHistoryForBookUseCase @Inject constructor(
|
||||||
|
private val historyRepository: HistoryRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(bookId: Int): History? {
|
||||||
|
logI("Getting history for [$bookId].")
|
||||||
|
|
||||||
|
return historyRepository.getHistoryForBook(bookId = bookId).fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully got history for [$bookId].")
|
||||||
|
it
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE("Could not get history for [$bookId] with error: ${it.message}")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.history
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.core.logE
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.domain.history.History
|
||||||
|
import ua.acclorite.book_story.domain.repository.HistoryRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetHistoryUseCase @Inject constructor(
|
||||||
|
private val historyRepository: HistoryRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(): List<History> {
|
||||||
|
logI("Getting all history.")
|
||||||
|
|
||||||
|
return historyRepository.getHistory().fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully got ${it.size} history entries.")
|
||||||
|
it
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE("Could not get history entries with error: ${it.message}")
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.history
|
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.history.History
|
|
||||||
import ua.acclorite.book_story.domain.repository.HistoryRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class GetLatestHistory @Inject constructor(
|
|
||||||
private val repository: HistoryRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(bookId: Int): History? {
|
|
||||||
return repository.getLatestBookHistory(bookId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.history
|
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.history.History
|
|
||||||
import ua.acclorite.book_story.domain.repository.HistoryRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class InsertHistory @Inject constructor(
|
|
||||||
private val repository: HistoryRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(history: History) {
|
|
||||||
repository.insertHistory(history)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.permission
|
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import ua.acclorite.book_story.domain.repository.PermissionRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class GrantPersistableUriPermission @Inject constructor(
|
|
||||||
private val repository: PermissionRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(uri: Uri) {
|
|
||||||
repository.grantPersistableUriPermission(uri)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.permission
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.core.logE
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.domain.repository.PermissionRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GrantPersistableUriPermissionUseCase @Inject constructor(
|
||||||
|
private val permissionRepository: PermissionRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(uri: String) {
|
||||||
|
logI("Granting persistable URI permission to \"$uri\".")
|
||||||
|
|
||||||
|
permissionRepository.grantPersistableUriPermission(uri).fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully granted persistable URI permission to \"$uri\".")
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE("Could not grant persistable URI permission to \"$uri\" with error: ${it.message}.")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.domain.use_case.permission
|
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import ua.acclorite.book_story.domain.repository.PermissionRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class ReleasePersistableUriPermission @Inject constructor(
|
|
||||||
private val repository: PermissionRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun execute(uri: Uri) {
|
|
||||||
repository.releasePersistableUriPermission(uri)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.use_case.permission
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.core.logE
|
||||||
|
import ua.acclorite.book_story.core.logI
|
||||||
|
import ua.acclorite.book_story.domain.repository.PermissionRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class ReleasePersistableUriPermissionUseCase @Inject constructor(
|
||||||
|
private val permissionRepository: PermissionRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(uri: String) {
|
||||||
|
logI("Releasing persistable URI permission from \"$uri\".")
|
||||||
|
|
||||||
|
permissionRepository.releasePersistableUriPermission(uri).fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully released persistable URI permission from \"$uri\".")
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE("Could not release persistable URI permission from \"$uri\" with error: ${it.message}.")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue