From 72b032cabf9b458fc7e7b4424c3b475efe222b65 Mon Sep 17 00:00:00 2001 From: Acclorite <2ma0bhpk@anonaddy.com> Date: Mon, 2 Jun 2025 20:31:20 +0300 Subject: [PATCH] refactor: refactor use case & repository --- .../java/ua/acclorite/book_story/core/Log.kt | 25 ++ .../book_story/data/di/RepositoryModule.kt | 8 + .../book_story/data/di/ServiceModule.kt | 34 ++ .../book_story/data/local/room/BookDao.kt | 19 +- .../book_story/data/mapper/book/BookMapper.kt | 5 +- .../data/mapper/book/BookMapperImpl.kt | 4 +- .../data/mapper/category/CategoryMapper.kt | 5 +- .../mapper/category/CategoryMapperImpl.kt | 4 +- .../category_sort/CategorySortMapper.kt | 5 +- .../CategorySortSortMapperImpl.kt | 4 +- .../mapper/color_preset/ColorPresetMapper.kt | 5 +- .../color_preset/ColorPresetMapperImpl.kt | 4 +- .../book_story/data/mapper/file/FileMapper.kt | 15 + .../data/mapper/file/FileMapperImpl.kt | 43 ++ .../data/repository/BookRepositoryImpl.kt | 391 ++---------------- .../data/repository/CategoryRepositoryImpl.kt | 30 +- .../repository/ColorPresetRepositoryImpl.kt | 57 +-- .../repository/DataStoreRepositoryImpl.kt | 54 +-- .../repository/FileSystemRepositoryImpl.kt | 171 ++------ .../data/repository/HistoryRepositoryImpl.kt | 77 ++-- .../repository/PermissionRepositoryImpl.kt | 40 +- .../data/service/CoverImageHandlerImpl.kt | 68 +++ .../data/service/FileProviderImpl.kt | 58 +++ .../acclorite/book_story/domain/file/File.kt | 3 +- .../domain/repository/BookRepository.kt | 47 +-- .../domain/repository/CategoryRepository.kt | 26 +- .../repository/ColorPresetRepository.kt | 13 +- .../domain/repository/DataStoreRepository.kt | 12 +- .../domain/repository/FileSystemRepository.kt | 9 +- .../domain/repository/HistoryRepository.kt | 23 +- .../domain/repository/PermissionRepository.kt | 11 +- .../domain/service/CoverImageHandler.kt | 17 + .../book_story/domain/service/FileProvider.kt | 15 + .../domain/use_case/book/AddBookUseCase.kt | 49 +++ .../domain/use_case/book/CanResetCover.kt | 19 - .../book/CanResetCoverImageUseCase.kt | 57 +++ .../domain/use_case/book/DeleteBookUseCase.kt | 47 +++ .../domain/use_case/book/DeleteBooks.kt | 25 -- .../domain/use_case/book/GetBookById.kt | 20 - .../domain/use_case/book/GetBookUseCase.kt | 39 ++ .../domain/use_case/book/GetBooks.kt | 20 - .../domain/use_case/book/GetBooksById.kt | 20 - .../domain/use_case/book/GetText.kt | 20 - .../domain/use_case/book/GetTextUseCase.kt | 43 ++ .../domain/use_case/book/InsertBook.kt | 20 - .../domain/use_case/book/ResetCoverImage.kt | 19 - .../use_case/book/ResetCoverImageUseCase.kt | 56 +++ .../use_case/book/SearchBooksUseCase.kt | 41 ++ .../domain/use_case/book/UpdateBook.kt | 20 - .../domain/use_case/book/UpdateBookUseCase.kt | 31 ++ .../use_case/book/UpdateCoverImageOfBook.kt | 21 - .../use_case/book/UpdateCoverImageUseCase.kt | 49 +++ .../use_case/category/AddCategoryUseCase.kt | 31 ++ .../use_case/category/DeleteCategory.kt | 20 - .../category/DeleteCategoryUseCase.kt | 30 ++ .../domain/use_case/category/GetCategories.kt | 20 - .../use_case/category/GetCategoriesUseCase.kt | 33 ++ .../use_case/category/GetCategorySort.kt | 20 - .../category/GetCategorySortingUseCase.kt | 33 ++ .../use_case/category/InsertCategory.kt | 20 - .../category/UpdateCategoriesOrderUseCase.kt | 30 ++ .../use_case/category/UpdateCategoryOrder.kt | 20 - .../use_case/category/UpdateCategorySort.kt | 20 - .../category/UpdateCategorySortingUseCase.kt | 33 ++ .../use_case/category/UpdateCategoryTitle.kt | 19 - .../category/UpdateCategoryUseCase.kt | 30 ++ .../color_preset/DeleteColorPreset.kt | 20 - .../color_preset/DeleteColorPresetUseCase.kt | 31 ++ .../use_case/color_preset/GetColorPresets.kt | 20 - .../color_preset/GetColorPresetsUseCase.kt | 33 ++ .../color_preset/ReorderColorPresets.kt | 20 - .../ReorderColorPresetsUseCase.kt | 31 ++ .../color_preset/SelectColorPreset.kt | 20 - .../color_preset/SelectColorPresetUseCase.kt | 31 ++ .../color_preset/UpdateColorPreset.kt | 20 - .../color_preset/UpdateColorPresetUseCase.kt | 31 ++ .../use_case/data_store/ChangeLanguage.kt | 28 -- .../ChangeLanguagePreferenceUseCase.kt | 39 ++ .../use_case/data_store/GetAllSettings.kt | 20 - .../data_store/GetPreferencesUseCase.kt | 62 +++ .../data_store/PutPreferenceUseCase.kt | 31 ++ .../use_case/data_store/SetDatastore.kt | 20 - .../use_case/file_system/GetBookFromFile.kt | 21 - .../file_system/GetBookFromFileUseCase.kt | 41 ++ .../domain/use_case/file_system/GetFiles.kt | 20 - .../file_system/SearchFilesUseCase.kt | 33 ++ .../use_case/history/AddHistoryUseCase.kt | 31 ++ .../domain/use_case/history/DeleteHistory.kt | 20 - .../use_case/history/DeleteHistoryUseCase.kt | 31 ++ .../use_case/history/DeleteWholeHistory.kt | 19 - .../history/DeleteWholeHistoryUseCase.kt | 30 ++ .../domain/use_case/history/GetHistory.kt | 20 - .../history/GetHistoryForBookUseCase.kt | 33 ++ .../use_case/history/GetHistoryUseCase.kt | 33 ++ .../use_case/history/GetLatestHistory.kt | 20 - .../domain/use_case/history/InsertHistory.kt | 20 - .../GrantPersistableUriPermission.kt | 20 - .../GrantPersistableUriPermissionUseCase.kt | 30 ++ .../ReleasePersistableUriPermission.kt | 20 - .../ReleasePersistableUriPermissionUseCase.kt | 30 ++ .../presentation/book_info/BookInfoModel.kt | 63 ++- .../presentation/browse/BrowseModel.kt | 28 +- .../presentation/history/HistoryModel.kt | 36 +- .../presentation/library/LibraryModel.kt | 27 +- .../book_story/presentation/main/MainModel.kt | 21 +- .../presentation/reader/ReaderModel.kt | 30 +- .../presentation/settings/SettingsModel.kt | 148 +++---- 107 files changed, 1904 insertions(+), 1655 deletions(-) create mode 100644 app/src/main/java/ua/acclorite/book_story/core/Log.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/data/di/ServiceModule.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/data/mapper/file/FileMapper.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/data/mapper/file/FileMapperImpl.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/data/service/CoverImageHandlerImpl.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/data/service/FileProviderImpl.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/service/CoverImageHandler.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/service/FileProvider.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/book/AddBookUseCase.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/book/CanResetCover.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/book/CanResetCoverImageUseCase.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/book/DeleteBookUseCase.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/book/DeleteBooks.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetBookById.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetBookUseCase.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetBooks.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetBooksById.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetText.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetTextUseCase.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/book/InsertBook.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/book/ResetCoverImage.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/book/ResetCoverImageUseCase.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/book/SearchBooksUseCase.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/book/UpdateBook.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/book/UpdateBookUseCase.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/book/UpdateCoverImageOfBook.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/book/UpdateCoverImageUseCase.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/category/AddCategoryUseCase.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/category/DeleteCategory.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/category/DeleteCategoryUseCase.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/category/GetCategories.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/category/GetCategoriesUseCase.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/category/GetCategorySort.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/category/GetCategorySortingUseCase.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/category/InsertCategory.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/category/UpdateCategoriesOrderUseCase.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/category/UpdateCategoryOrder.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/category/UpdateCategorySort.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/category/UpdateCategorySortingUseCase.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/category/UpdateCategoryTitle.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/category/UpdateCategoryUseCase.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/DeleteColorPreset.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/DeleteColorPresetUseCase.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/GetColorPresets.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/GetColorPresetsUseCase.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/ReorderColorPresets.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/ReorderColorPresetsUseCase.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/SelectColorPreset.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/SelectColorPresetUseCase.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/UpdateColorPreset.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/UpdateColorPresetUseCase.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/data_store/ChangeLanguage.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/data_store/ChangeLanguagePreferenceUseCase.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/data_store/GetAllSettings.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/data_store/GetPreferencesUseCase.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/data_store/PutPreferenceUseCase.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/data_store/SetDatastore.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/file_system/GetBookFromFile.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/file_system/GetBookFromFileUseCase.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/file_system/GetFiles.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/file_system/SearchFilesUseCase.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/history/AddHistoryUseCase.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/history/DeleteHistory.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/history/DeleteHistoryUseCase.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/history/DeleteWholeHistory.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/history/DeleteWholeHistoryUseCase.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/history/GetHistory.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/history/GetHistoryForBookUseCase.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/history/GetHistoryUseCase.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/history/GetLatestHistory.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/history/InsertHistory.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/permission/GrantPersistableUriPermission.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/permission/GrantPersistableUriPermissionUseCase.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/permission/ReleasePersistableUriPermission.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/permission/ReleasePersistableUriPermissionUseCase.kt diff --git a/app/src/main/java/ua/acclorite/book_story/core/Log.kt b/app/src/main/java/ua/acclorite/book_story/core/Log.kt new file mode 100644 index 00000000..ac43efd1 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/core/Log.kt @@ -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) +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/di/RepositoryModule.kt b/app/src/main/java/ua/acclorite/book_story/data/di/RepositoryModule.kt index 130a9e70..f1ec15ae 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/di/RepositoryModule.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/di/RepositoryModule.kt @@ -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.color_preset.ColorPresetMapper 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.HistoryMapperImpl import ua.acclorite.book_story.data.parser.FileParser @@ -123,6 +125,12 @@ abstract class RepositoryModule { categorySortSortMapperImpl: CategorySortSortMapperImpl ): CategorySortMapper + @Binds + @Singleton + abstract fun bindFileMapper( + fileMapperImpl: FileMapperImpl + ): FileMapper + @Binds @Singleton abstract fun bindFileParser( diff --git a/app/src/main/java/ua/acclorite/book_story/data/di/ServiceModule.kt b/app/src/main/java/ua/acclorite/book_story/data/di/ServiceModule.kt new file mode 100644 index 00000000..2db94b37 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/data/di/ServiceModule.kt @@ -0,0 +1,34 @@ +/* + * Book's Story — free and open-source Material You eBook reader. + * Copyright (C) 2024-2025 Acclorite + * SPDX-License-Identifier: GPL-3.0-only + */ + +package ua.acclorite.book_story.data.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 +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/local/room/BookDao.kt b/app/src/main/java/ua/acclorite/book_story/data/local/room/BookDao.kt index 66740373..f510431f 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/local/room/BookDao.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/local/room/BookDao.kt @@ -40,16 +40,13 @@ interface BookDao { suspend fun searchBooks(query: String): List @Query("SELECT * FROM bookentity WHERE id=:id") - suspend fun findBookById(id: Int): BookEntity - - @Query("SELECT * FROM bookentity WHERE id IN (:ids)") - suspend fun findBooksById(ids: List): List + suspend fun findBookById(id: Int): BookEntity? @Delete - suspend fun deleteBooks(books: List) + suspend fun deleteBook(book: BookEntity): Int @Update - suspend fun updateBooks(books: List) + suspend fun updateBook(book: BookEntity): Int /* - - - - - - - - - - - - - - - - - - - - - - */ @@ -58,21 +55,21 @@ interface BookDao { suspend fun getHistory(): List @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) suspend fun insertHistory( - history: List + history: HistoryEntity ) @Query("DELETE FROM historyentity") - suspend fun deleteWholeHistory() + suspend fun deleteWholeHistory(): Int @Query("DELETE FROM historyentity WHERE bookId = :bookId") - suspend fun deleteBookHistory(bookId: Int) + suspend fun deleteHistoryForBook(bookId: Int): Int @Delete - suspend fun deleteHistory(history: List) + suspend fun deleteHistory(history: HistoryEntity): Int /* - - - - - - - - - - - - - - - - - - - - - - */ diff --git a/app/src/main/java/ua/acclorite/book_story/data/mapper/book/BookMapper.kt b/app/src/main/java/ua/acclorite/book_story/data/mapper/book/BookMapper.kt index 89c34397..76bf1369 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/mapper/book/BookMapper.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/mapper/book/BookMapper.kt @@ -10,7 +10,6 @@ import ua.acclorite.book_story.data.local.dto.BookEntity import ua.acclorite.book_story.domain.library.Book interface BookMapper { - suspend fun toBookEntity(book: Book): BookEntity - - suspend fun toBook(bookEntity: BookEntity): Book + fun toBookEntity(book: Book): BookEntity + fun toBook(bookEntity: BookEntity): Book } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/mapper/book/BookMapperImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/mapper/book/BookMapperImpl.kt index f3a355bf..0a8a1328 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/mapper/book/BookMapperImpl.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/mapper/book/BookMapperImpl.kt @@ -14,7 +14,7 @@ import ua.acclorite.book_story.domain.library.Book import javax.inject.Inject class BookMapperImpl @Inject constructor() : BookMapper { - override suspend fun toBookEntity(book: Book): BookEntity { + override fun toBookEntity(book: Book): BookEntity { return BookEntity( id = book.id, 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( id = bookEntity.id, title = bookEntity.title, diff --git a/app/src/main/java/ua/acclorite/book_story/data/mapper/category/CategoryMapper.kt b/app/src/main/java/ua/acclorite/book_story/data/mapper/category/CategoryMapper.kt index 36031f8b..d635f34a 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/mapper/category/CategoryMapper.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/mapper/category/CategoryMapper.kt @@ -10,7 +10,6 @@ import ua.acclorite.book_story.data.local.dto.CategoryEntity import ua.acclorite.book_story.domain.library.Category interface CategoryMapper { - suspend fun toCategoryEntity(category: Category): CategoryEntity - - suspend fun toCategory(categoryEntity: CategoryEntity): Category + fun toCategoryEntity(category: Category): CategoryEntity + fun toCategory(categoryEntity: CategoryEntity): Category } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/mapper/category/CategoryMapperImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/mapper/category/CategoryMapperImpl.kt index 85b27151..1426eec8 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/mapper/category/CategoryMapperImpl.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/mapper/category/CategoryMapperImpl.kt @@ -11,7 +11,7 @@ import ua.acclorite.book_story.domain.library.Category import javax.inject.Inject class CategoryMapperImpl @Inject constructor() : CategoryMapper { - override suspend fun toCategoryEntity(category: Category): CategoryEntity { + override fun toCategoryEntity(category: Category): CategoryEntity { return CategoryEntity( id = category.id, 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( id = categoryEntity.id, title = categoryEntity.title, diff --git a/app/src/main/java/ua/acclorite/book_story/data/mapper/category_sort/CategorySortMapper.kt b/app/src/main/java/ua/acclorite/book_story/data/mapper/category_sort/CategorySortMapper.kt index caa4b1ff..bbd97a84 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/mapper/category_sort/CategorySortMapper.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/mapper/category_sort/CategorySortMapper.kt @@ -10,7 +10,6 @@ import ua.acclorite.book_story.data.local.dto.CategorySortEntity import ua.acclorite.book_story.domain.library.CategorySort interface CategorySortMapper { - suspend fun toCategorySortEntity(categorySort: CategorySort): CategorySortEntity - - suspend fun toCategorySort(categorySortEntity: CategorySortEntity): CategorySort + fun toCategorySortEntity(categorySort: CategorySort): CategorySortEntity + fun toCategorySort(categorySortEntity: CategorySortEntity): CategorySort } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/mapper/category_sort/CategorySortSortMapperImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/mapper/category_sort/CategorySortSortMapperImpl.kt index 0e3779e3..1a6e4170 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/mapper/category_sort/CategorySortSortMapperImpl.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/mapper/category_sort/CategorySortSortMapperImpl.kt @@ -11,7 +11,7 @@ import ua.acclorite.book_story.domain.library.CategorySort import javax.inject.Inject class CategorySortSortMapperImpl @Inject constructor() : CategorySortMapper { - override suspend fun toCategorySortEntity(categorySort: CategorySort): CategorySortEntity { + override fun toCategorySortEntity(categorySort: CategorySort): CategorySortEntity { return CategorySortEntity( categoryId = categorySort.categoryId, 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( categoryId = categorySortEntity.categoryId, sortOrder = categorySortEntity.sortOrder, diff --git a/app/src/main/java/ua/acclorite/book_story/data/mapper/color_preset/ColorPresetMapper.kt b/app/src/main/java/ua/acclorite/book_story/data/mapper/color_preset/ColorPresetMapper.kt index caa23bac..27247d05 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/mapper/color_preset/ColorPresetMapper.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/mapper/color_preset/ColorPresetMapper.kt @@ -10,7 +10,6 @@ import ua.acclorite.book_story.data.local.dto.ColorPresetEntity import ua.acclorite.book_story.domain.reader.ColorPreset interface ColorPresetMapper { - suspend fun toColorPresetEntity(colorPreset: ColorPreset, order: Int): ColorPresetEntity - - suspend fun toColorPreset(colorPresetEntity: ColorPresetEntity): ColorPreset + fun toColorPresetEntity(colorPreset: ColorPreset, order: Int): ColorPresetEntity + fun toColorPreset(colorPresetEntity: ColorPresetEntity): ColorPreset } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/mapper/color_preset/ColorPresetMapperImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/mapper/color_preset/ColorPresetMapperImpl.kt index 566426a7..1db5a93b 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/mapper/color_preset/ColorPresetMapperImpl.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/mapper/color_preset/ColorPresetMapperImpl.kt @@ -12,7 +12,7 @@ import ua.acclorite.book_story.domain.reader.ColorPreset import javax.inject.Inject class ColorPresetMapperImpl @Inject constructor() : ColorPresetMapper { - override suspend fun toColorPresetEntity( + override fun toColorPresetEntity( colorPreset: ColorPreset, order: Int ): 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( id = colorPresetEntity.id!!, name = colorPresetEntity.name, diff --git a/app/src/main/java/ua/acclorite/book_story/data/mapper/file/FileMapper.kt b/app/src/main/java/ua/acclorite/book_story/data/mapper/file/FileMapper.kt new file mode 100644 index 00000000..03dfb1e3 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/data/mapper/file/FileMapper.kt @@ -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 +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/mapper/file/FileMapperImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/mapper/file/FileMapperImpl.kt new file mode 100644 index 00000000..18b55d1e --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/data/mapper/file/FileMapperImpl.kt @@ -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 + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/repository/BookRepositoryImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/repository/BookRepositoryImpl.kt index 1b7ab60a..ce66ee0c 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/repository/BookRepositoryImpl.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/repository/BookRepositoryImpl.kt @@ -6,399 +6,62 @@ 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.data.local.room.BookDao 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.TextParser import ua.acclorite.book_story.domain.library.Book import ua.acclorite.book_story.domain.reader.ReaderText import ua.acclorite.book_story.domain.repository.BookRepository -import java.io.BufferedOutputStream -import java.io.ByteArrayOutputStream -import java.io.File -import java.io.FileOutputStream -import java.util.UUID +import ua.acclorite.book_story.domain.service.FileProvider import javax.inject.Inject 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 class BookRepositoryImpl @Inject constructor( - private val application: Application, private val database: BookDao, - private val bookMapper: BookMapper, - private val fileParser: FileParser, - private val textParser: TextParser + private val textParser: TextParser, + private val fileProvider: FileProvider ) : BookRepository { - /** - * Get all books matching [query] from database. - * Empty [query] equals to all books. - */ - override suspend fun getBooks(query: String): List { - Log.i(GET_BOOKS, "Searching for books with query: \"$query\".") - val books = database.searchBooks(query) + override suspend fun searchBooks(query: String): Result> = runCatching { + database.searchBooks(query).map { bookMapper.toBook(it) } + } - Log.i(GET_BOOKS, "Found ${books.size} books.") - return books.map { entity -> - val book = bookMapper.toBook(entity) - val lastHistory = database.getLatestHistoryForBook( - book.id - ) - - book.copy( - lastOpened = lastHistory?.time - ) + override suspend fun getBook(bookId: Int): Result = runCatching { + database.findBookById(bookId).let { + if (it == null) throw NoSuchElementException("Couldn't get book [$bookId].") + else bookMapper.toBook(it) } } - /** - * Get all books that match given [ids]. - */ - override suspend fun getBooksById(ids: List): List { - Log.i(GET_BOOKS_BY_ID, "Getting books with ids: $ids.") - val books = database.findBooksById(ids) + override suspend fun getText(bookId: Int): Result> { + return getBook(bookId) + .mapCatching { fileProvider.getFileFromBook(it).getOrThrow() } + .mapCatching { textParser.parse(it) } + } - return books.map { entity -> - val book = bookMapper.toBook(entity) - val lastHistory = database.getLatestHistoryForBook( - book.id - ) + override suspend fun addBook(book: Book): Result = runCatching { + database.insertBook(bookMapper.toBookEntity(book)) + } - book.copy( - lastOpened = lastHistory?.time - ) + override suspend fun updateBook(book: Book): Result = runCatching { + database.updateBook(bookMapper.toBookEntity(book)).also { + if (it == 0) throw Exception("Could not update book in database.") } } - /** - * Loads text from the book. Already formatted. - */ - override suspend fun getBookText(bookId: Int): List { - 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() + override suspend fun deleteBook(book: Book): Result = runCatching { + database.deleteBook(bookMapper.toBookEntity(book)).also { + if (it == 0) throw Exception("Could not delete book in database.") } - - val readerText = textParser.parse(cachedFile) - - if ( - readerText.filterIsInstance().isEmpty() || - readerText.filterIsInstance().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( - coverImage = if (coverUri.isNotBlank()) { - Uri.fromFile(File("$coversDir/$coverUri")) - } 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) { - 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 + override suspend fun getDefaultCover(book: Book): Result = runCatching { + return fileProvider.getFileFromBook(book) + .mapCatching { fileParser.parse(it)?.coverImage } } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/repository/CategoryRepositoryImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/repository/CategoryRepositoryImpl.kt index 9b0e74af..bbb54483 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/repository/CategoryRepositoryImpl.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/repository/CategoryRepositoryImpl.kt @@ -22,7 +22,7 @@ class CategoryRepositoryImpl @Inject constructor( private val categorySortMapper: CategorySortMapper, ) : CategoryRepository { - override suspend fun insertCategory(category: Category) { + override suspend fun addCategory(category: Category): Result = runCatching { database.insertCategory( categoryMapper.toCategoryEntity( category.copy( @@ -32,39 +32,41 @@ class CategoryRepositoryImpl @Inject constructor( ) } - override suspend fun getCategories(): List { - return database.getCategories().map { categoryMapper.toCategory(it) }.sortedBy { it.order } + override suspend fun getCategories(): Result> = runCatching { + database.getCategories().map { categoryMapper.toCategory(it) }.sortedBy { it.order } } - override suspend fun updateCategoryTitle(id: Int, title: String) { + override suspend fun updateCategory(category: Category): Result = runCatching { database.updateCategoryTitle( - id = id, - title = title + id = category.id, + title = category.title ) - updateCategoriesOrder(getCategories()) + updateOrder(getCategories().getOrThrow()) } - override suspend fun updateCategoriesOrder(categories: List) { - categories.mapIndexed { index, category -> + override suspend fun updateOrder(categories: List): Result = runCatching { + categories.forEachIndexed { index, category -> database.updateCategoryOrder(category.id, index) } } - override suspend fun deleteCategory(category: Category) { + override suspend fun deleteCategory(category: Category): Result = runCatching { database.deleteCategory(categoryMapper.toCategoryEntity(category)) database.deleteCategorySortEntity(category.id) - updateCategoriesOrder(getCategories()) + updateOrder(getCategories().getOrThrow()) } - override suspend fun updateCategorySortEntity(categorySort: CategorySort) { + override suspend fun updateCategorySorting( + categorySort: CategorySort + ): Result = runCatching { database.updateCategorySort( categorySortMapper.toCategorySortEntity(categorySort) ) } - override suspend fun getCategorySortEntities(): List { - return database.getCategorySortEntities().map { + override suspend fun getCategorySorting(): Result> = runCatching { + database.getCategorySortEntities().map { categorySortMapper.toCategorySort(it) } } diff --git a/app/src/main/java/ua/acclorite/book_story/data/repository/ColorPresetRepositoryImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/repository/ColorPresetRepositoryImpl.kt index a42ebab8..e803cce5 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/repository/ColorPresetRepositoryImpl.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/repository/ColorPresetRepositoryImpl.kt @@ -20,64 +20,51 @@ import javax.inject.Singleton @Singleton class ColorPresetRepositoryImpl @Inject constructor( private val database: BookDao, - private val colorPresetMapper: ColorPresetMapper ) : ColorPresetRepository { - /** - * Update color preset. - */ - override suspend fun updateColorPreset(colorPreset: ColorPreset) { + override suspend fun getColorPresets(): Result> = runCatching { + database.getColorPresets() + .sortedBy { it.order } + .map { colorPresetMapper.toColorPreset(it) } + } + + override suspend fun updateColorPreset( + colorPreset: ColorPreset + ): Result = runCatching { database.updateColorPreset( colorPresetMapper.toColorPresetEntity( - colorPreset, - if (colorPreset.id != -1) database.getColorPresetOrder(colorPreset.id) + colorPreset = colorPreset, + order = if (colorPreset.id != -1) database.getColorPresetOrder(colorPreset.id) else database.getColorPresetsSize() ) ) } - /** - * Select color preset. Only one can be selected at time. - */ - override suspend fun selectColorPreset(colorPreset: ColorPreset) { + override suspend fun selectColorPreset( + colorPreset: ColorPreset + ): Result = runCatching { database.getColorPresets().map { - it.copy( - isSelected = it.id == colorPreset.id - ) + it.copy(isSelected = it.id == colorPreset.id) }.forEach { database.updateColorPreset(it) } } - /** - * Get all color presets. - * Sorted by order (either manual or newest ones at the end). - */ - override suspend fun getColorPresets(): List { - 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) { + override suspend fun reorderColorPresets( + colorPresets: List + ): Result = runCatching { database.deleteColorPresets() - - orderedColorPresets.forEachIndexed { index, colorPreset -> + colorPresets.forEachIndexed { index, colorPreset -> database.updateColorPreset( colorPresetMapper.toColorPresetEntity(colorPreset, order = index) ) } } - /** - * Delete color preset. - */ - override suspend fun deleteColorPreset(colorPreset: ColorPreset) { + override suspend fun deleteColorPreset( + colorPreset: ColorPreset + ): Result = runCatching { database.deleteColorPreset( colorPresetMapper.toColorPresetEntity( colorPreset, -1 diff --git a/app/src/main/java/ua/acclorite/book_story/data/repository/DataStoreRepositoryImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/repository/DataStoreRepositoryImpl.kt index 285a9207..501a38a8 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/repository/DataStoreRepositoryImpl.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/repository/DataStoreRepositoryImpl.kt @@ -6,22 +6,12 @@ package ua.acclorite.book_story.data.repository -import android.util.Log 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.domain.repository.DataStoreRepository -import ua.acclorite.book_story.presentation.main.MainState -import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject import javax.inject.Singleton -private const val GET_ALL_SETTINGS = "GET SETTINGS, REPO" - /** * Data Store repository. * Manages all [DataStore] related work. @@ -31,42 +21,20 @@ class DataStoreRepositoryImpl @Inject constructor( private val dataStore: DataStore ) : DataStoreRepository { - /** - * Puts DataStore constant to [DataStore]. - */ - override suspend fun putDataToDataStore(key: Preferences.Key, value: T) { + override suspend fun putPreference( + key: Preferences.Key, + value: T + ): Result = runCatching { dataStore.putData(key, value) } - /** - * Gets all settings from DataStore and returns [MainState]. - */ - override suspend fun getAllSettings(): MainState { - Log.i(GET_ALL_SETTINGS, "Getting all settings.") - val result = CompletableDeferred() + override suspend fun getPreference( + key: Preferences.Key + ): Result = runCatching { + dataStore.getNullableData(key) ?: throw NoSuchElementException("Could not get preference.") + } - withContext(Dispatchers.Default) { - val keys = dataStore.getAllData() - val data = ConcurrentHashMap() - - 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>> = runCatching { + dataStore.getAllData() ?: throw NoSuchElementException("Could not get all preferences.") } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/repository/FileSystemRepositoryImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/repository/FileSystemRepositoryImpl.kt index 6d543bd0..9d335f7c 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/repository/FileSystemRepositoryImpl.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/repository/FileSystemRepositoryImpl.kt @@ -6,165 +6,68 @@ 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.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.data.mapper.file.FileMapper 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.domain.file.File 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 java.util.UUID import javax.inject.Inject 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. * Manages all File System related work. */ @Singleton class FileSystemRepositoryImpl @Inject constructor( - private val application: Application, private val database: BookDao, - private val fileParser: FileParser + private val fileMapper: FileMapper, + private val fileParser: FileParser, + private val fileProvider: FileProvider ) : FileSystemRepository { - /** - * Gets all matching files from device. - * Filters by [query] and sorts out not supported file formats and already added files. - */ - override suspend fun getFiles(query: String): List { - Log.i(GET_FILES, "Getting files from device, query: \"$query\".") + override suspend fun searchFiles(query: String): Result> { + return fileProvider.getStorageFiles().mapCatching { storages -> + val existingFiles = database.searchBooks("").map { it.filePath } - val existingPaths = database - .searchBooks("") - .map { it.filePath } - val supportedExtensions = provideExtensions() - - /** - * 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 - existingPaths.none { existingPath -> - existingPath.equals(path, ignoreCase = true) - }.let { if (!it) return false } - - return true - } - - fun CachedFile.getSelectableFilesFromStorage(): List { - val files = mutableListOf() - - walk { file -> - if (!file.isValid()) return@walk - - files.add( - File( - name = file.name, - uri = file.uri, - path = file.path, - size = file.size, - lastModified = file.lastModified, - isDirectory = file.isDirectory - ) + storages.map { storage -> + storage.getFilesFromStorage( + query = query, + existingFiles = existingFiles ) - } - - return files - } - - fun getAllStorages(): List { - return application.contentResolver.persistedUriPermissions.mapNotNull { permission -> - val storage = CachedFileCompat.fromUri( - 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() - - 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() + }.flatten() } } - /** - * 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 - ) - ) + private fun CachedFile.isValid( + query: String, + existingFiles: List + ): 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 + } - 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) - ) + private fun CachedFile.getFilesFromStorage( + query: String, + existingFiles: List + ): List { + val files = mutableListOf() + walk { cachedFile -> + if (cachedFile.isValid(query, existingFiles)) { + files.add(fileMapper.toFile(cachedFile)) + } } + return files + } - Log.i(GET_BOOK_FROM_FILE, "Successfully got book from file.") - return NotNull(bookWithCover = parsedBook) + override suspend fun getBookFromFile(file: File): Result = runCatching { + fileParser.parse(fileMapper.toCachedFile(file)) + ?: throw Exception("Could not parse ${file.name}.") } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/repository/HistoryRepositoryImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/repository/HistoryRepositoryImpl.kt index c35de84a..f7cad22a 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/repository/HistoryRepositoryImpl.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/repository/HistoryRepositoryImpl.kt @@ -13,73 +13,42 @@ import ua.acclorite.book_story.domain.repository.HistoryRepository import javax.inject.Inject import javax.inject.Singleton -/** - * History repository. - * Manages all [History] related work. - */ @Singleton class HistoryRepositoryImpl @Inject constructor( private val database: BookDao, - - private val historyMapper: HistoryMapper, + private val historyMapper: HistoryMapper ) : HistoryRepository { - /** - * Insert history in database. - */ - override suspend fun insertHistory(history: History) { - database.insertHistory( - listOf( - historyMapper.toHistoryEntity( - history - ) - ) - ) - } - - /** - * Get all history from database. - */ - override suspend fun getHistory(): List { - return database.getHistory().map { - historyMapper.toHistory( - it - ) + override suspend fun getHistoryForBook(bookId: Int): Result = runCatching { + database.getHistoryForBook(bookId).let { + if (it == null) throw NoSuchElementException("Could not get history from [$bookId].") + else historyMapper.toHistory(it) } } - /** - * Get latest history of the matching [bookId]. - */ - override suspend fun getLatestBookHistory(bookId: Int): History? { - val history = database.getLatestHistoryForBook(bookId) - return history?.let { historyMapper.toHistory(it) } + override suspend fun addHistory(history: History): Result = runCatching { + database.insertHistory(historyMapper.toHistoryEntity(history)) } - /** - * Delete whole history. - */ - override suspend fun deleteWholeHistory() { - database.deleteWholeHistory() + override suspend fun getHistory(): Result> = runCatching { + database.getHistory().map { historyMapper.toHistory(it) } } - /** - * Delete all history of the matching [bookId]. - */ - override suspend fun deleteBookHistory(bookId: Int) { - database.deleteBookHistory(bookId) + override suspend fun deleteWholeHistory(): Result = runCatching { + database.deleteWholeHistory().also { + if (it == 0) throw Exception("Could not delete whole history in database.") + } } - /** - * Delete specific history item. - */ - override suspend fun deleteHistory(history: History) { - database.deleteHistory( - listOf( - historyMapper.toHistoryEntity( - history - ) - ) - ) + override suspend fun deleteHistoryForBook(bookId: Int): Result = runCatching { + database.deleteHistoryForBook(bookId = bookId).also { + if (it == 0) throw Exception("Could not delete history for book [$bookId] in database.") + } + } + + override suspend fun deleteHistory(history: History): Result = runCatching { + database.deleteHistory(historyMapper.toHistoryEntity(history)).also { + if (it == 0) throw Exception("Could not delete history in database.") + } } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/repository/PermissionRepositoryImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/repository/PermissionRepositoryImpl.kt index 198ba4a5..1753665c 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/repository/PermissionRepositoryImpl.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/repository/PermissionRepositoryImpl.kt @@ -8,45 +8,27 @@ package ua.acclorite.book_story.data.repository import android.app.Application import android.content.Intent -import android.net.Uri -import android.util.Log +import androidx.core.net.toUri import ua.acclorite.book_story.domain.repository.PermissionRepository import javax.inject.Inject import javax.inject.Singleton -private const val GRANT_URI = "GRANT URI PERM, REPO" -private const val RELEASE_URI = "RELEASE URI PERM, REPO" - @Singleton class PermissionRepositoryImpl @Inject constructor( private val application: Application ) : PermissionRepository { - override suspend fun grantPersistableUriPermission(uri: Uri) { - Log.i(GRANT_URI, "Granting persistable uri permission to \"${uri.path}\" URI.") - - try { - application.contentResolver.takePersistableUriPermission( - uri, - 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 grantPersistableUriPermission(uri: String): Result = runCatching { + application.contentResolver.takePersistableUriPermission( + uri.toUri(), + Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION + ) } - override suspend fun releasePersistableUriPermission(uri: Uri) { - Log.i(RELEASE_URI, "Releasing persistable uri permission from \"${uri.path}\" URI.") - - try { - application.contentResolver.releasePersistableUriPermission( - uri, - 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.") - } + override suspend fun releasePersistableUriPermission(uri: String): Result = runCatching { + application.contentResolver.releasePersistableUriPermission( + uri.toUri(), + Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION + ) } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/service/CoverImageHandlerImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/service/CoverImageHandlerImpl.kt new file mode 100644 index 00000000..744650cf --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/data/service/CoverImageHandlerImpl.kt @@ -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 = 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 = runCatching { + coverImage.toFile().apply { + if (exists() && !delete()) throw Exception("Couldn't delete cover image.") + } + } + + override suspend fun compressCover(coverImage: CoverImage): Result = 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) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/service/FileProviderImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/service/FileProviderImpl.kt new file mode 100644 index 00000000..01da7ddf --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/data/service/FileProviderImpl.kt @@ -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 = 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> = 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 + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/file/File.kt b/app/src/main/java/ua/acclorite/book_story/domain/file/File.kt index 45c8e985..67e3a836 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/file/File.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/file/File.kt @@ -6,13 +6,12 @@ package ua.acclorite.book_story.domain.file -import android.net.Uri import androidx.compose.runtime.Immutable @Immutable data class File( val name: String, - val uri: Uri, + val uri: String, val path: String, val size: Long, val lastModified: Long, diff --git a/app/src/main/java/ua/acclorite/book_story/domain/repository/BookRepository.kt b/app/src/main/java/ua/acclorite/book_story/domain/repository/BookRepository.kt index 988c8d27..0a9bc798 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/repository/BookRepository.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/repository/BookRepository.kt @@ -7,46 +7,35 @@ package ua.acclorite.book_story.domain.repository 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.reader.ReaderText interface BookRepository { - - suspend fun getBooks( + suspend fun searchBooks( query: String - ): List + ): Result> - suspend fun getBooksById( - ids: List - ): List - - suspend fun getBookText( + suspend fun getBook( bookId: Int - ): List + ): Result - suspend fun insertBook( - bookWithCover: BookWithCover - ) + suspend fun getText( + bookId: Int + ): Result> + + suspend fun addBook( + book: Book + ): Result suspend fun updateBook( book: Book - ) + ): Result - suspend fun updateCoverImageOfBook( - bookWithOldCover: Book, - newCoverImage: CoverImage? - ) + suspend fun deleteBook( + book: Book + ): Result - suspend fun deleteBooks( - books: List - ) - - suspend fun canResetCover( - bookId: Int - ): Boolean - - suspend fun resetCoverImage( - bookId: Int - ): Boolean + suspend fun getDefaultCover( + book: Book + ): Result } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/repository/CategoryRepository.kt b/app/src/main/java/ua/acclorite/book_story/domain/repository/CategoryRepository.kt index e0d4dfbb..abb41e0a 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/repository/CategoryRepository.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/repository/CategoryRepository.kt @@ -10,29 +10,27 @@ import ua.acclorite.book_story.domain.library.Category import ua.acclorite.book_story.domain.library.CategorySort interface CategoryRepository { - - suspend fun insertCategory( + suspend fun addCategory( category: Category - ) + ): Result - suspend fun getCategories(): List + suspend fun getCategories(): Result> - suspend fun updateCategoryTitle( - id: Int, - title: String - ) + suspend fun updateCategory( + category: Category + ): Result - suspend fun updateCategoriesOrder( + suspend fun updateOrder( categories: List - ) + ): Result suspend fun deleteCategory( category: Category - ) + ): Result - suspend fun updateCategorySortEntity( + suspend fun updateCategorySorting( categorySort: CategorySort - ) + ): Result - suspend fun getCategorySortEntities(): List + suspend fun getCategorySorting(): Result> } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/repository/ColorPresetRepository.kt b/app/src/main/java/ua/acclorite/book_story/domain/repository/ColorPresetRepository.kt index 57604a59..507ff8ce 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/repository/ColorPresetRepository.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/repository/ColorPresetRepository.kt @@ -9,22 +9,21 @@ package ua.acclorite.book_story.domain.repository import ua.acclorite.book_story.domain.reader.ColorPreset interface ColorPresetRepository { + suspend fun getColorPresets(): Result> suspend fun updateColorPreset( colorPreset: ColorPreset - ) + ): Result suspend fun selectColorPreset( colorPreset: ColorPreset - ) - - suspend fun getColorPresets(): List + ): Result suspend fun reorderColorPresets( - orderedColorPresets: List - ) + colorPresets: List + ): Result suspend fun deleteColorPreset( colorPreset: ColorPreset - ) + ): Result } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/repository/DataStoreRepository.kt b/app/src/main/java/ua/acclorite/book_story/domain/repository/DataStoreRepository.kt index aa71fe5d..17111b8b 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/repository/DataStoreRepository.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/repository/DataStoreRepository.kt @@ -7,14 +7,16 @@ package ua.acclorite.book_story.domain.repository import androidx.datastore.preferences.core.Preferences -import ua.acclorite.book_story.presentation.main.MainState interface DataStoreRepository { - - suspend fun putDataToDataStore( + suspend fun putPreference( key: Preferences.Key, value: T - ) + ): Result - suspend fun getAllSettings(): MainState + suspend fun getPreference( + key: Preferences.Key, + ): Result + + suspend fun getAllPreferences(): Result>> } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/repository/FileSystemRepository.kt b/app/src/main/java/ua/acclorite/book_story/domain/repository/FileSystemRepository.kt index 8e30a808..67d3606c 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/repository/FileSystemRepository.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/repository/FileSystemRepository.kt @@ -6,16 +6,15 @@ 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 interface FileSystemRepository { - - suspend fun getFiles( + suspend fun searchFiles( query: String = "" - ): List + ): Result> suspend fun getBookFromFile( file: File - ): NullableBook + ): Result } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/repository/HistoryRepository.kt b/app/src/main/java/ua/acclorite/book_story/domain/repository/HistoryRepository.kt index 09f5754f..c08cc2a5 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/repository/HistoryRepository.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/repository/HistoryRepository.kt @@ -9,24 +9,23 @@ package ua.acclorite.book_story.domain.repository import ua.acclorite.book_story.domain.history.History interface HistoryRepository { + suspend fun getHistoryForBook( + bookId: Int + ): Result - suspend fun insertHistory( + suspend fun addHistory( history: History - ) + ): Result - suspend fun getHistory(): List + suspend fun getHistory(): Result> - suspend fun getLatestBookHistory( + suspend fun deleteWholeHistory(): Result + + suspend fun deleteHistoryForBook( bookId: Int - ): History? - - suspend fun deleteWholeHistory() - - suspend fun deleteBookHistory( - bookId: Int - ) + ): Result suspend fun deleteHistory( history: History - ) + ): Result } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/repository/PermissionRepository.kt b/app/src/main/java/ua/acclorite/book_story/domain/repository/PermissionRepository.kt index 05014e9e..b3924be5 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/repository/PermissionRepository.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/repository/PermissionRepository.kt @@ -6,15 +6,12 @@ package ua.acclorite.book_story.domain.repository -import android.net.Uri - interface PermissionRepository { - suspend fun grantPersistableUriPermission( - uri: Uri - ) + uri: String + ): Result suspend fun releasePersistableUriPermission( - uri: Uri - ) + uri: String + ): Result } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/service/CoverImageHandler.kt b/app/src/main/java/ua/acclorite/book_story/domain/service/CoverImageHandler.kt new file mode 100644 index 00000000..7268d413 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/service/CoverImageHandler.kt @@ -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 + suspend fun deleteCover(coverImage: Uri): Result + suspend fun compressCover(coverImage: CoverImage): Result +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/service/FileProvider.kt b/app/src/main/java/ua/acclorite/book_story/domain/service/FileProvider.kt new file mode 100644 index 00000000..460f2cd1 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/service/FileProvider.kt @@ -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 + fun getStorageFiles(): Result> +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/AddBookUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/AddBookUseCase.kt new file mode 100644 index 00000000..f1e17e9f --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/AddBookUseCase.kt @@ -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}") + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/CanResetCover.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/CanResetCover.kt deleted file mode 100644 index 209bf6be..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/CanResetCover.kt +++ /dev/null @@ -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) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/CanResetCoverImageUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/CanResetCoverImageUseCase.kt new file mode 100644 index 00000000..1d2469fc --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/CanResetCoverImageUseCase.kt @@ -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 + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/DeleteBookUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/DeleteBookUseCase.kt new file mode 100644 index 00000000..ff886204 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/DeleteBookUseCase.kt @@ -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}") + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/DeleteBooks.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/DeleteBooks.kt deleted file mode 100644 index a97c293c..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/DeleteBooks.kt +++ /dev/null @@ -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) { - bookRepository.deleteBooks(books) - books.forEach { - historyRepository.deleteBookHistory(it.id) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetBookById.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetBookById.kt deleted file mode 100644 index 23b3e1d5..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetBookById.kt +++ /dev/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 GetBookById @Inject constructor( - private val repository: BookRepository -) { - - suspend fun execute(id: Int): Book? { - return repository.getBooksById(listOf(id)).firstOrNull() - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetBookUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetBookUseCase.kt new file mode 100644 index 00000000..3271e004 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetBookUseCase.kt @@ -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 + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetBooks.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetBooks.kt deleted file mode 100644 index cb12d117..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetBooks.kt +++ /dev/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 { - return repository.getBooks(query) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetBooksById.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetBooksById.kt deleted file mode 100644 index 03c5fcc9..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetBooksById.kt +++ /dev/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 GetBooksById @Inject constructor( - private val repository: BookRepository -) { - - suspend fun execute(ids: List): List { - return repository.getBooksById(ids) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetText.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetText.kt deleted file mode 100644 index 1ad98d71..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetText.kt +++ /dev/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.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 { - return repository.getBookText(bookId = bookId) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetTextUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetTextUseCase.kt new file mode 100644 index 00000000..e6cf44d8 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetTextUseCase.kt @@ -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 { + logI("Getting text from: [$bookId].") + if (bookId == -1) return emptyList() + + bookRepository.getText(bookId).mapCatching { readerText -> + if ( + readerText.filterIsInstance().isEmpty() || + readerText.filterIsInstance().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() + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/InsertBook.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/InsertBook.kt deleted file mode 100644 index d9c85d5d..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/InsertBook.kt +++ /dev/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.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) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/ResetCoverImage.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/ResetCoverImage.kt deleted file mode 100644 index 88cdc4f6..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/ResetCoverImage.kt +++ /dev/null @@ -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) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/ResetCoverImageUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/ResetCoverImageUseCase.kt new file mode 100644 index 00000000..77fe0af6 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/ResetCoverImageUseCase.kt @@ -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 + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/SearchBooksUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/SearchBooksUseCase.kt new file mode 100644 index 00000000..f77c3d2e --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/SearchBooksUseCase.kt @@ -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 { + 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() + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/UpdateBook.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/UpdateBook.kt deleted file mode 100644 index 23076651..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/UpdateBook.kt +++ /dev/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 UpdateBook @Inject constructor( - private val repository: BookRepository -) { - - suspend fun execute(book: Book) { - repository.updateBook(book) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/UpdateBookUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/UpdateBookUseCase.kt new file mode 100644 index 00000000..fd8b3fde --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/UpdateBookUseCase.kt @@ -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}") + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/UpdateCoverImageOfBook.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/UpdateCoverImageOfBook.kt deleted file mode 100644 index 2ea11a87..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/UpdateCoverImageOfBook.kt +++ /dev/null @@ -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) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/UpdateCoverImageUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/UpdateCoverImageUseCase.kt new file mode 100644 index 00000000..7a0c4680 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/UpdateCoverImageUseCase.kt @@ -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}") + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/AddCategoryUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/AddCategoryUseCase.kt new file mode 100644 index 00000000..cd4b1936 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/AddCategoryUseCase.kt @@ -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}") + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/DeleteCategory.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/DeleteCategory.kt deleted file mode 100644 index 8b8810ac..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/DeleteCategory.kt +++ /dev/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.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) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/DeleteCategoryUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/DeleteCategoryUseCase.kt new file mode 100644 index 00000000..ed974300 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/DeleteCategoryUseCase.kt @@ -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}") + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/GetCategories.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/GetCategories.kt deleted file mode 100644 index 7742db96..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/GetCategories.kt +++ /dev/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.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 { - return repository.getCategories() - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/GetCategoriesUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/GetCategoriesUseCase.kt new file mode 100644 index 00000000..82d853c9 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/GetCategoriesUseCase.kt @@ -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 { + 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() + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/GetCategorySort.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/GetCategorySort.kt deleted file mode 100644 index 83288641..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/GetCategorySort.kt +++ /dev/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.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 { - return repository.getCategorySortEntities() - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/GetCategorySortingUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/GetCategorySortingUseCase.kt new file mode 100644 index 00000000..42c8288e --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/GetCategorySortingUseCase.kt @@ -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 { + 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() + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/InsertCategory.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/InsertCategory.kt deleted file mode 100644 index 6bfe963b..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/InsertCategory.kt +++ /dev/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.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) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/UpdateCategoriesOrderUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/UpdateCategoriesOrderUseCase.kt new file mode 100644 index 00000000..5a33cc1a --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/UpdateCategoriesOrderUseCase.kt @@ -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) { + 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}") + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/UpdateCategoryOrder.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/UpdateCategoryOrder.kt deleted file mode 100644 index 66946145..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/UpdateCategoryOrder.kt +++ /dev/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.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) { - repository.updateCategoriesOrder(categories) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/UpdateCategorySort.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/UpdateCategorySort.kt deleted file mode 100644 index 7c62af4b..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/UpdateCategorySort.kt +++ /dev/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.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) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/UpdateCategorySortingUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/UpdateCategorySortingUseCase.kt new file mode 100644 index 00000000..966b5e59 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/UpdateCategorySortingUseCase.kt @@ -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}" + ) + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/UpdateCategoryTitle.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/UpdateCategoryTitle.kt deleted file mode 100644 index f16cf210..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/UpdateCategoryTitle.kt +++ /dev/null @@ -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) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/UpdateCategoryUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/UpdateCategoryUseCase.kt new file mode 100644 index 00000000..f34bdd3e --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/category/UpdateCategoryUseCase.kt @@ -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}") + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/DeleteColorPreset.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/DeleteColorPreset.kt deleted file mode 100644 index 3b12db02..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/DeleteColorPreset.kt +++ /dev/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.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) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/DeleteColorPresetUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/DeleteColorPresetUseCase.kt new file mode 100644 index 00000000..ac0ff5bd --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/DeleteColorPresetUseCase.kt @@ -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}") + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/GetColorPresets.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/GetColorPresets.kt deleted file mode 100644 index f7e2f044..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/GetColorPresets.kt +++ /dev/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.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 { - return repository.getColorPresets() - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/GetColorPresetsUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/GetColorPresetsUseCase.kt new file mode 100644 index 00000000..480aeddc --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/GetColorPresetsUseCase.kt @@ -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 { + 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() + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/ReorderColorPresets.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/ReorderColorPresets.kt deleted file mode 100644 index e81de1c9..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/ReorderColorPresets.kt +++ /dev/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.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) { - repository.reorderColorPresets(reorderedColorPresets) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/ReorderColorPresetsUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/ReorderColorPresetsUseCase.kt new file mode 100644 index 00000000..f11d878d --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/ReorderColorPresetsUseCase.kt @@ -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) { + 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}") + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/SelectColorPreset.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/SelectColorPreset.kt deleted file mode 100644 index cfcee6a5..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/SelectColorPreset.kt +++ /dev/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.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) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/SelectColorPresetUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/SelectColorPresetUseCase.kt new file mode 100644 index 00000000..7099e495 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/SelectColorPresetUseCase.kt @@ -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}") + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/UpdateColorPreset.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/UpdateColorPreset.kt deleted file mode 100644 index 0eaa66b6..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/UpdateColorPreset.kt +++ /dev/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.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) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/UpdateColorPresetUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/UpdateColorPresetUseCase.kt new file mode 100644 index 00000000..a16488a0 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/color_preset/UpdateColorPresetUseCase.kt @@ -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}") + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/data_store/ChangeLanguage.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/data_store/ChangeLanguage.kt deleted file mode 100644 index 2d1c0925..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/data_store/ChangeLanguage.kt +++ /dev/null @@ -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 - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/data_store/ChangeLanguagePreferenceUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/data_store/ChangeLanguagePreferenceUseCase.kt new file mode 100644 index 00000000..2b9e88ea --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/data_store/ChangeLanguagePreferenceUseCase.kt @@ -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}") + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/data_store/GetAllSettings.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/data_store/GetAllSettings.kt deleted file mode 100644 index 4c4216c4..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/data_store/GetAllSettings.kt +++ /dev/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.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() - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/data_store/GetPreferencesUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/data_store/GetPreferencesUseCase.kt new file mode 100644 index 00000000..418c6e4e --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/data_store/GetPreferencesUseCase.kt @@ -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() + 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() + } + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/data_store/PutPreferenceUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/data_store/PutPreferenceUseCase.kt new file mode 100644 index 00000000..0fd17bf8 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/data_store/PutPreferenceUseCase.kt @@ -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 invoke(key: Preferences.Key, 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}") + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/data_store/SetDatastore.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/data_store/SetDatastore.kt deleted file mode 100644 index 5defb507..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/data_store/SetDatastore.kt +++ /dev/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.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 execute(key: Preferences.Key, value: T) { - repository.putDataToDataStore(key, value) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/file_system/GetBookFromFile.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/file_system/GetBookFromFile.kt deleted file mode 100644 index cd9ca4dd..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/file_system/GetBookFromFile.kt +++ /dev/null @@ -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) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/file_system/GetBookFromFileUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/file_system/GetBookFromFileUseCase.kt new file mode 100644 index 00000000..12325c35 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/file_system/GetBookFromFileUseCase.kt @@ -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) + ) + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/file_system/GetFiles.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/file_system/GetFiles.kt deleted file mode 100644 index 39106233..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/file_system/GetFiles.kt +++ /dev/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.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 { - return repository.getFiles(query) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/file_system/SearchFilesUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/file_system/SearchFilesUseCase.kt new file mode 100644 index 00000000..6b55c857 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/file_system/SearchFilesUseCase.kt @@ -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 { + 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() + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/history/AddHistoryUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/history/AddHistoryUseCase.kt new file mode 100644 index 00000000..8e5a8474 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/history/AddHistoryUseCase.kt @@ -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}") + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/history/DeleteHistory.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/history/DeleteHistory.kt deleted file mode 100644 index 1ebda6f8..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/history/DeleteHistory.kt +++ /dev/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.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) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/history/DeleteHistoryUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/history/DeleteHistoryUseCase.kt new file mode 100644 index 00000000..0d8983a4 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/history/DeleteHistoryUseCase.kt @@ -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}") + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/history/DeleteWholeHistory.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/history/DeleteWholeHistory.kt deleted file mode 100644 index 8cf0d43b..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/history/DeleteWholeHistory.kt +++ /dev/null @@ -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() - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/history/DeleteWholeHistoryUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/history/DeleteWholeHistoryUseCase.kt new file mode 100644 index 00000000..78df36aa --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/history/DeleteWholeHistoryUseCase.kt @@ -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}") + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/history/GetHistory.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/history/GetHistory.kt deleted file mode 100644 index c66a6dc9..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/history/GetHistory.kt +++ /dev/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.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 { - return repository.getHistory() - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/history/GetHistoryForBookUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/history/GetHistoryForBookUseCase.kt new file mode 100644 index 00000000..8052b788 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/history/GetHistoryForBookUseCase.kt @@ -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 + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/history/GetHistoryUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/history/GetHistoryUseCase.kt new file mode 100644 index 00000000..bb38d9ae --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/history/GetHistoryUseCase.kt @@ -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 { + 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() + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/history/GetLatestHistory.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/history/GetLatestHistory.kt deleted file mode 100644 index 2ecb8aa5..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/history/GetLatestHistory.kt +++ /dev/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.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) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/history/InsertHistory.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/history/InsertHistory.kt deleted file mode 100644 index 4db16edf..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/history/InsertHistory.kt +++ /dev/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.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) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/permission/GrantPersistableUriPermission.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/permission/GrantPersistableUriPermission.kt deleted file mode 100644 index 2d802f50..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/permission/GrantPersistableUriPermission.kt +++ /dev/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.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) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/permission/GrantPersistableUriPermissionUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/permission/GrantPersistableUriPermissionUseCase.kt new file mode 100644 index 00000000..06cbc534 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/permission/GrantPersistableUriPermissionUseCase.kt @@ -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}.") + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/permission/ReleasePersistableUriPermission.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/permission/ReleasePersistableUriPermission.kt deleted file mode 100644 index b7c7c106..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/permission/ReleasePersistableUriPermission.kt +++ /dev/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.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) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/permission/ReleasePersistableUriPermissionUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/permission/ReleasePersistableUriPermissionUseCase.kt new file mode 100644 index 00000000..069d4238 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/permission/ReleasePersistableUriPermissionUseCase.kt @@ -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}.") + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoModel.kt index 70175b75..d502f7b3 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoModel.kt @@ -21,12 +21,12 @@ import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import kotlinx.coroutines.yield import ua.acclorite.book_story.R -import ua.acclorite.book_story.domain.use_case.book.CanResetCover -import ua.acclorite.book_story.domain.use_case.book.DeleteBooks -import ua.acclorite.book_story.domain.use_case.book.GetBookById -import ua.acclorite.book_story.domain.use_case.book.ResetCoverImage -import ua.acclorite.book_story.domain.use_case.book.UpdateBook -import ua.acclorite.book_story.domain.use_case.book.UpdateCoverImageOfBook +import ua.acclorite.book_story.domain.use_case.book.CanResetCoverImageUseCase +import ua.acclorite.book_story.domain.use_case.book.DeleteBookUseCase +import ua.acclorite.book_story.domain.use_case.book.GetBookUseCase +import ua.acclorite.book_story.domain.use_case.book.ResetCoverImageUseCase +import ua.acclorite.book_story.domain.use_case.book.UpdateBookUseCase +import ua.acclorite.book_story.domain.use_case.book.UpdateCoverImageUseCase import ua.acclorite.book_story.presentation.browse.BrowseScreen import ua.acclorite.book_story.presentation.history.HistoryScreen import ua.acclorite.book_story.presentation.library.LibraryScreen @@ -35,12 +35,12 @@ import javax.inject.Inject @HiltViewModel class BookInfoModel @Inject constructor( - private val getBookById: GetBookById, - private val updateBook: UpdateBook, - private val canResetCover: CanResetCover, - private val updateCoverImageOfBook: UpdateCoverImageOfBook, - private val resetCoverImage: ResetCoverImage, - private val deleteBooks: DeleteBooks + private val updateCoverImageUseCase: UpdateCoverImageUseCase, + private val updateBookUseCase: UpdateBookUseCase, + private val getBookUseCase: GetBookUseCase, + private val deleteBookUseCase: DeleteBookUseCase, + private val canResetCoverImageUseCase: CanResetCoverImageUseCase, + private val resetCoverImageUseCase: ResetCoverImageUseCase ) : ViewModel() { private val mutex = Mutex() @@ -76,14 +76,10 @@ class BookInfoModel @Inject constructor( BitmapFactory.decodeStream(it) } ?: return@launch - updateCoverImageOfBook.execute( - _state.value.book, - image - ) + updateCoverImageUseCase(_state.value.book.id, image) - val newCoverImage = getBookById.execute( - _state.value.book.id - )?.coverImage ?: return@launch + val newCoverImage = getBookUseCase(_state.value.book.id)?.coverImage + ?: return@launch _state.update { it.copy( @@ -91,7 +87,7 @@ class BookInfoModel @Inject constructor( coverImage = newCoverImage ), bottomSheet = null, - canResetCover = canResetCover.execute(bookId = it.book.id) + canResetCover = canResetCoverImageUseCase(it.book.id) ) } @@ -107,7 +103,7 @@ class BookInfoModel @Inject constructor( is BookInfoEvent.OnResetCover -> { launch { - val result = resetCoverImage.execute(_state.value.book.id) + val result = resetCoverImageUseCase(_state.value.book.id) if (!result) { withContext(Dispatchers.Main) { @@ -117,7 +113,7 @@ class BookInfoModel @Inject constructor( return@launch } - val book = getBookById.execute(_state.value.book.id) + val book = getBookUseCase(_state.value.book.id) if (book == null) { withContext(Dispatchers.Main) { @@ -151,17 +147,14 @@ class BookInfoModel @Inject constructor( return@launch } - updateCoverImageOfBook.execute( - bookWithOldCover = _state.value.book, - newCoverImage = null - ) + updateCoverImageUseCase(_state.value.book.id, null) _state.update { it.copy( book = it.book.copy( coverImage = null ), bottomSheet = null, - canResetCover = canResetCover.execute(bookId = it.book.id) + canResetCover = canResetCoverImageUseCase(it.book.id) ) } @@ -178,7 +171,7 @@ class BookInfoModel @Inject constructor( is BookInfoEvent.OnCheckCoverReset -> { launch(Dispatchers.IO) { if (_state.value.book.id == -1) return@launch - canResetCover.execute(_state.value.book.id).apply { + canResetCoverImageUseCase(_state.value.book.id).apply { _state.update { it.copy( canResetCover = this @@ -213,7 +206,7 @@ class BookInfoModel @Inject constructor( ) ) } - updateBook.execute(_state.value.book) + updateBookUseCase(_state.value.book) LibraryScreen.refreshListChannel.trySend(0) HistoryScreen.refreshListChannel.trySend(0) @@ -242,7 +235,7 @@ class BookInfoModel @Inject constructor( ) ) } - updateBook.execute(_state.value.book) + updateBookUseCase(_state.value.book) LibraryScreen.refreshListChannel.trySend(0) HistoryScreen.refreshListChannel.trySend(0) @@ -271,7 +264,7 @@ class BookInfoModel @Inject constructor( ) ) } - updateBook.execute(_state.value.book) + updateBookUseCase(_state.value.book) LibraryScreen.refreshListChannel.trySend(0) HistoryScreen.refreshListChannel.trySend(0) @@ -300,7 +293,7 @@ class BookInfoModel @Inject constructor( ) ) } - updateBook.execute(_state.value.book) + updateBookUseCase(_state.value.book) LibraryScreen.refreshListChannel.trySend(0) HistoryScreen.refreshListChannel.trySend(0) @@ -329,7 +322,7 @@ class BookInfoModel @Inject constructor( ) } - deleteBooks.execute(listOf(_state.value.book)) + deleteBookUseCase(_state.value.book) LibraryScreen.refreshListChannel.trySend(0) HistoryScreen.refreshListChannel.trySend(0) @@ -363,7 +356,7 @@ class BookInfoModel @Inject constructor( bottomSheet = null ) } - updateBook.execute(_state.value.book) + updateBookUseCase(_state.value.book) LibraryScreen.refreshListChannel.trySend(0) HistoryScreen.refreshListChannel.trySend(0) @@ -392,7 +385,7 @@ class BookInfoModel @Inject constructor( navigateBack: () -> Unit ) { viewModelScope.launch(Dispatchers.IO) { - val book = getBookById.execute(bookId) + val book = getBookUseCase(bookId) if (book == null) { navigateBack() diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/browse/BrowseModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/browse/BrowseModel.kt index 6f788bfe..1666c728 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/browse/BrowseModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/browse/BrowseModel.kt @@ -6,6 +6,7 @@ package ua.acclorite.book_story.presentation.browse +import androidx.core.net.toUri import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel @@ -24,9 +25,9 @@ import kotlinx.coroutines.yield import ua.acclorite.book_story.R import ua.acclorite.book_story.data.model.common.NullableBook import ua.acclorite.book_story.domain.file.File -import ua.acclorite.book_story.domain.use_case.book.InsertBook -import ua.acclorite.book_story.domain.use_case.file_system.GetBookFromFile -import ua.acclorite.book_story.domain.use_case.file_system.GetFiles +import ua.acclorite.book_story.domain.use_case.book.AddBookUseCase +import ua.acclorite.book_story.domain.use_case.file_system.GetBookFromFileUseCase +import ua.acclorite.book_story.domain.use_case.file_system.SearchFilesUseCase import ua.acclorite.book_story.presentation.browse.model.BrowseSortOrder import ua.acclorite.book_story.presentation.browse.model.SelectableFile import ua.acclorite.book_story.presentation.library.LibraryScreen @@ -37,9 +38,9 @@ import javax.inject.Inject @HiltViewModel class BrowseModel @Inject constructor( - private val getFiles: GetFiles, - private val getBookFromFile: GetBookFromFile, - private val insertBook: InsertBook + private val addBookUseCase: AddBookUseCase, + private val searchFilesUseCase: SearchFilesUseCase, + private val getBookFromFileUseCase: GetBookFromFileUseCase ) : ViewModel() { private val mutex = Mutex() @@ -296,10 +297,10 @@ class BrowseModel @Inject constructor( .forEach { yield() books.add( - getBookFromFile.execute( + getBookFromFileUseCase( File( name = it.name, - uri = it.uri, + uri = it.uri.toString(), path = it.path, size = it.size, lastModified = it.lastModified, @@ -347,7 +348,12 @@ class BrowseModel @Inject constructor( }.ifEmpty { return@launch } for (book in booksToInsert) { - insertBook.execute(book.bookWithCover!!) + book.bookWithCover?.let { + addBookUseCase( + it.book, + it.coverImage + ) + } } LibraryScreen.refreshListChannel.trySend(0) @@ -414,14 +420,14 @@ class BrowseModel @Inject constructor( private suspend fun getFilesFromDownloads( query: String = if (_state.value.showSearch) _state.value.searchQuery else "" ) { - getFiles.execute(query).apply { + searchFilesUseCase(query).apply { yield() _state.update { it.copy( files = map { SelectableFile( name = it.name, - uri = it.uri, + uri = it.uri.toUri(), path = it.path, size = it.size, lastModified = it.lastModified, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/history/HistoryModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/history/HistoryModel.kt index 976e9323..47a67e1e 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/history/HistoryModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/history/HistoryModel.kt @@ -24,11 +24,11 @@ import kotlinx.coroutines.withContext import kotlinx.coroutines.yield import ua.acclorite.book_story.R import ua.acclorite.book_story.domain.history.History -import ua.acclorite.book_story.domain.use_case.book.GetBooksById -import ua.acclorite.book_story.domain.use_case.history.DeleteHistory -import ua.acclorite.book_story.domain.use_case.history.DeleteWholeHistory -import ua.acclorite.book_story.domain.use_case.history.GetHistory -import ua.acclorite.book_story.domain.use_case.history.InsertHistory +import ua.acclorite.book_story.domain.use_case.book.GetBookUseCase +import ua.acclorite.book_story.domain.use_case.history.AddHistoryUseCase +import ua.acclorite.book_story.domain.use_case.history.DeleteHistoryUseCase +import ua.acclorite.book_story.domain.use_case.history.DeleteWholeHistoryUseCase +import ua.acclorite.book_story.domain.use_case.history.GetHistoryUseCase import ua.acclorite.book_story.presentation.history.model.GroupedHistory import ua.acclorite.book_story.presentation.library.LibraryScreen import ua.acclorite.book_story.ui.common.util.showToast @@ -40,11 +40,11 @@ import javax.inject.Inject @HiltViewModel class HistoryModel @Inject constructor( - private val getHistory: GetHistory, - private val getBooksById: GetBooksById, - private val insertHistory: InsertHistory, - private val deleteHistory: DeleteHistory, - private val deleteWholeHistory: DeleteWholeHistory + private val getBookUseCase: GetBookUseCase, + private val getHistoryUseCase: GetHistoryUseCase, + private val addHistoryUseCase: AddHistoryUseCase, + private val deleteHistoryUseCase: DeleteHistoryUseCase, + private val deleteWholeHistoryUseCase: DeleteWholeHistoryUseCase ) : ViewModel() { private val mutex = Mutex() @@ -78,7 +78,7 @@ class HistoryModel @Inject constructor( } viewModelScope.launch(Dispatchers.IO) { HistoryScreen.insertHistoryChannel.receiveAsFlow().collectLatest { - insertHistory.execute( + addHistoryUseCase( History( bookId = it, book = null, @@ -199,7 +199,7 @@ class HistoryModel @Inject constructor( is HistoryEvent.OnDeleteHistoryEntry -> { viewModelScope.launch(Dispatchers.IO) { - deleteHistory.execute(event.history) + deleteHistoryUseCase(event.history) onEvent( HistoryEvent.OnRefreshList( @@ -229,7 +229,7 @@ class HistoryModel @Inject constructor( when (snackbarResult) { SnackbarResult.Dismissed -> Unit SnackbarResult.ActionPerformed -> { - insertHistory.execute(event.history) + addHistoryUseCase(event.history) LibraryScreen.refreshListChannel.trySend(0) onEvent( @@ -262,7 +262,7 @@ class HistoryModel @Inject constructor( ) } - deleteWholeHistory.execute() + deleteWholeHistoryUseCase() LibraryScreen.refreshListChannel.trySend(0) onEvent( HistoryEvent.OnRefreshList( @@ -307,12 +307,12 @@ class HistoryModel @Inject constructor( return maxElementsById.filterNotNull() } - val history = getHistory.execute().sortedByDescending { + val history = getHistoryUseCase().sortedByDescending { it.time }.run { - val books = getBooksById.execute( - this.map { it.bookId }.distinct() - ).toMutableList() + val books = map { it.bookId }.distinct().mapNotNull { + getBookUseCase(it) + }.toMutableList() mapNotNull { val book = books.find { book -> book.id == it.bookId } ?: return@mapNotNull null diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/library/LibraryModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/library/LibraryModel.kt index f819ce5e..4f6af2c1 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/library/LibraryModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/library/LibraryModel.kt @@ -23,9 +23,9 @@ import kotlinx.coroutines.withContext import kotlinx.coroutines.yield import ua.acclorite.book_story.R import ua.acclorite.book_story.domain.library.Book -import ua.acclorite.book_story.domain.use_case.book.DeleteBooks -import ua.acclorite.book_story.domain.use_case.book.GetBooks -import ua.acclorite.book_story.domain.use_case.book.UpdateBook +import ua.acclorite.book_story.domain.use_case.book.DeleteBookUseCase +import ua.acclorite.book_story.domain.use_case.book.SearchBooksUseCase +import ua.acclorite.book_story.domain.use_case.book.UpdateBookUseCase import ua.acclorite.book_story.presentation.browse.BrowseScreen import ua.acclorite.book_story.presentation.history.HistoryScreen import ua.acclorite.book_story.presentation.library.model.SelectableBook @@ -34,9 +34,9 @@ import javax.inject.Inject @HiltViewModel class LibraryModel @Inject constructor( - private val getBooks: GetBooks, - private val deleteBooks: DeleteBooks, - private val moveBooks: UpdateBook + private val updateBookUseCase: UpdateBookUseCase, + private val searchBooksUseCase: SearchBooksUseCase, + private val deleteBookUseCase: DeleteBookUseCase ) : ViewModel() { private val mutex = Mutex() @@ -208,7 +208,7 @@ class LibraryModel @Inject constructor( viewModelScope.launch { _state.value.books.forEach { book -> if (!book.selected) return@forEach - moveBooks.execute( + updateBookUseCase( book.data.copy( categories = event.selectedCategories.map { it.id } ) @@ -253,12 +253,10 @@ class LibraryModel @Inject constructor( is LibraryEvent.OnActionDeleteDialog -> { viewModelScope.launch { - deleteBooks.execute( - _state.value.books.mapNotNull { - if (!it.selected) return@mapNotNull null - it.data - } - ) + _state.value.books.forEach { + if (!it.selected) return@forEach + deleteBookUseCase(it.data) + } _state.update { it.copy( @@ -314,8 +312,7 @@ class LibraryModel @Inject constructor( private suspend fun getBooksFromDatabase( query: String = if (_state.value.showSearch) _state.value.searchQuery else "" ) { - val books = getBooks - .execute(query) + val books = searchBooksUseCase(query) .sortedWith(compareByDescending { it.lastOpened }.thenBy { it.title }) .map { book -> SelectableBook(book, false) } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/main/MainModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/main/MainModel.kt index 7380fa9e..edfcb092 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/main/MainModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/main/MainModel.kt @@ -22,9 +22,9 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import kotlinx.coroutines.yield -import ua.acclorite.book_story.domain.use_case.data_store.ChangeLanguage -import ua.acclorite.book_story.domain.use_case.data_store.GetAllSettings -import ua.acclorite.book_story.domain.use_case.data_store.SetDatastore +import ua.acclorite.book_story.domain.use_case.data_store.ChangeLanguagePreferenceUseCase +import ua.acclorite.book_story.domain.use_case.data_store.GetPreferencesUseCase +import ua.acclorite.book_story.domain.use_case.data_store.PutPreferenceUseCase import ua.acclorite.book_story.presentation.browse.model.BrowseLayout import ua.acclorite.book_story.presentation.browse.model.BrowseSortOrder import ua.acclorite.book_story.presentation.library.model.LibraryLayout @@ -50,10 +50,9 @@ import javax.inject.Inject @HiltViewModel class MainModel @Inject constructor( private val stateHandle: SavedStateHandle, - - private val setDatastore: SetDatastore, - private val changeLanguage: ChangeLanguage, - private val getAllSettings: GetAllSettings + private val putPreferenceUseCase: PutPreferenceUseCase, + private val changeLanguagePreferenceUseCase: ChangeLanguagePreferenceUseCase, + private val getPreferencesUseCase: GetPreferencesUseCase ) : ViewModel() { private val mutex = Mutex() @@ -618,10 +617,10 @@ class MainModel @Inject constructor( fun init(settingsModelReady: StateFlow) { viewModelScope.launch(Dispatchers.Main) { - val settings = getAllSettings.execute() + val settings = getPreferencesUseCase() /* All additional execution */ - changeLanguage.execute(settings.language) + changeLanguagePreferenceUseCase(settings.language) updateStateWithSavedHandle { settings } mainModelReady.update { true } @@ -648,7 +647,7 @@ class MainModel @Inject constructor( private fun handleLanguageUpdate(event: MainEvent.OnChangeLanguage) { viewModelScope.launch(Dispatchers.Main.immediate) { - changeLanguage.execute(event.value) + changeLanguagePreferenceUseCase(event.value) updateStateWithSavedHandle { it.copy(language = event.value) } @@ -696,7 +695,7 @@ class MainModel @Inject constructor( updateState: V.(MainState) -> MainState ) { viewModelScope.launch(Dispatchers.Main.immediate) { - setDatastore.execute(key = key, value = value) + putPreferenceUseCase(key = key, value = value) updateStateWithSavedHandle { value.updateState(it) } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderModel.kt index f400bdb0..fc3f8d03 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderModel.kt @@ -37,10 +37,10 @@ import kotlinx.coroutines.yield import ua.acclorite.book_story.R import ua.acclorite.book_story.core.ui.UIText import ua.acclorite.book_story.domain.reader.ReaderText.Chapter -import ua.acclorite.book_story.domain.use_case.book.GetBookById -import ua.acclorite.book_story.domain.use_case.book.GetText -import ua.acclorite.book_story.domain.use_case.book.UpdateBook -import ua.acclorite.book_story.domain.use_case.history.GetLatestHistory +import ua.acclorite.book_story.domain.use_case.book.GetBookUseCase +import ua.acclorite.book_story.domain.use_case.book.GetTextUseCase +import ua.acclorite.book_story.domain.use_case.book.UpdateBookUseCase +import ua.acclorite.book_story.domain.use_case.history.GetHistoryForBookUseCase import ua.acclorite.book_story.presentation.history.HistoryScreen import ua.acclorite.book_story.presentation.library.LibraryScreen import ua.acclorite.book_story.presentation.reader.model.Checkpoint @@ -55,10 +55,10 @@ private const val READER = "READER, MODEL" @HiltViewModel class ReaderModel @Inject constructor( - private val getBookById: GetBookById, - private val updateBook: UpdateBook, - private val getText: GetText, - private val getLatestHistory: GetLatestHistory + private val updateBookUseCase: UpdateBookUseCase, + private val getTextUseCase: GetTextUseCase, + private val getBookUseCase: GetBookUseCase, + private val getHistoryForBookUseCase: GetHistoryForBookUseCase ) : ViewModel() { private val mutex = Mutex() @@ -76,7 +76,7 @@ class ReaderModel @Inject constructor( when (event) { is ReaderEvent.OnLoadText -> { launch(Dispatchers.IO) { - val text = getText.execute(_state.value.book.id) + val text = getTextUseCase(_state.value.book.id) yield() if (text.isEmpty()) { @@ -95,7 +95,7 @@ class ReaderModel @Inject constructor( activity = event.activity ) - val lastOpened = getLatestHistory.execute(_state.value.book.id)?.time + val lastOpened = getHistoryForBookUseCase(_state.value.book.id)?.time yield() _state.update { @@ -110,7 +110,7 @@ class ReaderModel @Inject constructor( yield() - updateBook.execute(_state.value.book) + updateBookUseCase(_state.value.book) LibraryScreen.refreshListChannel.trySend(0) HistoryScreen.refreshListChannel.trySend(0) @@ -177,7 +177,7 @@ class ReaderModel @Inject constructor( ) } - updateBook.execute(_state.value.book) + updateBookUseCase(_state.value.book) LibraryScreen.refreshListChannel.trySend(300) HistoryScreen.refreshListChannel.trySend(300) @@ -265,7 +265,7 @@ class ReaderModel @Inject constructor( ) } - updateBook.execute(_state.value.book) + updateBookUseCase(_state.value.book) LibraryScreen.refreshListChannel.trySend(0) HistoryScreen.refreshListChannel.trySend(0) @@ -466,7 +466,7 @@ class ReaderModel @Inject constructor( navigateBack: () -> Unit ) { viewModelScope.launch(Dispatchers.IO) { - val book = getBookById.execute(bookId) + val book = getBookUseCase(bookId) if (book == null) { navigateBack() @@ -518,7 +518,7 @@ class ReaderModel @Inject constructor( ) } - updateBook.execute(_state.value.book) + updateBookUseCase(_state.value.book) LibraryScreen.refreshListChannel.trySend(0) HistoryScreen.refreshListChannel.trySend(0) diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/settings/SettingsModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/settings/SettingsModel.kt index 881aafbf..38352824 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/settings/SettingsModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/settings/SettingsModel.kt @@ -20,23 +20,22 @@ import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import kotlinx.coroutines.yield import ua.acclorite.book_story.R -import ua.acclorite.book_story.domain.library.Category import ua.acclorite.book_story.domain.library.CategorySort import ua.acclorite.book_story.domain.reader.ColorPreset -import ua.acclorite.book_story.domain.use_case.category.DeleteCategory -import ua.acclorite.book_story.domain.use_case.category.GetCategories -import ua.acclorite.book_story.domain.use_case.category.GetCategorySort -import ua.acclorite.book_story.domain.use_case.category.InsertCategory -import ua.acclorite.book_story.domain.use_case.category.UpdateCategoryOrder -import ua.acclorite.book_story.domain.use_case.category.UpdateCategorySort -import ua.acclorite.book_story.domain.use_case.category.UpdateCategoryTitle -import ua.acclorite.book_story.domain.use_case.color_preset.DeleteColorPreset -import ua.acclorite.book_story.domain.use_case.color_preset.GetColorPresets -import ua.acclorite.book_story.domain.use_case.color_preset.ReorderColorPresets -import ua.acclorite.book_story.domain.use_case.color_preset.SelectColorPreset -import ua.acclorite.book_story.domain.use_case.color_preset.UpdateColorPreset -import ua.acclorite.book_story.domain.use_case.permission.GrantPersistableUriPermission -import ua.acclorite.book_story.domain.use_case.permission.ReleasePersistableUriPermission +import ua.acclorite.book_story.domain.use_case.category.AddCategoryUseCase +import ua.acclorite.book_story.domain.use_case.category.DeleteCategoryUseCase +import ua.acclorite.book_story.domain.use_case.category.GetCategoriesUseCase +import ua.acclorite.book_story.domain.use_case.category.GetCategorySortingUseCase +import ua.acclorite.book_story.domain.use_case.category.UpdateCategoriesOrderUseCase +import ua.acclorite.book_story.domain.use_case.category.UpdateCategorySortingUseCase +import ua.acclorite.book_story.domain.use_case.category.UpdateCategoryUseCase +import ua.acclorite.book_story.domain.use_case.color_preset.DeleteColorPresetUseCase +import ua.acclorite.book_story.domain.use_case.color_preset.GetColorPresetsUseCase +import ua.acclorite.book_story.domain.use_case.color_preset.ReorderColorPresetsUseCase +import ua.acclorite.book_story.domain.use_case.color_preset.SelectColorPresetUseCase +import ua.acclorite.book_story.domain.use_case.color_preset.UpdateColorPresetUseCase +import ua.acclorite.book_story.domain.use_case.permission.GrantPersistableUriPermissionUseCase +import ua.acclorite.book_story.domain.use_case.permission.ReleasePersistableUriPermissionUseCase import ua.acclorite.book_story.ui.common.constants.provideDefaultColorPreset import ua.acclorite.book_story.ui.common.util.showToast import javax.inject.Inject @@ -44,20 +43,20 @@ import kotlin.random.Random @HiltViewModel class SettingsModel @Inject constructor( - private val getColorPresets: GetColorPresets, - private val updateColorPreset: UpdateColorPreset, - private val selectColorPreset: SelectColorPreset, - private val reorderColorPresets: ReorderColorPresets, - private val deleteColorPreset: DeleteColorPreset, - private val grantPersistableUriPermission: GrantPersistableUriPermission, - private val releasePersistableUriPermission: ReleasePersistableUriPermission, - private val insertCategory: InsertCategory, - private val getCategories: GetCategories, - private val updateCategoryTitle: UpdateCategoryTitle, - private val updateCategoryOrder: UpdateCategoryOrder, - private val deleteCategory: DeleteCategory, - private val updateCategorySort: UpdateCategorySort, - private val getCategoriesSort: GetCategorySort, + private val getColorPresetsUseCase: GetColorPresetsUseCase, + private val updateColorPresetUseCase: UpdateColorPresetUseCase, + private val selectColorPresetUseCase: SelectColorPresetUseCase, + private val reorderColorPresetsUseCase: ReorderColorPresetsUseCase, + private val deleteColorPresetUseCase: DeleteColorPresetUseCase, + private val grantPersistableUriPermissionUseCase: GrantPersistableUriPermissionUseCase, + private val releasePersistableUriPermissionUseCase: ReleasePersistableUriPermissionUseCase, + private val addCategoryUseCase: AddCategoryUseCase, + private val getCategoriesUseCase: GetCategoriesUseCase, + private val updateCategoryUseCase: UpdateCategoryUseCase, + private val updateCategoriesOrderUseCase: UpdateCategoriesOrderUseCase, + private val deleteCategoryUseCase: DeleteCategoryUseCase, + private val updateCategorySortingUseCase: UpdateCategorySortingUseCase, + private val getCategorySortingUseCase: GetCategorySortingUseCase ) : ViewModel() { private val mutex = Mutex() @@ -77,12 +76,12 @@ class SettingsModel @Inject constructor( init { viewModelScope.launch(Dispatchers.IO) { - var colorPresets = getColorPresets.execute() + var colorPresets = getColorPresetsUseCase() if (colorPresets.isEmpty()) { - updateColorPreset.execute(provideDefaultColorPreset()) - getColorPresets.execute().first().select() - colorPresets = getColorPresets.execute() + updateColorPresetUseCase(provideDefaultColorPreset()) + getColorPresetsUseCase().first().select() + colorPresets = getColorPresetsUseCase() } val scrollIndex = colorPresets.indexOfFirst { @@ -102,8 +101,8 @@ class SettingsModel @Inject constructor( it.copy( selectedColorPreset = colorPresets.selected(), colorPresets = colorPresets, - categories = getCategories.execute(), // Getting categories - categoriesSort = getCategoriesSort.execute() + categories = getCategoriesUseCase(), // Getting categories + categoriesSort = getCategorySortingUseCase() ) } @@ -116,30 +115,26 @@ class SettingsModel @Inject constructor( when (event) { is SettingsEvent.OnGrantPersistableUriPermission -> { viewModelScope.launch { - grantPersistableUriPermission.execute( - event.uri + grantPersistableUriPermissionUseCase( + event.uri.toString() ) } } is SettingsEvent.OnReleasePersistableUriPermission -> { viewModelScope.launch { - releasePersistableUriPermission.execute( - event.uri + releasePersistableUriPermissionUseCase( + event.uri.toString() ) } } is SettingsEvent.OnCreateCategory -> { viewModelScope.launch { - insertCategory.execute( - category = Category( - title = event.title - ) - ) + addCategoryUseCase(event.title) _state.update { it.copy( - categories = getCategories.execute() + categories = getCategoriesUseCase() ) } } @@ -147,13 +142,13 @@ class SettingsModel @Inject constructor( is SettingsEvent.OnUpdateCategoryTitle -> { viewModelScope.launch { - updateCategoryTitle.execute( - id = event.id, - title = event.title + updateCategoryUseCase( + categoryId = event.id, + newTitle = event.title ) _state.update { it.copy( - categories = getCategories.execute() + categories = getCategoriesUseCase() ) } } @@ -161,12 +156,12 @@ class SettingsModel @Inject constructor( is SettingsEvent.OnUpdateCategoryOrder -> { viewModelScope.launch { - updateCategoryOrder.execute( + updateCategoriesOrderUseCase( categories = event.categories ) _state.update { it.copy( - categories = getCategories.execute() + categories = getCategoriesUseCase() ) } } @@ -174,12 +169,12 @@ class SettingsModel @Inject constructor( is SettingsEvent.OnRemoveCategory -> { viewModelScope.launch { - deleteCategory.execute( + deleteCategoryUseCase( category = event.category ) _state.update { it.copy( - categories = getCategories.execute() + categories = getCategoriesUseCase() ) } } @@ -187,7 +182,7 @@ class SettingsModel @Inject constructor( is SettingsEvent.OnUpdateCategorySort -> { viewModelScope.launch { - updateCategorySort.execute( + updateCategorySortingUseCase( categorySort = CategorySort( categoryId = event.categoryId, sortOrder = event.sortOrder, @@ -196,7 +191,7 @@ class SettingsModel @Inject constructor( ) _state.update { it.copy( - categoriesSort = getCategoriesSort.execute() + categoriesSort = getCategorySortingUseCase() ) } } @@ -206,7 +201,8 @@ class SettingsModel @Inject constructor( viewModelScope.launch { cancelColorPresetJobs() selectColorPresetJob = launch { - val colorPreset = event.id.getColorPresetById() ?: return@launch + val colorPreset = _state.value.colorPresets.getColorPresetById(event.id) + ?: return@launch yield() @@ -345,7 +341,8 @@ class SettingsModel @Inject constructor( cancelColorPresetJobs() deleteColorPresetJob = launch { if (_state.value.colorPresets.size == 1) return@launch - val colorPreset = event.id.getColorPresetById() ?: return@launch + val colorPreset = _state.value.colorPresets.getColorPresetById(event.id) + ?: return@launch yield() @@ -362,12 +359,12 @@ class SettingsModel @Inject constructor( yield() - deleteColorPreset.execute(colorPreset) - val nextColorPreset = getColorPresets.execute().getOrNull(nextPosition) + deleteColorPresetUseCase(colorPreset) + val nextColorPreset = getColorPresetsUseCase().getOrNull(nextPosition) ?: return@launch nextColorPreset.select() - val colorPresets = getColorPresets.execute() + val colorPresets = getColorPresetsUseCase() _state.update { it.copy( @@ -389,7 +386,8 @@ class SettingsModel @Inject constructor( viewModelScope.launch { cancelColorPresetJobs() updateTitleColorPresetJob = launch { - val colorPreset = event.id.getColorPresetById() ?: return@launch + val colorPreset = _state.value.colorPresets.getColorPresetById(event.id) + ?: return@launch yield() @@ -399,7 +397,7 @@ class SettingsModel @Inject constructor( yield() - updateColorPreset.execute(updatedColorPreset) + updateColorPresetUseCase(updatedColorPreset) _state.update { it.copy( selectedColorPreset = updatedColorPreset, @@ -416,7 +414,8 @@ class SettingsModel @Inject constructor( viewModelScope.launch { cancelColorPresetJobs() shuffleColorPresetJob = launch { - val colorPreset = event.id.getColorPresetById() ?: return@launch + val colorPreset = _state.value.colorPresets.getColorPresetById(event.id) + ?: return@launch yield() @@ -435,7 +434,7 @@ class SettingsModel @Inject constructor( yield() - updateColorPreset.execute(shuffledColorPreset) + updateColorPresetUseCase(shuffledColorPreset) _state.update { it.copy( selectedColorPreset = shuffledColorPreset, @@ -458,10 +457,10 @@ class SettingsModel @Inject constructor( backgroundColor = event.backgroundColor, fontColor = event.fontColor ) - updateColorPreset.execute(newColorPreset) + updateColorPresetUseCase(newColorPreset) - getColorPresets.execute().last().select() - val colorPresets = getColorPresets.execute() + getColorPresetsUseCase().last().select() + val colorPresets = getColorPresetsUseCase() _state.update { it.copy( @@ -479,7 +478,8 @@ class SettingsModel @Inject constructor( viewModelScope.launch { cancelColorPresetJobs() updateColorColorPresetJob = launch { - val colorPreset = event.id.getColorPresetById() ?: return@launch + val colorPreset = _state.value.colorPresets.getColorPresetById(event.id) + ?: return@launch yield() @@ -492,7 +492,7 @@ class SettingsModel @Inject constructor( yield() - updateColorPreset.execute(updatedColorPreset) + updateColorPresetUseCase(updatedColorPreset) _state.update { it.copy( selectedColorPreset = updatedColorPreset, @@ -528,7 +528,7 @@ class SettingsModel @Inject constructor( viewModelScope.launch { cancelColorPresetJobs() launch { - reorderColorPresets.execute(_state.value.colorPresets) + reorderColorPresetsUseCase(_state.value.colorPresets) } } } @@ -548,7 +548,7 @@ class SettingsModel @Inject constructor( } private suspend fun ColorPreset.select(animate: Boolean = false) { - selectColorPreset.execute(this) + selectColorPresetUseCase(this) _state.update { it.copy( animateColorPreset = animate @@ -556,9 +556,9 @@ class SettingsModel @Inject constructor( } } - private fun Int.getColorPresetById(): ColorPreset? { - return _state.value.colorPresets.firstOrNull { - it.id == this + private fun List.getColorPresetById(id: Int): ColorPreset? { + return firstOrNull { + it.id == id } }