From 1e42c258e7c94b5e0a00d13eb03f9fd66cbc0401 Mon Sep 17 00:00:00 2001 From: acclorite Date: Mon, 15 Jul 2024 00:50:28 +0300 Subject: [PATCH] =?UTF-8?q?Removed=20Built-In=20translator=20(it=20was=20b?= =?UTF-8?q?ased=20Closed=20Source=20libraries)=20+=20Removed=20all=20Close?= =?UTF-8?q?d=20Source=20libraries=20+=20LineWithTranslation=20=E2=86=92=20?= =?UTF-8?q?ReaderLine=20+=20Room=20migration=203=20=E2=86=92=204.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 35 +- .../4.json | 126 +++ app/src/main/AndroidManifest.xml | 5 - .../acclorite/book_story/data/di/AppModule.kt | 20 - .../book_story/data/di/RepositoryModule.kt | 8 - .../book_story/data/local/dto/BookEntity.kt | 8 +- .../data/local/dto/LanguageHistoryEntity.kt | 10 - .../book_story/data/local/room/BookDao.kt | 9 - .../data/local/room/BookDatabase.kt | 19 +- .../data/mapper/book/BookMapperImpl.kt | 12 +- .../language_history/LanguageHistoryMapper.kt | 10 - .../LanguageHistoryMapperImpl.kt | 22 - .../data/parser/epub/EpubFileParser.kt | 7 +- .../data/parser/fb2/Fb2FileParser.kt | 7 +- .../data/parser/pdf/PdfFileParser.kt | 7 +- .../data/parser/txt/TxtFileParser.kt | 7 +- .../data/repository/BookRepositoryImpl.kt | 211 +---- .../acclorite/book_story/domain/model/Book.kt | 6 - .../book_story/domain/model/Language.kt | 31 - .../domain/model/LanguageHistory.kt | 9 - .../domain/model/LineWithTranslation.kt | 21 - .../book_story/domain/model/ReaderLine.kt | 11 + .../domain/repository/BookRepository.kt | 27 - .../domain/use_case/DeleteLanguageModel.kt | 15 - .../domain/use_case/DownloadLanguageModel.kt | 21 - .../domain/use_case/GetLanguageHistory.kt | 12 - .../book_story/domain/use_case/GetText.kt | 6 +- .../domain/use_case/IdentifyLanguage.kt | 15 - .../use_case/IsLanguageModelDownloaded.kt | 13 - .../domain/use_case/TranslateText.kt | 23 - .../domain/use_case/UpdateLanguageHistory.kt | 12 - .../book_story/domain/util/Constants.kt | 7 +- .../domain/util/DataStoreConstants.kt | 3 - .../book_story/domain/util/Typealias.kt | 1 - .../TranslatorLanguageBottomSheet.kt | 298 ------ .../TranslatorLanguageDownloadDialog.kt | 100 -- .../components/TranslatorLanguageItem.kt | 243 ----- .../components/TranslatorLanguageTopBar.kt | 107 --- .../data/TranslatorLanguageEvent.kt | 44 - .../data/TranslatorLanguageState.kt | 21 - .../data/TranslatorLanguageViewModel.kt | 455 --------- .../presentation/data/ArithmeticOperations.kt | 61 +- .../book_story/presentation/data/MainEvent.kt | 3 - .../book_story/presentation/data/MainState.kt | 23 +- .../presentation/data/MainViewModel.kt | 33 - .../BookInfoConfirmUpdateDialogItem.kt | 21 - .../book_info/data/BookInfoViewModel.kt | 2 +- .../screens/reader/ReaderScreen.kt | 58 +- .../reader/components/ReaderTextParagraph.kt | 71 ++ .../reader/components/app_bar/ReaderTopBar.kt | 13 - .../ReaderDownloadLanguageDialog.kt | 116 --- .../ReaderDownloadLanguageDialogItem.kt | 177 ---- .../components/text/ReaderTextParagraph.kt | 220 ----- .../text/ReaderTextTrailingContent.kt | 97 -- .../ReaderTranslatorBottomSheet.kt | 318 ------- ...derTranslatorBottomSheetLanguageChooser.kt | 191 ---- .../screens/reader/data/ReaderEvent.kt | 42 - .../screens/reader/data/ReaderState.kt | 13 +- .../screens/reader/data/ReaderViewModel.kt | 860 +----------------- .../screens/settings/data/SettingsEvent.kt | 5 - .../screens/settings/data/SettingsState.kt | 9 - .../settings/data/SettingsViewModel.kt | 17 - .../settings/nested/reader/ReaderSettings.kt | 154 +--- app/src/main/res/drawable/empty_languages.png | Bin 20005 -> 0 bytes app/src/main/res/values-uk/strings.xml | 51 -- app/src/main/res/values/strings.xml | 53 -- 66 files changed, 256 insertions(+), 4376 deletions(-) create mode 100644 app/schemas/ua.acclorite.book_story.data.local.room.BookDatabase/4.json delete mode 100644 app/src/main/java/ua/acclorite/book_story/data/local/dto/LanguageHistoryEntity.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/data/mapper/language_history/LanguageHistoryMapper.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/data/mapper/language_history/LanguageHistoryMapperImpl.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/model/Language.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/model/LanguageHistory.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/model/LineWithTranslation.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/model/ReaderLine.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/DeleteLanguageModel.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/DownloadLanguageModel.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/GetLanguageHistory.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/IdentifyLanguage.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/IsLanguageModelDownloaded.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/TranslateText.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/UpdateLanguageHistory.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/components/translator_language/TranslatorLanguageBottomSheet.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/components/translator_language/components/TranslatorLanguageDownloadDialog.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/components/translator_language/components/TranslatorLanguageItem.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/components/translator_language/components/TranslatorLanguageTopBar.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/components/translator_language/data/TranslatorLanguageEvent.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/components/translator_language/data/TranslatorLanguageState.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/components/translator_language/data/TranslatorLanguageViewModel.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/confirm_update_dialog/BookInfoConfirmUpdateDialogItem.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderTextParagraph.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/download_language_dialog/ReaderDownloadLanguageDialog.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/download_language_dialog/ReaderDownloadLanguageDialogItem.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/text/ReaderTextParagraph.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/text/ReaderTextTrailingContent.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/translator_bottom_sheet/ReaderTranslatorBottomSheet.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/translator_bottom_sheet/ReaderTranslatorBottomSheetLanguageChooser.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/data/SettingsState.kt delete mode 100644 app/src/main/res/drawable/empty_languages.png diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e079d60e..99dac196 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -92,19 +92,19 @@ dependencies { implementation("androidx.activity:activity-compose:1.9.0") // Compose BOM libraries - implementation(platform("androidx.compose:compose-bom:2024.06.00")) - implementation("androidx.compose.foundation:foundation") - implementation("androidx.compose.animation:animation") - implementation("androidx.compose.animation:animation-android") - implementation("androidx.compose.foundation:foundation-layout") - implementation("androidx.compose.ui:ui") - implementation("androidx.compose.ui:ui-graphics") - implementation("androidx.compose.ui:ui-tooling-preview") - implementation("androidx.compose.ui:ui-android") + // Compose BOM was eliminated - it is recognized as Closed Source in AboutLibraries. + implementation("androidx.compose.foundation:foundation:1.6.8") + implementation("androidx.compose.animation:animation:1.6.8") + implementation("androidx.compose.animation:animation-android:1.6.8") + implementation("androidx.compose.foundation:foundation-layout:1.6.8") + implementation("androidx.compose.ui:ui:1.6.8") + implementation("androidx.compose.ui:ui-graphics:1.6.8") + implementation("androidx.compose.ui:ui-tooling-preview:1.6.8") + implementation("androidx.compose.ui:ui-android:1.6.8") implementation("androidx.compose.material3:material3:1.3.0-alpha06") - implementation("androidx.compose.material3:material3-window-size-class") - implementation("androidx.compose.material:material-icons-extended") - implementation("androidx.compose.material:material") + implementation("androidx.compose.material3:material3-window-size-class:1.2.1") + implementation("androidx.compose.material:material-icons-extended:1.6.8") + implementation("androidx.compose.material:material:1.6.8") // All dependencies implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.3") @@ -165,15 +165,4 @@ dependencies { // About open source libraries implementation("com.mikepenz:aboutlibraries-core:latest.release") implementation("com.mikepenz:aboutlibraries-compose-m3:latest.release") - - // Translator - implementation("com.google.mlkit:translate:latest.release") { - exclude("com.google.android.gms", "play-services-base") - exclude("com.google.android.datatransport", "transport-api") - exclude("com.google.android.datatransport", "transport-backend-cct") - exclude("com.google.android.datatransport", "transport-runtime") - exclude("com.google.firebase", "firebase-encoders-json") - exclude("com.google.firebase", "firebase-encoders") - } - implementation("com.google.mlkit:language-id:latest.release") } \ No newline at end of file diff --git a/app/schemas/ua.acclorite.book_story.data.local.room.BookDatabase/4.json b/app/schemas/ua.acclorite.book_story.data.local.room.BookDatabase/4.json new file mode 100644 index 00000000..a1d39c09 --- /dev/null +++ b/app/schemas/ua.acclorite.book_story.data.local.room.BookDatabase/4.json @@ -0,0 +1,126 @@ +{ + "formatVersion": 1, + "database": { + "version": 4, + "identityHash": "c8a3497d25808dec49816f26182b61d6", + "entities": [ + { + "tableName": "BookEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `author` TEXT, `description` TEXT, `textPath` TEXT NOT NULL, `filePath` TEXT NOT NULL, `scrollIndex` INTEGER NOT NULL, `scrollOffset` INTEGER NOT NULL, `progress` REAL NOT NULL, `image` TEXT, `category` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "author", + "columnName": "author", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "textPath", + "columnName": "textPath", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filePath", + "columnName": "filePath", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "scrollIndex", + "columnName": "scrollIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "scrollOffset", + "columnName": "scrollOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "progress", + "columnName": "progress", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "image", + "columnName": "image", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "HistoryEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `bookId` INTEGER NOT NULL, `time` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "bookId", + "columnName": "bookId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "time", + "columnName": "time", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c8a3497d25808dec49816f26182b61d6')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a4b0fbb7..562a6ff7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -57,11 +57,6 @@ - - - ) - - - @Upsert - suspend fun updateLanguageHistory(history: List) - - @Query("SELECT * FROM languagehistoryentity") - suspend fun getLanguageHistory(): List } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/local/room/BookDatabase.kt b/app/src/main/java/ua/acclorite/book_story/data/local/room/BookDatabase.kt index cbfb65c8..d32dfc60 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/local/room/BookDatabase.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/local/room/BookDatabase.kt @@ -2,23 +2,25 @@ package ua.acclorite.book_story.data.local.room import androidx.room.AutoMigration import androidx.room.Database +import androidx.room.DeleteColumn +import androidx.room.DeleteTable import androidx.room.RoomDatabase +import androidx.room.migration.AutoMigrationSpec import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import ua.acclorite.book_story.data.local.dto.BookEntity import ua.acclorite.book_story.data.local.dto.HistoryEntity -import ua.acclorite.book_story.data.local.dto.LanguageHistoryEntity @Database( entities = [ BookEntity::class, - HistoryEntity::class, - LanguageHistoryEntity::class + HistoryEntity::class ], - version = 3, + version = 4, autoMigrations = [ AutoMigration(1, 2), AutoMigration(2, 3), + AutoMigration(3, 4, spec = DatabaseHelper.MIGRATION_3_4::class), ], exportSchema = true ) @@ -26,6 +28,7 @@ abstract class BookDatabase : RoomDatabase() { abstract val dao: BookDao } +@Suppress("ClassName") object DatabaseHelper { val MIGRATION_2_3 = object : Migration(2, 3) { @@ -39,4 +42,12 @@ object DatabaseHelper { ) } } + + @DeleteColumn("BookEntity", "enableTranslator") + @DeleteColumn("BookEntity", "translateFrom") + @DeleteColumn("BookEntity", "translateTo") + @DeleteColumn("BookEntity", "doubleClickTranslation") + @DeleteColumn("BookEntity", "translateWhenOpen") + @DeleteTable("LanguageHistoryEntity") + class MIGRATION_3_4 : AutoMigrationSpec } \ 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 8463cd87..e2953018 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 @@ -20,12 +20,7 @@ class BookMapperImpl @Inject constructor() : BookMapper { textPath = book.textPath, description = book.description, image = if (book.coverImage != null) book.coverImage.toString() else null, - category = book.category, - enableTranslator = book.enableTranslator, - translateFrom = book.translateFrom, - translateTo = book.translateTo, - doubleClickTranslation = book.doubleClickTranslation, - translateWhenOpen = book.translateWhenOpen + category = book.category ) } @@ -45,11 +40,6 @@ class BookMapperImpl @Inject constructor() : BookMapper { lastOpened = null, category = bookEntity.category, coverImage = if (bookEntity.image != null) Uri.parse(bookEntity.image) else null, - enableTranslator = bookEntity.enableTranslator, - translateFrom = bookEntity.translateFrom, - translateTo = bookEntity.translateTo, - doubleClickTranslation = bookEntity.doubleClickTranslation, - translateWhenOpen = bookEntity.translateWhenOpen ) } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/mapper/language_history/LanguageHistoryMapper.kt b/app/src/main/java/ua/acclorite/book_story/data/mapper/language_history/LanguageHistoryMapper.kt deleted file mode 100644 index 2257aa5f..00000000 --- a/app/src/main/java/ua/acclorite/book_story/data/mapper/language_history/LanguageHistoryMapper.kt +++ /dev/null @@ -1,10 +0,0 @@ -package ua.acclorite.book_story.data.mapper.language_history - -import ua.acclorite.book_story.data.local.dto.LanguageHistoryEntity -import ua.acclorite.book_story.domain.model.LanguageHistory - -interface LanguageHistoryMapper { - suspend fun toLanguageHistoryEntity(languageHistory: LanguageHistory): LanguageHistoryEntity - - suspend fun toLanguageHistory(languageHistoryEntity: LanguageHistoryEntity): LanguageHistory -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/mapper/language_history/LanguageHistoryMapperImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/mapper/language_history/LanguageHistoryMapperImpl.kt deleted file mode 100644 index 2e2433fb..00000000 --- a/app/src/main/java/ua/acclorite/book_story/data/mapper/language_history/LanguageHistoryMapperImpl.kt +++ /dev/null @@ -1,22 +0,0 @@ -package ua.acclorite.book_story.data.mapper.language_history - -import ua.acclorite.book_story.data.local.dto.LanguageHistoryEntity -import ua.acclorite.book_story.domain.model.LanguageHistory -import javax.inject.Inject - -class LanguageHistoryMapperImpl @Inject constructor() : LanguageHistoryMapper { - - override suspend fun toLanguageHistoryEntity(languageHistory: LanguageHistory): LanguageHistoryEntity { - return LanguageHistoryEntity( - languageCode = languageHistory.languageCode, - order = languageHistory.order - ) - } - - override suspend fun toLanguageHistory(languageHistoryEntity: LanguageHistoryEntity): LanguageHistory { - return LanguageHistory( - languageCode = languageHistoryEntity.languageCode, - order = languageHistoryEntity.order - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubFileParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubFileParser.kt index dd71ef30..1c051501 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubFileParser.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubFileParser.kt @@ -55,12 +55,7 @@ class EpubFileParser @Inject constructor() : FileParser { filePath = file.path, lastOpened = null, category = Category.entries[0], - coverImage = null, - enableTranslator = false, - translateFrom = "", - translateTo = "", - doubleClickTranslation = false, - translateWhenOpen = false + coverImage = null ) to coverImage } catch (e: Exception) { e.printStackTrace() diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/fb2/Fb2FileParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/fb2/Fb2FileParser.kt index ee382b80..d03698b4 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/parser/fb2/Fb2FileParser.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/parser/fb2/Fb2FileParser.kt @@ -65,12 +65,7 @@ class Fb2FileParser @Inject constructor() : FileParser { filePath = file.path, lastOpened = null, category = Category.entries[0], - coverImage = null, - enableTranslator = false, - translateFrom = "", - translateTo = "", - doubleClickTranslation = false, - translateWhenOpen = false + coverImage = null ) to null } catch (e: Exception) { e.printStackTrace() diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/pdf/PdfFileParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/pdf/PdfFileParser.kt index 550190c2..c66e5f10 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/parser/pdf/PdfFileParser.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/parser/pdf/PdfFileParser.kt @@ -43,12 +43,7 @@ class PdfFileParser @Inject constructor(private val application: Application) : filePath = file.path, lastOpened = null, category = Category.entries[0], - coverImage = null, - enableTranslator = false, - translateFrom = "", - translateTo = "", - doubleClickTranslation = false, - translateWhenOpen = false + coverImage = null ) to null } catch (e: Exception) { e.printStackTrace() diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/txt/TxtFileParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/txt/TxtFileParser.kt index 131369fc..3588838f 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/parser/txt/TxtFileParser.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/parser/txt/TxtFileParser.kt @@ -31,12 +31,7 @@ class TxtFileParser @Inject constructor() : FileParser { filePath = file.path, lastOpened = null, category = Category.entries[0], - coverImage = null, - enableTranslator = false, - translateFrom = "", - translateTo = "", - doubleClickTranslation = false, - translateWhenOpen = false + coverImage = null ) to null } catch (e: Exception) { e.printStackTrace() 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 cc296656..f022f72f 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 @@ -8,13 +8,6 @@ import android.os.Environment import android.provider.MediaStore import android.util.Log import androidx.datastore.preferences.core.Preferences -import com.google.mlkit.common.model.DownloadConditions -import com.google.mlkit.common.model.RemoteModelManager -import com.google.mlkit.nl.languageid.LanguageIdentifier -import com.google.mlkit.nl.translate.TranslateLanguage -import com.google.mlkit.nl.translate.TranslateRemoteModel -import com.google.mlkit.nl.translate.Translation -import com.google.mlkit.nl.translate.TranslatorOptions import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -23,7 +16,6 @@ import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.launch -import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext import ua.acclorite.book_story.R import ua.acclorite.book_story.data.local.data_store.DataStore @@ -31,32 +23,25 @@ import ua.acclorite.book_story.data.local.notification.UpdatesNotificationServic import ua.acclorite.book_story.data.local.room.BookDao import ua.acclorite.book_story.data.mapper.book.BookMapper import ua.acclorite.book_story.data.mapper.history.HistoryMapper -import ua.acclorite.book_story.data.mapper.language_history.LanguageHistoryMapper import ua.acclorite.book_story.data.parser.FileParser import ua.acclorite.book_story.data.parser.TextParser import ua.acclorite.book_story.data.remote.GithubAPI import ua.acclorite.book_story.data.remote.dto.LatestReleaseInfo import ua.acclorite.book_story.domain.model.Book import ua.acclorite.book_story.domain.model.History -import ua.acclorite.book_story.domain.model.LanguageHistory import ua.acclorite.book_story.domain.model.NullableBook import ua.acclorite.book_story.domain.repository.BookRepository import ua.acclorite.book_story.domain.util.Constants import ua.acclorite.book_story.domain.util.CoverImage -import ua.acclorite.book_story.domain.util.DataStoreConstants -import ua.acclorite.book_story.domain.util.LanguageCode import ua.acclorite.book_story.domain.util.Resource import ua.acclorite.book_story.domain.util.UIText import ua.acclorite.book_story.presentation.data.MainState import java.io.ByteArrayOutputStream import java.io.File import java.io.FileOutputStream -import java.util.Locale import java.util.UUID import javax.inject.Inject import javax.inject.Singleton -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine @Suppress("DEPRECATION") @Singleton @@ -64,15 +49,12 @@ class BookRepositoryImpl @Inject constructor( private val application: Application, private val database: BookDao, private val dataStore: DataStore, - private val modelManager: RemoteModelManager, - private val languageIdentifier: LanguageIdentifier, private val githubAPI: GithubAPI, private val updatesNotificationService: UpdatesNotificationService, private val bookMapper: BookMapper, private val historyMapper: HistoryMapper, - private val languageHistoryMapper: LanguageHistoryMapper, private val fileParser: FileParser, private val textParser: TextParser @@ -190,34 +172,11 @@ class BookRepositoryImpl @Inject constructor( } } - val enableTranslator = dataStore.getNullableData(DataStoreConstants.ENABLE_TRANSLATOR) - ?: false - - val translateFrom = dataStore.getNullableData(DataStoreConstants.TRANSLATE_FROM) - ?: "auto" - - val translateTo = dataStore.getNullableData(DataStoreConstants.TRANSLATE_TO) ?: if ( - Constants.LANGUAGES.any { Locale.getDefault().language.take(2) == it.first } - ) { - Locale.getDefault().language.take(2) - } else { - "en" - } - - val doubleClickTranslation = dataStore.getNullableData( - DataStoreConstants.DOUBLE_CLICK_TRANSLATION - ) ?: true - - val updatedBook = book.copy( textPath = "$booksDir/$textUri", coverImage = if (coverUri.isNotBlank()) { Uri.fromFile(File("$coversDir/$coverUri")) - } else null, - enableTranslator = enableTranslator, - translateFrom = translateFrom, - translateTo = translateTo, - doubleClickTranslation = doubleClickTranslation + } else null ) val bookToInsert = bookMapper.toBookEntity(updatedBook) @@ -708,174 +667,6 @@ class BookRepositoryImpl @Inject constructor( } } } - - override suspend fun isLanguageModelDownloaded(languageCode: String): Boolean { - val model = TranslateRemoteModel.Builder(languageCode).build() - return suspendCancellableCoroutine { continuation -> - modelManager.isModelDownloaded(model) - .addOnSuccessListener { - continuation.resume(it) - } - .addOnFailureListener { - continuation.resume(false) - } - } - } - - override suspend fun downloadLanguageModel( - languageCode: String, - onCompleted: () -> Unit, - onFailure: (Exception) -> Unit - ) { - val isLanguageDownloaded = isLanguageModelDownloaded(languageCode) - if (isLanguageDownloaded) { - onCompleted() - return - } - - return suspendCancellableCoroutine { continuation -> - continuation.invokeOnCancellation { - Log.i("TRANSLATOR", "Downloading Model: Coroutine Canceled") - onCompleted() - } - - val language = TranslateRemoteModel.Builder(languageCode).build() - val conditions = DownloadConditions.Builder().build() - - modelManager.download(language, conditions) - .addOnSuccessListener { - onCompleted() - continuation.resume(Unit) - } - .addOnFailureListener { - onFailure(it) - onCompleted() - continuation.resume(Unit) - } - .addOnCanceledListener { - onCompleted() - Log.i("TRANSLATOR", "Downloading Model: Canceled") - continuation.resume(Unit) - } - } - } - - override suspend fun deleteLanguageModel(languageCode: String): Boolean { - val isLanguageDownloaded = isLanguageModelDownloaded(languageCode) - if (!isLanguageDownloaded) { - return true - } - - return suspendCancellableCoroutine { continuation -> - val language = TranslateRemoteModel.Builder(languageCode).build() - - modelManager.deleteDownloadedModel(language) - .addOnSuccessListener { - continuation.resume(true) - } - .addOnFailureListener { - continuation.resume(false) - } - } - } - - override suspend fun identifyLanguage(text: String): Resource { - return suspendCancellableCoroutine { continuation -> - languageIdentifier.identifyLanguage(text) - .addOnSuccessListener { - val languageCode = it.trim().lowercase().take(2) - var identified = true - - if (languageCode == "ru") { - identified = false - } - - if (TranslateLanguage.fromLanguageTag(languageCode) == null) { - identified = false - } - - if (identified) { - continuation.resume( - Resource.Success(languageCode) - ) - return@addOnSuccessListener - } - - continuation.resume( - Resource.Error(UIText.StringResource(R.string.error_language_not_identified)) - ) - } - .addOnFailureListener { - continuation.resume( - Resource.Error( - UIText.StringResource( - R.string.error_query, - it.message ?: "" - ) - ) - ) - } - } - } - - override suspend fun translateText( - sourceLanguage: LanguageCode, - targetLanguage: LanguageCode, - text: String - ): Resource { - return suspendCoroutine { continuation -> - val translatorOptions = TranslatorOptions.Builder() - .setSourceLanguage(sourceLanguage) - .setTargetLanguage(targetLanguage) - .build() - val translator = Translation.getClient(translatorOptions) - - translator.translate(text) - .addOnSuccessListener { - translator.close() - continuation.resume( - Resource.Success( - it.trim() - ) - ) - } - .addOnFailureListener { - translator.close() - continuation.resume( - Resource.Error( - UIText.StringResource( - R.string.error_query, - it.message ?: "" - ) - ) - ) - } - .addOnCanceledListener { - translator.close() - continuation.resume( - Resource.Error( - UIText.StringResource( - R.string.error_something_went_wrong - ) - ) - ) - } - } - } - - override suspend fun getLanguageHistory(): List { - return database.getLanguageHistory().map { - languageHistoryMapper.toLanguageHistory(it) - } - } - - override suspend fun updateLanguageHistory(history: List) { - database.updateLanguageHistory( - history.map { - languageHistoryMapper.toLanguageHistoryEntity(it) - } - ) - } } diff --git a/app/src/main/java/ua/acclorite/book_story/domain/model/Book.kt b/app/src/main/java/ua/acclorite/book_story/domain/model/Book.kt index f2762588..f6d5ba17 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/model/Book.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/model/Book.kt @@ -22,10 +22,4 @@ data class Book( val lastOpened: Long?, val category: Category, - - val enableTranslator: Boolean, - val translateFrom: String, - val translateTo: String, - val doubleClickTranslation: Boolean, - val translateWhenOpen: Boolean ) diff --git a/app/src/main/java/ua/acclorite/book_story/domain/model/Language.kt b/app/src/main/java/ua/acclorite/book_story/domain/model/Language.kt deleted file mode 100644 index e8ec13a2..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/model/Language.kt +++ /dev/null @@ -1,31 +0,0 @@ -package ua.acclorite.book_story.domain.model - -import androidx.compose.runtime.Immutable -import ua.acclorite.book_story.domain.util.ID -import ua.acclorite.book_story.domain.util.Selected -import ua.acclorite.book_story.domain.util.UIText - -@Immutable -data class Language( - val languageCode: String, - val displayLanguage: String, - val isDownloading: Boolean, - val isCanceled: Boolean, - val isDownloaded: Boolean, - val isSelected: Selected, - val historyOrder: Int? -) - -@Immutable -data class SelectableLanguage( - val languageCode: String, - val displayLanguage: String, - val isDownloading: Boolean, - val isCanceled: Boolean, - val isDownloaded: Boolean, - val isError: Boolean, - val errorMessage: UIText? = null, - val isSelected: Selected, - val canUnselect: Boolean, - val occurrences: List? -) diff --git a/app/src/main/java/ua/acclorite/book_story/domain/model/LanguageHistory.kt b/app/src/main/java/ua/acclorite/book_story/domain/model/LanguageHistory.kt deleted file mode 100644 index f99b1869..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/model/LanguageHistory.kt +++ /dev/null @@ -1,9 +0,0 @@ -package ua.acclorite.book_story.domain.model - -import androidx.compose.runtime.Immutable - -@Immutable -data class LanguageHistory( - val languageCode: String, - val order: Int -) diff --git a/app/src/main/java/ua/acclorite/book_story/domain/model/LineWithTranslation.kt b/app/src/main/java/ua/acclorite/book_story/domain/model/LineWithTranslation.kt deleted file mode 100644 index 0b636587..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/model/LineWithTranslation.kt +++ /dev/null @@ -1,21 +0,0 @@ -package ua.acclorite.book_story.domain.model - -import android.os.Parcelable -import androidx.compose.runtime.Immutable -import kotlinx.parcelize.Parcelize -import ua.acclorite.book_story.domain.util.UIText - -@Parcelize -@Immutable -data class LineWithTranslation( - val originalLine: String, - - val useTranslation: Boolean = false, - val translatingFrom: String = "", - val translatingTo: String = "", - val translatedLine: String? = null, - - val isTranslationLoading: Boolean = false, - val isTranslationFailed: Boolean = false, - val errorMessage: UIText? = null -) : Parcelable diff --git a/app/src/main/java/ua/acclorite/book_story/domain/model/ReaderLine.kt b/app/src/main/java/ua/acclorite/book_story/domain/model/ReaderLine.kt new file mode 100644 index 00000000..cd86cd85 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/model/ReaderLine.kt @@ -0,0 +1,11 @@ +package ua.acclorite.book_story.domain.model + +import android.os.Parcelable +import androidx.compose.runtime.Immutable +import kotlinx.parcelize.Parcelize + +@Parcelize +@Immutable +data class ReaderLine( + val line: String +) : Parcelable \ No newline at end of file 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 7f83f74c..8015a2b9 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 @@ -6,10 +6,8 @@ import kotlinx.coroutines.flow.Flow import ua.acclorite.book_story.data.remote.dto.LatestReleaseInfo import ua.acclorite.book_story.domain.model.Book import ua.acclorite.book_story.domain.model.History -import ua.acclorite.book_story.domain.model.LanguageHistory import ua.acclorite.book_story.domain.model.NullableBook import ua.acclorite.book_story.domain.util.CoverImage -import ua.acclorite.book_story.domain.util.LanguageCode import ua.acclorite.book_story.domain.util.Resource import ua.acclorite.book_story.presentation.data.MainState import java.io.File @@ -91,30 +89,5 @@ interface BookRepository { history: List ) - suspend fun checkForUpdates(postNotification: Boolean): LatestReleaseInfo? - - - suspend fun isLanguageModelDownloaded(languageCode: String): Boolean - - suspend fun downloadLanguageModel( - languageCode: String, - onCompleted: () -> Unit, - onFailure: (Exception) -> Unit - ) - - suspend fun deleteLanguageModel(languageCode: String): Boolean - - suspend fun identifyLanguage(text: String): Resource - - suspend fun translateText( - sourceLanguage: LanguageCode, - targetLanguage: LanguageCode, - text: String - ): Resource - - - suspend fun getLanguageHistory(): List - - suspend fun updateLanguageHistory(history: List) } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/DeleteLanguageModel.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/DeleteLanguageModel.kt deleted file mode 100644 index c1fa11be..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/DeleteLanguageModel.kt +++ /dev/null @@ -1,15 +0,0 @@ -package ua.acclorite.book_story.domain.use_case - -import ua.acclorite.book_story.domain.repository.BookRepository -import javax.inject.Inject - -class DeleteLanguageModel @Inject constructor( - private val repository: BookRepository -) { - - suspend fun execute(languageCode: String): Boolean { - return repository.deleteLanguageModel( - languageCode = languageCode - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/DownloadLanguageModel.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/DownloadLanguageModel.kt deleted file mode 100644 index b9101483..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/DownloadLanguageModel.kt +++ /dev/null @@ -1,21 +0,0 @@ -package ua.acclorite.book_story.domain.use_case - -import ua.acclorite.book_story.domain.repository.BookRepository -import javax.inject.Inject - -class DownloadLanguageModel @Inject constructor( - private val repository: BookRepository -) { - - suspend fun execute( - languageCode: String, - onCompleted: () -> Unit, - onFailure: (Exception) -> Unit - ) { - repository.downloadLanguageModel( - languageCode = languageCode, - onCompleted = onCompleted, - onFailure = onFailure - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetLanguageHistory.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetLanguageHistory.kt deleted file mode 100644 index b522c361..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetLanguageHistory.kt +++ /dev/null @@ -1,12 +0,0 @@ -package ua.acclorite.book_story.domain.use_case - -import ua.acclorite.book_story.domain.model.LanguageHistory -import ua.acclorite.book_story.domain.repository.BookRepository -import javax.inject.Inject - -class GetLanguageHistory @Inject constructor(private val repository: BookRepository) { - - suspend fun execute(): List { - return repository.getLanguageHistory() - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetText.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetText.kt index 03c88ceb..e7311860 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetText.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetText.kt @@ -1,15 +1,15 @@ package ua.acclorite.book_story.domain.use_case -import ua.acclorite.book_story.domain.model.LineWithTranslation +import ua.acclorite.book_story.domain.model.ReaderLine import ua.acclorite.book_story.domain.repository.BookRepository import ua.acclorite.book_story.domain.util.ID import javax.inject.Inject class GetText @Inject constructor(private val repository: BookRepository) { - suspend fun execute(textPath: String): Map { + suspend fun execute(textPath: String): Map { return repository.getBookText(textPath = textPath).withIndex().associate { - it.index to LineWithTranslation(originalLine = it.value) + it.index to ReaderLine(line = it.value) } } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/IdentifyLanguage.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/IdentifyLanguage.kt deleted file mode 100644 index 8050d985..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/IdentifyLanguage.kt +++ /dev/null @@ -1,15 +0,0 @@ -package ua.acclorite.book_story.domain.use_case - -import ua.acclorite.book_story.domain.repository.BookRepository -import ua.acclorite.book_story.domain.util.LanguageCode -import ua.acclorite.book_story.domain.util.Resource -import javax.inject.Inject - -class IdentifyLanguage @Inject constructor( - private val repository: BookRepository -) { - - suspend fun execute(text: String): Resource { - return repository.identifyLanguage(text) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/IsLanguageModelDownloaded.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/IsLanguageModelDownloaded.kt deleted file mode 100644 index 1b99e7f5..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/IsLanguageModelDownloaded.kt +++ /dev/null @@ -1,13 +0,0 @@ -package ua.acclorite.book_story.domain.use_case - -import ua.acclorite.book_story.domain.repository.BookRepository -import javax.inject.Inject - -class IsLanguageModelDownloaded @Inject constructor( - private val repository: BookRepository -) { - - suspend fun execute(languageCode: String): Boolean { - return repository.isLanguageModelDownloaded(languageCode) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/TranslateText.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/TranslateText.kt deleted file mode 100644 index d4dcdb26..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/TranslateText.kt +++ /dev/null @@ -1,23 +0,0 @@ -package ua.acclorite.book_story.domain.use_case - -import ua.acclorite.book_story.domain.repository.BookRepository -import ua.acclorite.book_story.domain.util.LanguageCode -import ua.acclorite.book_story.domain.util.Resource -import javax.inject.Inject - -class TranslateText @Inject constructor( - private val repository: BookRepository -) { - - suspend fun execute( - sourceLanguage: LanguageCode, - targetLanguage: LanguageCode, - text: String - ): Resource { - return repository.translateText( - sourceLanguage, - targetLanguage, - text - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/UpdateLanguageHistory.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/UpdateLanguageHistory.kt deleted file mode 100644 index 3ecf61ba..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/UpdateLanguageHistory.kt +++ /dev/null @@ -1,12 +0,0 @@ -package ua.acclorite.book_story.domain.use_case - -import ua.acclorite.book_story.domain.model.LanguageHistory -import ua.acclorite.book_story.domain.repository.BookRepository -import javax.inject.Inject - -class UpdateLanguageHistory @Inject constructor(private val repository: BookRepository) { - - suspend fun execute(history: List) { - repository.updateLanguageHistory(history) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/util/Constants.kt b/app/src/main/java/ua/acclorite/book_story/domain/util/Constants.kt index 088df6a7..1ec88b7b 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/util/Constants.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/util/Constants.kt @@ -291,11 +291,6 @@ object Constants { scrollOffset = 0, progress = 0f, lastOpened = null, - category = Category.READING, - enableTranslator = false, - translateFrom = "", - translateTo = "", - doubleClickTranslation = false, - translateWhenOpen = false + category = Category.READING ) } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/util/DataStoreConstants.kt b/app/src/main/java/ua/acclorite/book_story/domain/util/DataStoreConstants.kt index c896dbfd..f0f7b8c3 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/util/DataStoreConstants.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/util/DataStoreConstants.kt @@ -23,8 +23,5 @@ object DataStoreConstants { val PARAGRAPH_INDENTATION = booleanPreferencesKey("paragraph_indentation") val CHECK_FOR_UPDATES = booleanPreferencesKey("check_for_updates") val SIDE_PADDING = intPreferencesKey("side_padding") - val ENABLE_TRANSLATOR = booleanPreferencesKey("enable_translator") - val TRANSLATE_FROM = stringPreferencesKey("translate_from") - val TRANSLATE_TO = stringPreferencesKey("translate_to") val DOUBLE_CLICK_TRANSLATION = booleanPreferencesKey("double_click_translation") } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/util/Typealias.kt b/app/src/main/java/ua/acclorite/book_story/domain/util/Typealias.kt index a88b6e2d..9f8b0176 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/util/Typealias.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/util/Typealias.kt @@ -6,6 +6,5 @@ import ua.acclorite.book_story.presentation.data.Navigator typealias CoverImage = Bitmap typealias Selected = Boolean typealias ID = Int -typealias LanguageCode = String typealias Route = String typealias OnNavigate = (Navigator.() -> Unit) -> Unit diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/translator_language/TranslatorLanguageBottomSheet.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/translator_language/TranslatorLanguageBottomSheet.kt deleted file mode 100644 index d2054b95..00000000 --- a/app/src/main/java/ua/acclorite/book_story/presentation/components/translator_language/TranslatorLanguageBottomSheet.kt +++ /dev/null @@ -1,298 +0,0 @@ -package ua.acclorite.book_story.presentation.components.translator_language - -import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeOut -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.AutoAwesome -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.State -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel -import ua.acclorite.book_story.R -import ua.acclorite.book_story.presentation.components.CategoryTitle -import ua.acclorite.book_story.presentation.components.CustomAnimatedVisibility -import ua.acclorite.book_story.presentation.components.CustomBottomSheet -import ua.acclorite.book_story.presentation.components.customItems -import ua.acclorite.book_story.presentation.components.is_messages.IsEmpty -import ua.acclorite.book_story.presentation.components.translator_language.components.TranslatorLanguageDownloadDialog -import ua.acclorite.book_story.presentation.components.translator_language.components.TranslatorLanguageItem -import ua.acclorite.book_story.presentation.components.translator_language.components.TranslatorLanguageItemWithIcon -import ua.acclorite.book_story.presentation.components.translator_language.components.TranslatorLanguageTopBar -import ua.acclorite.book_story.presentation.components.translator_language.data.TranslatorLanguageEvent -import ua.acclorite.book_story.presentation.components.translator_language.data.TranslatorLanguageState -import ua.acclorite.book_story.presentation.components.translator_language.data.TranslatorLanguageViewModel -import ua.acclorite.book_story.presentation.ui.DefaultTransition -import ua.acclorite.book_story.presentation.ui.Transitions - -/** - * Translator language bottom sheet. - * Manages downloading, deleting and selecting language for translator. - * - * @param selectedLanguage Current selected language. - * @param unselectedLanguage Current unselected language(second). - * @param translateFromSelecting Whether user selects "Translate from" or "Translate to" language. - * @param onDismiss Dismissing BottomSheet. - * @param onSelect Gives two variables, first - Translate from, second - Translate to. If user selects "Translate from" and then selects current unselected language(in this case "Translate to"), then it also changes "Translate to" to previous "Translate from" value. - */ -@Composable -fun TranslatorLanguageBottomSheet( - selectedLanguage: String, - unselectedLanguage: String, - translateFromSelecting: Boolean, - onDismiss: () -> Unit, - onSelect: (String, String) -> Unit -) { - val translatorLanguageViewModel: TranslatorLanguageViewModel = hiltViewModel() - - val state = translatorLanguageViewModel.state.collectAsState() - val loading = remember { mutableStateOf(true) } - - LaunchedEffect(Unit) { - translatorLanguageViewModel.init( - selectedLanguage = selectedLanguage, - unselectedLanguage = unselectedLanguage, - translateFromSelecting = translateFromSelecting, - loaded = { - loading.value = false - } - ) - } - - TranslatorLanguageBottomSheetContent( - state = state, - loading = loading, - onSelect = { from, to -> - val fromSelecting = state.value.translateFromSelecting - val languageToChoose = if (fromSelecting) from else to - - if (languageToChoose != "auto") { - translatorLanguageViewModel.onEvent( - TranslatorLanguageEvent.OnUpdateLanguageHistory( - languageToChoose - ) - ) - } - onSelect(from, to) - }, - onDismiss = onDismiss, - onEvent = translatorLanguageViewModel::onEvent - ) -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -private fun TranslatorLanguageBottomSheetContent( - state: State, - loading: State, - onSelect: (String, String) -> Unit, - onDismiss: () -> Unit, - onEvent: (TranslatorLanguageEvent) -> Unit -) { - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() - - val languagesWithHistory = remember(state.value.languages) { - state.value.languages.filter { - it.historyOrder != null - }.sortedBy { - it.historyOrder - } - } - val languages = remember(state.value.languages) { - state.value.languages.filter { - it.historyOrder == null - }.sortedBy { - it.languageCode - } - } - - CustomBottomSheet( - hasFixedHeight = true, - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight(), - dragHandle = {}, - shape = RoundedCornerShape(0), - scrimColor = MaterialTheme.colorScheme.surface, - onDismissRequest = { - onDismiss() - }, - containerColor = MaterialTheme.colorScheme.surface - ) { - Scaffold( - modifier = Modifier - .fillMaxSize() - .nestedScroll(scrollBehavior.nestedScrollConnection), - containerColor = Color.Transparent, - topBar = { - TranslatorLanguageTopBar( - state = state, - scrollBehavior = scrollBehavior, - onDismissBottomSheet = onDismiss, - onEvent = onEvent - ) - } - ) { paddingValues -> - Box( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues), - contentAlignment = Alignment.Center - ) { - DefaultTransition( - visible = !loading.value, - modifier = Modifier.fillMaxSize() - ) { - if (state.value.showDownloadLanguageDialog) { - TranslatorLanguageDownloadDialog( - state = state, - onSelect = onSelect, - onEvent = onEvent - ) - } - - LazyColumn(Modifier.fillMaxSize()) { - item { - Spacer( - modifier = Modifier - .animateItem() - .height(12.dp) - ) - } - - if ( - state.value.translateFromSelecting && - (!state.value.showSearch || state.value.searchQuery.isEmpty()) - ) { - item { - val isSelected = remember(state.value.selectedLanguage) { - state.value.selectedLanguage == "auto" - } - - TranslatorLanguageItemWithIcon( - isSelected = isSelected, - displayLanguage = stringResource(id = R.string.translator_auto), - customIcon = Icons.Default.AutoAwesome, - onSelect = { - onSelect("auto", state.value.unselectedLanguage) - } - ) - - Spacer( - modifier = Modifier - .animateItem() - .height(36.dp) - ) - } - } - - if (languagesWithHistory.isNotEmpty()) { - item { - CategoryTitle( - title = stringResource(id = R.string.translator_recent_languages), - color = MaterialTheme.colorScheme.primary, - padding = 28.dp, - modifier = Modifier.animateItem() - ) - Spacer( - modifier = Modifier - .animateItem() - .height(14.dp) - ) - } - - - customItems( - languagesWithHistory, - key = { it.languageCode } - ) { language -> - TranslatorLanguageItem( - language = language, - onSelect = onSelect, - onEvent = onEvent - ) - } - } - - if (languages.isNotEmpty()) { - item { - if (languagesWithHistory.isNotEmpty()) { - Spacer( - modifier = Modifier - .animateItem() - .height(36.dp) - ) - } - - CategoryTitle( - title = stringResource(id = R.string.translator_all_languages), - color = MaterialTheme.colorScheme.primary, - padding = 28.dp, - modifier = Modifier.animateItem() - ) - Spacer( - modifier = Modifier - .animateItem() - .height(14.dp) - ) - } - - customItems( - languages, - key = { it.languageCode } - ) { language -> - TranslatorLanguageItem( - language = language, - onSelect = onSelect, - onEvent = onEvent - ) - } - } - - item { - Spacer( - modifier = Modifier - .animateItem() - .height(12.dp) - ) - } - } - - CustomAnimatedVisibility( - visible = state.value.languages.isEmpty(), - modifier = Modifier.align(Alignment.Center), - enter = Transitions.DefaultTransitionIn, - exit = fadeOut(tween(0)) - ) { - IsEmpty( - message = stringResource(id = R.string.translator_languages_empty), - icon = painterResource(id = R.drawable.empty_languages) - ) - } - } - } - } - } -} diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/translator_language/components/TranslatorLanguageDownloadDialog.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/translator_language/components/TranslatorLanguageDownloadDialog.kt deleted file mode 100644 index 2a49a48b..00000000 --- a/app/src/main/java/ua/acclorite/book_story/presentation/components/translator_language/components/TranslatorLanguageDownloadDialog.kt +++ /dev/null @@ -1,100 +0,0 @@ -package ua.acclorite.book_story.presentation.components.translator_language.components - -import android.widget.Toast -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Download -import androidx.compose.runtime.Composable -import androidx.compose.runtime.State -import androidx.compose.runtime.remember -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import ua.acclorite.book_story.R -import ua.acclorite.book_story.presentation.components.custom_dialog.CustomDialogWithContent -import ua.acclorite.book_story.presentation.components.translator_language.data.TranslatorLanguageEvent -import ua.acclorite.book_story.presentation.components.translator_language.data.TranslatorLanguageState -import java.util.Locale - -/** - * Translator Language Download Dialog. - * Used for downloading languages and selecting them right after. - * - * @param state [TranslatorLanguageState]. - * @param onSelect OnSelect callback. Happens when language is downloaded. - * @param onEvent [TranslatorLanguageEvent] callback. - */ -@Composable -fun TranslatorLanguageDownloadDialog( - state: State, - onSelect: (String, String) -> Unit, - onEvent: (TranslatorLanguageEvent) -> Unit -) { - val context = LocalContext.current - val displayLanguage = remember(state.value.languageToSelect) { - val locale = Locale(state.value.languageToSelect) - locale.getDisplayLanguage(locale).replaceFirstChar { - it.uppercase() - } - } - - CustomDialogWithContent( - title = stringResource(id = R.string.download_language, displayLanguage), - imageVectorIcon = Icons.Default.Download, - description = stringResource( - id = R.string.download_language_description - ), - actionText = stringResource(id = R.string.download), - isActionEnabled = true, - disableOnClick = true, - onDismiss = { onEvent(TranslatorLanguageEvent.OnDismissDownloadDialog) }, - onAction = { - val selectedLanguage = state.value.languageToSelect - - onEvent(TranslatorLanguageEvent.OnDismissDownloadDialog) - onEvent( - TranslatorLanguageEvent.OnDownloadLanguage( - languageCode = state.value.languageToSelect, - onComplete = { - if (selectedLanguage == state.value.languageToSelect) { - withContext(Dispatchers.Main) { - Toast.makeText( - context, - context.getString(R.string.language_downloaded), - Toast.LENGTH_LONG - ).show() - } - - onEvent( - TranslatorLanguageEvent.OnSelectLanguage( - languageCode = state.value.languageToSelect, - onSelect = onSelect, - onError = { - Toast - .makeText( - context, - it.asString(context), - Toast.LENGTH_LONG - ) - .show() - } - ) - ) - } - }, - onFailure = { - Toast.makeText( - context, - context.getString( - R.string.error_query, - it.message - ), - Toast.LENGTH_LONG - ).show() - } - ) - ) - }, - withDivider = false - ) -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/translator_language/components/TranslatorLanguageItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/translator_language/components/TranslatorLanguageItem.kt deleted file mode 100644 index 5f1afe22..00000000 --- a/app/src/main/java/ua/acclorite/book_story/presentation/components/translator_language/components/TranslatorLanguageItem.kt +++ /dev/null @@ -1,243 +0,0 @@ -package ua.acclorite.book_story.presentation.components.translator_language.components - -import android.widget.Toast -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyItemScope -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Check -import androidx.compose.material.icons.filled.CheckCircle -import androidx.compose.material.icons.filled.Download -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import ua.acclorite.book_story.R -import ua.acclorite.book_story.domain.model.Language -import ua.acclorite.book_story.presentation.components.translator_language.data.TranslatorLanguageEvent -import ua.acclorite.book_story.presentation.ui.DefaultTransition - -/** - * Translator Language Item with Icon. - * - * @param isSelected Whether item is selected. - * @param displayLanguage Display language to be shown. - * @param customIcon Icon. - * @param onSelect OnSelect(click) callback. - */ -@Composable -fun LazyItemScope.TranslatorLanguageItemWithIcon( - isSelected: Boolean, - displayLanguage: String, - customIcon: ImageVector, - onSelect: () -> Unit, -) { - Row( - modifier = Modifier - .animateItem() - .padding(horizontal = 12.dp) - .clip(RoundedCornerShape(100)) - .background( - if (isSelected) MaterialTheme.colorScheme.primaryContainer - else Color.Transparent, - RoundedCornerShape(100) - ) - .clickable { - onSelect() - } - .padding(horizontal = 16.dp, vertical = 14.dp), - verticalAlignment = Alignment.CenterVertically - ) { - if (isSelected) { - Icon( - imageVector = Icons.Default.Check, - contentDescription = stringResource(R.string.selected_content_desc), - tint = MaterialTheme.colorScheme.onPrimaryContainer, - modifier = Modifier.size(24.dp) - ) - Spacer(modifier = Modifier.width(16.dp)) - } - - Text( - displayLanguage, - color = if (isSelected) MaterialTheme.colorScheme.onPrimaryContainer - else MaterialTheme.colorScheme.onSurface, - style = MaterialTheme.typography.bodyLarge, - modifier = Modifier.weight(1f) - ) - - Spacer(modifier = Modifier.width(16.dp)) - Icon( - imageVector = customIcon, - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.size(24.dp) - ) - } -} - -/** - * Translator Language Item with Icon. - * - * @param language [Language] item. - * @param onSelect OnSelect(click) callback. - * @param onEvent [TranslatorLanguageEvent] callback. - */ -@Composable -fun LazyItemScope.TranslatorLanguageItem( - language: Language, - onSelect: (String, String) -> Unit, - onEvent: (TranslatorLanguageEvent) -> Unit -) { - val context = LocalContext.current - - Row( - modifier = Modifier - .animateItem() - .padding(horizontal = 12.dp) - .clip(RoundedCornerShape(100)) - .background( - if (language.isSelected) MaterialTheme.colorScheme.primaryContainer - else Color.Transparent, - RoundedCornerShape(100) - ) - .clickable(enabled = !language.isDownloading) { - onEvent( - TranslatorLanguageEvent.OnSelectLanguage( - languageCode = language.languageCode, - onSelect = onSelect, - onError = { - Toast - .makeText( - context, - it.asString(context), - Toast.LENGTH_LONG - ) - .show() - } - ) - ) - } - .padding(horizontal = 16.dp, vertical = 14.dp), - verticalAlignment = Alignment.CenterVertically - ) { - if (language.isSelected) { - Icon( - imageVector = Icons.Default.Check, - contentDescription = stringResource(R.string.selected_content_desc), - tint = MaterialTheme.colorScheme.onPrimaryContainer, - modifier = Modifier.size(24.dp) - ) - Spacer(modifier = Modifier.width(16.dp)) - } - - Text( - language.displayLanguage, - color = if (language.isSelected) MaterialTheme.colorScheme.onPrimaryContainer - else MaterialTheme.colorScheme.onSurface, - style = MaterialTheme.typography.bodyLarge, - modifier = Modifier.weight(1f) - ) - - Box(contentAlignment = Alignment.CenterEnd) { - DefaultTransition(!language.isDownloaded && !language.isDownloading) { - Row { - Spacer(modifier = Modifier.width(16.dp)) - Icon( - imageVector = Icons.Default.Download, - contentDescription = stringResource(id = R.string.download), - tint = if (language.isSelected) MaterialTheme.colorScheme.onSurfaceVariant - else MaterialTheme.colorScheme.primary, - modifier = Modifier - .size(24.dp) - .clickable( - interactionSource = null, - indication = null - ) { - onEvent( - TranslatorLanguageEvent.OnDownloadLanguage( - languageCode = language.languageCode, - onComplete = {}, - onFailure = { - Toast - .makeText( - context, - context.getString( - R.string.error_query, - it.message - ), - Toast.LENGTH_LONG - ) - .show() - } - ) - ) - } - ) - } - } - - DefaultTransition(language.isDownloading && !language.isDownloaded) { - Row { - Spacer(modifier = Modifier.width(16.dp)) - CircularProgressIndicator( - strokeWidth = 2.7.dp, - modifier = Modifier - .size(22.dp) - .clickable( - interactionSource = null, - indication = null - ) { - onEvent( - TranslatorLanguageEvent.OnCancelDownload( - languageCode = language.languageCode - ) - ) - }, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - Spacer(modifier = Modifier.width(1.dp)) - } - } - - DefaultTransition(language.isDownloaded && !language.isDownloading) { - Row { - Spacer(modifier = Modifier.width(16.dp)) - Icon( - imageVector = Icons.Filled.CheckCircle, - contentDescription = stringResource(id = R.string.done), - tint = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier - .size(24.dp) - .clickable( - interactionSource = null, - indication = null - ) { - onEvent( - TranslatorLanguageEvent.OnDeleteLanguage( - languageCode = language.languageCode - ) - ) - } - ) - } - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/translator_language/components/TranslatorLanguageTopBar.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/translator_language/components/TranslatorLanguageTopBar.kt deleted file mode 100644 index 3274cbec..00000000 --- a/app/src/main/java/ua/acclorite/book_story/presentation/components/translator_language/components/TranslatorLanguageTopBar.kt +++ /dev/null @@ -1,107 +0,0 @@ -@file:OptIn(ExperimentalMaterial3Api::class) - -package ua.acclorite.book_story.presentation.components.translator_language.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.filled.Close -import androidx.compose.material.icons.filled.Search -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarScrollBehavior -import androidx.compose.runtime.Composable -import androidx.compose.runtime.State -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.res.stringResource -import ua.acclorite.book_story.R -import ua.acclorite.book_story.presentation.components.AnimatedTopAppBar -import ua.acclorite.book_story.presentation.components.CustomIconButton -import ua.acclorite.book_story.presentation.components.CustomSearchTextField -import ua.acclorite.book_story.presentation.components.translator_language.data.TranslatorLanguageEvent -import ua.acclorite.book_story.presentation.components.translator_language.data.TranslatorLanguageState - -/** - * Translator Language TopBar. - * - * @param state [TranslatorLanguageState]. - * @param scrollBehavior [TopAppBarScrollBehavior]. - * @param onDismissBottomSheet OnDismiss callback. - * @param onEvent [TranslatorLanguageEvent] callback. - */ -@Composable -fun TranslatorLanguageTopBar( - state: State, - scrollBehavior: TopAppBarScrollBehavior, - onDismissBottomSheet: () -> Unit, - onEvent: (TranslatorLanguageEvent) -> Unit -) { - val focusRequester = remember { FocusRequester() } - - AnimatedTopAppBar( - scrollBehavior = scrollBehavior, - isTopBarScrolled = null, - - content1Visibility = !state.value.showSearch, - content1NavigationIcon = { - CustomIconButton( - icon = Icons.Default.Close, - contentDescription = R.string.go_back_content_desc, - disableOnClick = true - ) { - onDismissBottomSheet() - } - }, - content1Title = { - Text( - text = stringResource( - id = R.string.translator_query, stringResource( - if (state.value.translateFromSelecting) R.string.translator_from - else R.string.translator_to - ) - ) - ) - }, - content1Actions = { - CustomIconButton( - icon = Icons.Default.Search, - contentDescription = R.string.search_content_desc, - disableOnClick = true - ) { - onEvent(TranslatorLanguageEvent.OnSearchShowHide) - } - }, - - content2Visibility = state.value.showSearch, - content2NavigationIcon = { - CustomIconButton( - icon = Icons.AutoMirrored.Default.ArrowBack, - contentDescription = R.string.exit_search_content_desc, - disableOnClick = true - ) { - onEvent(TranslatorLanguageEvent.OnSearchShowHide) - } - }, - content2Title = { - CustomSearchTextField( - modifier = Modifier - .focusRequester(focusRequester) - .onGloballyPositioned { - onEvent(TranslatorLanguageEvent.OnRequestFocus(focusRequester)) - }, - query = state.value.searchQuery, - onQueryChange = { - onEvent(TranslatorLanguageEvent.OnSearchQueryChange(it)) - }, - onSearch = { onEvent(TranslatorLanguageEvent.OnSearch) }, - placeholder = stringResource( - id = R.string.search_query, - stringResource(id = R.string.languages) - ) - ) - } - ) -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/translator_language/data/TranslatorLanguageEvent.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/translator_language/data/TranslatorLanguageEvent.kt deleted file mode 100644 index 2adb04ac..00000000 --- a/app/src/main/java/ua/acclorite/book_story/presentation/components/translator_language/data/TranslatorLanguageEvent.kt +++ /dev/null @@ -1,44 +0,0 @@ -package ua.acclorite.book_story.presentation.components.translator_language.data - -import androidx.compose.runtime.Immutable -import androidx.compose.ui.focus.FocusRequester -import ua.acclorite.book_story.domain.model.Language -import ua.acclorite.book_story.domain.util.LanguageCode -import ua.acclorite.book_story.domain.util.UIText - -@Immutable -sealed class TranslatorLanguageEvent { - data class OnSelectLanguage( - val languageCode: String, - val onError: suspend (UIText) -> Unit, - val onSelect: (String, String) -> Unit - ) : TranslatorLanguageEvent() - - data class OnDownloadLanguage( - val languageCode: String, - val onComplete: suspend () -> Unit, - val onFailure: (Exception) -> Unit - ) : TranslatorLanguageEvent() - - data class OnCancelDownload( - val languageCode: String - ) : TranslatorLanguageEvent() - - data class OnDeleteLanguage( - val languageCode: String - ) : TranslatorLanguageEvent() - - data object OnSearchShowHide : TranslatorLanguageEvent() - data class OnRequestFocus(val focusRequester: FocusRequester) : TranslatorLanguageEvent() - data class OnSearchQueryChange(val query: String) : TranslatorLanguageEvent() - data object OnSearch : TranslatorLanguageEvent() - data object OnDismissDownloadDialog : TranslatorLanguageEvent() - data class OnUpdateLanguage( - val languageCode: String, - val block: (Language) -> Language - ) : TranslatorLanguageEvent() - - data class OnUpdateLanguageHistory( - val languageToChoose: LanguageCode - ) : TranslatorLanguageEvent() -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/translator_language/data/TranslatorLanguageState.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/translator_language/data/TranslatorLanguageState.kt deleted file mode 100644 index 0ffc0005..00000000 --- a/app/src/main/java/ua/acclorite/book_story/presentation/components/translator_language/data/TranslatorLanguageState.kt +++ /dev/null @@ -1,21 +0,0 @@ -package ua.acclorite.book_story.presentation.components.translator_language.data - -import androidx.compose.runtime.Immutable -import ua.acclorite.book_story.domain.model.Language - -@Immutable -data class TranslatorLanguageState( - val selectedLanguage: String = "", - val unselectedLanguage: String = "", - val languageToSelect: String = "", - val translateFromSelecting: Boolean = false, - val showDownloadLanguageDialog: Boolean = false, - - val unfilteredLanguages: List = emptyList(), - val languages: List = emptyList(), - - val showSearch: Boolean = false, - val searchQuery: String = "", - val hasFocused: Boolean = false, - - ) diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/translator_language/data/TranslatorLanguageViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/translator_language/data/TranslatorLanguageViewModel.kt deleted file mode 100644 index 121672b5..00000000 --- a/app/src/main/java/ua/acclorite/book_story/presentation/components/translator_language/data/TranslatorLanguageViewModel.kt +++ /dev/null @@ -1,455 +0,0 @@ -package ua.acclorite.book_story.presentation.components.translator_language.data - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.google.mlkit.nl.translate.TranslateLanguage -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch -import kotlinx.coroutines.yield -import ua.acclorite.book_story.R -import ua.acclorite.book_story.domain.model.Language -import ua.acclorite.book_story.domain.model.LanguageHistory -import ua.acclorite.book_story.domain.use_case.DeleteLanguageModel -import ua.acclorite.book_story.domain.use_case.DownloadLanguageModel -import ua.acclorite.book_story.domain.use_case.GetLanguageHistory -import ua.acclorite.book_story.domain.use_case.IsLanguageModelDownloaded -import ua.acclorite.book_story.domain.use_case.UpdateLanguageHistory -import ua.acclorite.book_story.domain.util.UIText -import java.util.Locale -import javax.inject.Inject - -@HiltViewModel -class TranslatorLanguageViewModel @Inject constructor( - private val isLanguageModelDownloaded: IsLanguageModelDownloaded, - private val downloadLanguageModel: DownloadLanguageModel, - private val deleteLanguageModel: DeleteLanguageModel, - private val getLanguageHistory: GetLanguageHistory, - private val updateLanguageHistory: UpdateLanguageHistory, -) : ViewModel() { - - private val _state = MutableStateFlow(TranslatorLanguageState()) - val state = _state.asStateFlow() - - private var job: Job? = null - - @Suppress("LABEL_NAME_CLASH") - fun onEvent(event: TranslatorLanguageEvent) { - when (event) { - is TranslatorLanguageEvent.OnSelectLanguage -> { - viewModelScope.launch(Dispatchers.IO) { - if (_state.value.showDownloadLanguageDialog) { - return@launch - } - - val isDownloaded = isLanguageModelDownloaded.execute( - event.languageCode - ) - - _state.update { - it.copy( - languageToSelect = event.languageCode - ) - } - - onEvent( - TranslatorLanguageEvent.OnUpdateLanguage(event.languageCode) { - it.copy( - isDownloaded = isDownloaded - ) - } - ) - - if (isDownloaded) { - val from = _state.value.translateFromSelecting - val langToSelect = _state.value.languageToSelect - val unselectedLang = _state.value.unselectedLanguage - val selectedLang = _state.value.selectedLanguage - - val translateFrom: String - val translateTo: String - - if (langToSelect == unselectedLang) { - if (from) { - if (selectedLang == "auto") { - launch(Dispatchers.Main) { - event.onError( - UIText.StringResource(R.string.error_cannot_select_language) - ) - } - return@launch - } - - translateFrom = langToSelect - translateTo = selectedLang - } else { - translateTo = langToSelect - translateFrom = selectedLang - } - } else { - if (from) { - translateFrom = langToSelect - translateTo = unselectedLang - } else { - translateTo = langToSelect - translateFrom = unselectedLang - } - } - - if (translateTo == "auto") { - launch(Dispatchers.Main) { - event.onError( - UIText.StringResource(R.string.error_wrong_language) - ) - } - return@launch - } - - event.onSelect( - translateFrom, - translateTo - ) - return@launch - } - - _state.update { - it.copy( - showDownloadLanguageDialog = true - ) - } - } - } - - is TranslatorLanguageEvent.OnDownloadLanguage -> { - viewModelScope.launch(Dispatchers.IO) { - onEvent( - TranslatorLanguageEvent.OnUpdateLanguage(event.languageCode) { - it.copy( - isDownloading = true, - isCanceled = false - ) - } - ) - - val isDownloaded = isLanguageModelDownloaded.execute( - event.languageCode - ) - - onEvent( - TranslatorLanguageEvent.OnUpdateLanguage(event.languageCode) { - it.copy( - isDownloaded = isDownloaded - ) - } - ) - - if (isDownloaded) { - onEvent( - TranslatorLanguageEvent.OnUpdateLanguage(event.languageCode) { - it.copy( - isDownloading = false - ) - } - ) - return@launch - } - - downloadLanguageModel.execute( - languageCode = event.languageCode, - onCompleted = { - viewModelScope.launch(Dispatchers.IO) { - val modelDownloaded = isLanguageModelDownloaded.execute( - event.languageCode - ) - - onEvent( - TranslatorLanguageEvent.OnUpdateLanguage(event.languageCode) { - it.copy( - isDownloading = false, - isDownloaded = modelDownloaded - ) - } - ) - - val isCanceled = _state.value.unfilteredLanguages.find { - it.languageCode == event.languageCode - }?.isCanceled - - if (isCanceled == true && modelDownloaded) { - deleteLanguageModel.execute( - languageCode = event.languageCode - ) - onEvent( - TranslatorLanguageEvent.OnUpdateLanguage(event.languageCode) { - it.copy( - isDownloading = false, - isDownloaded = false, - isCanceled = false - ) - } - ) - return@launch - } - - event.onComplete() - } - }, - onFailure = { - event.onFailure(it) - } - ) - } - } - - is TranslatorLanguageEvent.OnCancelDownload -> { - viewModelScope.launch(Dispatchers.IO) { - onEvent( - TranslatorLanguageEvent.OnUpdateLanguage(event.languageCode) { - it.copy( - isDownloaded = false, - isDownloading = false, - isCanceled = true - ) - } - ) - } - } - - is TranslatorLanguageEvent.OnDeleteLanguage -> { - viewModelScope.launch(Dispatchers.IO) { - val deleted = deleteLanguageModel.execute(event.languageCode) - - if (deleted) { - onEvent( - TranslatorLanguageEvent.OnUpdateLanguage(event.languageCode) { - it.copy( - isDownloaded = false, - isDownloading = false - ) - } - ) - } - } - } - - is TranslatorLanguageEvent.OnSearchShowHide -> { - viewModelScope.launch(Dispatchers.IO) { - val shouldHide = _state.value.showSearch - - if (shouldHide) { - filterLanguages("") - } else { - _state.update { - it.copy( - searchQuery = "", - hasFocused = false - ) - } - } - - _state.update { - it.copy( - showSearch = !shouldHide - ) - } - } - } - - is TranslatorLanguageEvent.OnSearchQueryChange -> { - _state.update { - it.copy( - searchQuery = event.query - ) - } - job?.cancel() - job = viewModelScope.launch(Dispatchers.IO) { - delay(500) - yield() - onEvent(TranslatorLanguageEvent.OnSearch) - } - } - - is TranslatorLanguageEvent.OnSearch -> { - viewModelScope.launch(Dispatchers.IO) { - filterLanguages() - } - } - - is TranslatorLanguageEvent.OnRequestFocus -> { - if (!_state.value.hasFocused) { - event.focusRequester.requestFocus() - _state.update { - it.copy( - hasFocused = true - ) - } - } - } - - is TranslatorLanguageEvent.OnDismissDownloadDialog -> { - _state.update { - it.copy( - showDownloadLanguageDialog = false - ) - } - } - - is TranslatorLanguageEvent.OnUpdateLanguage -> { - viewModelScope.launch(Dispatchers.IO) { - val unfilteredLanguages = _state.value.unfilteredLanguages.map { - if (it.languageCode == event.languageCode) { - event.block(it) - } else { - it - } - } - - _state.update { - it.copy( - unfilteredLanguages = unfilteredLanguages - ) - } - filterLanguages() - } - } - - is TranslatorLanguageEvent.OnUpdateLanguageHistory -> { - viewModelScope.launch { - var history = getLanguageHistory - .execute() - .sortedBy { it.order } - .toMutableList() - - val langToAdd = _state.value.languageToSelect - - val firstItem = history.indexOfFirst { it.languageCode == langToAdd } - val indexOfItem = if (firstItem != -1) firstItem else null - - if (indexOfItem != null) { - history.removeAt(indexOfItem) - } - history.add(0, LanguageHistory(langToAdd, 0)) - - history = history.mapIndexed { index, item -> - item.copy( - order = index - ) - }.toMutableList() - - updateLanguageHistory.execute(history) - } - } - } - } - - fun init( - selectedLanguage: String, - unselectedLanguage: String, - translateFromSelecting: Boolean, - loaded: () -> Unit - ) { - viewModelScope.launch(Dispatchers.IO) { - val oldState = _state.value - _state.update { - TranslatorLanguageState() - } - - val history = getLanguageHistory.execute() - val unfilteredLanguages = TranslateLanguage.getAllLanguages() - .filter { - if (it == "ru") { - return@filter false - } - - true - } - .map { languageCode -> - val previousLanguage = oldState.languages.find { - it.languageCode == languageCode - } - - val locale = Locale(languageCode) - val displayLanguage = locale.getDisplayLanguage(locale).replaceFirstChar { - it.uppercase() - } - - val isDownloading = previousLanguage?.isDownloading ?: false - val isCancelled = previousLanguage?.isCanceled ?: false - val isSelected = selectedLanguage == languageCode - - val historyOrder = history.find { - it.languageCode == languageCode - }?.order - - Language( - languageCode = languageCode, - displayLanguage = displayLanguage, - isDownloading = isDownloading, - isDownloaded = false, - isCanceled = isCancelled, - isSelected = isSelected, - historyOrder = historyOrder - ) - } - - - _state.update { - it.copy( - selectedLanguage = selectedLanguage, - unselectedLanguage = unselectedLanguage, - translateFromSelecting = translateFromSelecting, - unfilteredLanguages = unfilteredLanguages - ) - } - filterLanguages() - loaded() - loadDownloadedState() - } - } - - private suspend fun loadDownloadedState() { - viewModelScope.launch(Dispatchers.IO) { - _state.value.unfilteredLanguages.map { language -> - val isDownloaded = isLanguageModelDownloaded.execute( - language.languageCode - ) - val isDownloading = if (isDownloaded) false else null - - onEvent( - TranslatorLanguageEvent.OnUpdateLanguage( - languageCode = language.languageCode, - block = { - it.copy( - isDownloaded = isDownloaded, - isDownloading = isDownloading ?: it.isDownloading - ) - } - ) - ) - } - } - } - - private fun filterLanguages( - query: String = if (_state.value.showSearch) _state.value.searchQuery else "" - ) { - _state.update { - it.copy( - languages = it.unfilteredLanguages.filter { language -> - if (query.isBlank()) { - return@filter true - } - - language.displayLanguage.lowercase().trim().contains( - query.lowercase().trim() - ) - } - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/data/ArithmeticOperations.kt b/app/src/main/java/ua/acclorite/book_story/presentation/data/ArithmeticOperations.kt index 91d8f848..71b426bd 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/data/ArithmeticOperations.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/data/ArithmeticOperations.kt @@ -8,63 +8,4 @@ fun String.removeTrailingZero(): String { .dropLastWhile { it == '.' } } -fun Double.removeDigits(digits: Int) = "%.${digits}f".format(this).replace(",", ".") - -fun Map.update(item: Pair): Map { - val keyExists = containsKey(item.first) - - return if (!keyExists) this - else this.plus(item) -} - -fun Map.update(items: List>): Map { - var map = this - - for (item in items) { - map = map.update(item) - } - - return map -} - -fun Map.update(items: Map): Map { - var map = this - - for (item in items) { - map = map.update(item.toPair()) - } - - return map -} - -fun Map.updateWithCopy( - calculation: (T) -> T -): Map { - var map = this - - for (item in this) { - map = map.update(item.key to calculation(item.value)) - } - - return map -} - -fun Map.removeKeys( - keys: Set -): Map { - val map = this.toMutableMap() - - for (key in keys) { - map.remove(key) - } - - return map.toMap() -} - -fun Map.removeKey( - key: K -): Map { - val map = this.toMutableMap() - map.remove(key) - return map.toMap() -} \ No newline at end of file +fun Double.removeDigits(digits: Int) = "%.${digits}f".format(this).replace(",", ".") \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/data/MainEvent.kt b/app/src/main/java/ua/acclorite/book_story/presentation/data/MainEvent.kt index d9243fc7..f914b000 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/data/MainEvent.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/data/MainEvent.kt @@ -17,8 +17,5 @@ sealed class MainEvent { data class OnChangeShowStartScreen(val bool: Boolean) : MainEvent() data class OnChangeCheckForUpdates(val bool: Boolean) : MainEvent() data class OnChangeSidePadding(val sidePadding: Int) : MainEvent() - data class OnChangeEnableTranslator(val bool: Boolean) : MainEvent() - data class OnChangeTranslateFrom(val languageCode: String) : MainEvent() - data class OnChangeTranslateTo(val languageCode: String) : MainEvent() data class OnChangeDoubleClickTranslation(val bool: Boolean) : MainEvent() } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/data/MainState.kt b/app/src/main/java/ua/acclorite/book_story/presentation/data/MainState.kt index 120c18c5..3b7013ac 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/data/MainState.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/data/MainState.kt @@ -5,7 +5,6 @@ import android.os.Parcelable import androidx.annotation.Keep import androidx.compose.runtime.Immutable import androidx.compose.ui.graphics.Color -import com.google.mlkit.nl.translate.TranslateLanguage import kotlinx.parcelize.Parcelize import ua.acclorite.book_story.domain.util.Constants import ua.acclorite.book_story.domain.util.DataStoreConstants @@ -44,9 +43,6 @@ data class MainState( val showStartScreen: Boolean? = null, val checkForUpdates: Boolean? = null, val sidePadding: Int? = null, - val enableTranslator: Boolean? = null, - val translateFrom: String? = null, - val translateTo: String? = null, val doubleClickTranslation: Boolean? = null ) : Parcelable { companion object { @@ -109,23 +105,9 @@ data class MainState( val sidePadding: Int = data[SIDE_PADDING.name] as? Int ?: 6 - val enableTranslator: Boolean = data[ENABLE_TRANSLATOR.name] as? Boolean - ?: false - - val translateFrom: String = data[TRANSLATE_FROM.name] as? String - ?: "auto" - - val translateTo: String = data[TRANSLATE_TO.name] as? String ?: if ( - TranslateLanguage.getAllLanguages().any { it == language } - ) { - language - } else { - "en" - } - val doubleClickTranslation: Boolean = data[DOUBLE_CLICK_TRANSLATION.name] as? Boolean - ?: true + ?: false return MainState( language = language, @@ -144,9 +126,6 @@ data class MainState( paragraphIndentation = paragraphIndentation, checkForUpdates = checkForUpdates, sidePadding = sidePadding, - enableTranslator = enableTranslator, - translateFrom = translateFrom, - translateTo = translateTo, doubleClickTranslation = doubleClickTranslation ) } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/data/MainViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/data/MainViewModel.kt index ae3cc71f..11f10914 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/data/MainViewModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/data/MainViewModel.kt @@ -228,39 +228,6 @@ class MainViewModel @Inject constructor( } } - is MainEvent.OnChangeEnableTranslator -> { - viewModelScope.launch(Dispatchers.IO) { - setDatastore.execute(DataStoreConstants.ENABLE_TRANSLATOR, event.bool) - updateStateWithSavedHandle { - it.copy( - enableTranslator = event.bool - ) - } - } - } - - is MainEvent.OnChangeTranslateFrom -> { - viewModelScope.launch(Dispatchers.IO) { - setDatastore.execute(DataStoreConstants.TRANSLATE_FROM, event.languageCode) - updateStateWithSavedHandle { - it.copy( - translateFrom = event.languageCode - ) - } - } - } - - is MainEvent.OnChangeTranslateTo -> { - viewModelScope.launch(Dispatchers.IO) { - setDatastore.execute(DataStoreConstants.TRANSLATE_TO, event.languageCode) - updateStateWithSavedHandle { - it.copy( - translateTo = event.languageCode - ) - } - } - } - is MainEvent.OnChangeDoubleClickTranslation -> { viewModelScope.launch(Dispatchers.IO) { setDatastore.execute(DataStoreConstants.DOUBLE_CLICK_TRANSLATION, event.bool) diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/confirm_update_dialog/BookInfoConfirmUpdateDialogItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/confirm_update_dialog/BookInfoConfirmUpdateDialogItem.kt deleted file mode 100644 index ad2c8d7a..00000000 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/confirm_update_dialog/BookInfoConfirmUpdateDialogItem.kt +++ /dev/null @@ -1,21 +0,0 @@ -package ua.acclorite.book_story.presentation.screens.book_info.components.confirm_update_dialog - -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp - -/** - * Confirm update dialog item. - */ -@Composable -fun BookInfoConfirmUpdateDialogItem(title: String) { - Text( - text = "• $title", - color = MaterialTheme.colorScheme.onSurfaceVariant, - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(horizontal = 24.dp, vertical = 4.dp) - ) -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoViewModel.kt index 8620b2cb..36b8a6d0 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoViewModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoViewModel.kt @@ -534,7 +534,7 @@ class BookInfoViewModel @Inject constructor( val updatedText = updatedBook.text val text = getText.execute(book.textPath) - if (updatedText != text.map { it.value.originalLine }) { + if (updatedText != text.map { it.value.line }) { textUpdated = true } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/ReaderScreen.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/ReaderScreen.kt index 3a1c1eb9..119d3084 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/ReaderScreen.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/ReaderScreen.kt @@ -40,7 +40,6 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontStyle @@ -55,7 +54,6 @@ import ua.acclorite.book_story.presentation.components.CustomAnimatedVisibility import ua.acclorite.book_story.presentation.components.CustomSelectionContainer import ua.acclorite.book_story.presentation.components.customItems import ua.acclorite.book_story.presentation.components.is_messages.IsError -import ua.acclorite.book_story.presentation.components.translator_language.TranslatorLanguageBottomSheet import ua.acclorite.book_story.presentation.data.LocalNavigator import ua.acclorite.book_story.presentation.data.MainEvent import ua.acclorite.book_story.presentation.data.MainState @@ -66,13 +64,11 @@ import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewMode import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel import ua.acclorite.book_story.presentation.screens.reader.components.ReaderEndItem +import ua.acclorite.book_story.presentation.screens.reader.components.ReaderTextParagraph import ua.acclorite.book_story.presentation.screens.reader.components.app_bar.ReaderBottomBar import ua.acclorite.book_story.presentation.screens.reader.components.app_bar.ReaderTopBar -import ua.acclorite.book_story.presentation.screens.reader.components.download_language_dialog.ReaderDownloadLanguageDialog import ua.acclorite.book_story.presentation.screens.reader.components.settings_bottom_sheet.ReaderSettingsBottomSheet import ua.acclorite.book_story.presentation.screens.reader.components.start_item.ReaderStartItem -import ua.acclorite.book_story.presentation.screens.reader.components.text.ReaderTextParagraph -import ua.acclorite.book_story.presentation.screens.reader.components.translator_bottom_sheet.ReaderTranslatorBottomSheet import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent import ua.acclorite.book_story.presentation.screens.reader.data.ReaderState import ua.acclorite.book_story.presentation.screens.reader.data.ReaderViewModel @@ -163,7 +159,6 @@ private fun ReaderScreen( onHistoryEvent: (HistoryEvent) -> Unit, ) { val context = LocalContext.current as ComponentActivity - val density = LocalDensity.current val nestedScrollConnection = remember(state) { object : NestedScrollConnection { @@ -214,45 +209,6 @@ private fun ReaderScreen( } } - if (state.value.showTranslatorBottomSheet) { - ReaderTranslatorBottomSheet( - state = state, - onEvent = onEvent - ) - } - if (state.value.showLanguageBottomSheet) { - TranslatorLanguageBottomSheet( - selectedLanguage = if (state.value.languageBottomSheetTranslateFrom) { - state.value.book.translateFrom - } else state.value.book.translateTo, - - unselectedLanguage = if (!state.value.languageBottomSheetTranslateFrom) { - state.value.book.translateFrom - } else state.value.book.translateTo, - - translateFromSelecting = state.value.languageBottomSheetTranslateFrom, - onSelect = { from, to -> - onEvent( - ReaderEvent.OnChangeTranslatorSettings( - translateFrom = from, - translateTo = to - ) - ) - onEvent( - ReaderEvent.OnShowHideLanguageBottomSheet( - show = false - ) - ) - }, - onDismiss = { - onEvent( - ReaderEvent.OnShowHideLanguageBottomSheet( - show = false - ) - ) - } - ) - } if (state.value.showSettingsBottomSheet) { ReaderSettingsBottomSheet( mainState = mainState, @@ -260,12 +216,6 @@ private fun ReaderScreen( onMainEvent = onMainEvent ) } - if (state.value.showDownloadLanguageDialog) { - ReaderDownloadLanguageDialog( - state = state, - onEvent = onEvent - ) - } Scaffold( Modifier @@ -379,11 +329,7 @@ private fun ReaderScreen( text, key = { key -> key.first } ) { line -> ReaderTextParagraph( - state = state, - id = line.first, line = line.second, - context = context, - density = density, fontFamily = fontFamily, fontColor = fontColor, lineHeight = lineHeight, @@ -391,8 +337,6 @@ private fun ReaderScreen( fontSize = mainState.value.fontSize!!.sp, sidePadding = sidePadding, paragraphIndentation = mainState.value.paragraphIndentation!!, - toolbarHidden = toolbarHidden, - onEvent = onEvent ) } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderTextParagraph.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderTextParagraph.kt new file mode 100644 index 00000000..b953fa18 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderTextParagraph.kt @@ -0,0 +1,71 @@ +package ua.acclorite.book_story.presentation.screens.reader.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyItemScope +import androidx.compose.foundation.text.BasicText +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.style.LineBreak +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.TextUnit +import ua.acclorite.book_story.domain.model.FontWithName +import ua.acclorite.book_story.domain.model.ReaderLine + +/** + * Reader Text Paragraph item. + * + * @param line Current line. + * @param fontFamily Line's font family. + * @param fontColor Line's font color. + * @param lineHeight Line's line height. + * @param fontStyle Line's font style. + * @param fontSize Line's font size. + * @param sidePadding Line's side padding. + * @param paragraphIndentation Whether Paragraph Indentation is enabled for this line. + */ +@Composable +fun LazyItemScope.ReaderTextParagraph( + line: ReaderLine, + fontFamily: FontWithName, + fontColor: Color, + lineHeight: TextUnit, + fontStyle: FontStyle, + fontSize: TextUnit, + sidePadding: Dp, + paragraphIndentation: Boolean +) { + Column( + modifier = Modifier + .animateItem(fadeInSpec = null, fadeOutSpec = null) + .fillMaxWidth() + .padding( + horizontal = sidePadding + ), + verticalArrangement = Arrangement.Center + ) { + BasicText( + text = buildAnnotatedString { + if (paragraphIndentation) { + append(" ") + } + + append(line.line) + }, + style = TextStyle( + fontFamily = fontFamily.font, + fontStyle = fontStyle, + fontSize = fontSize, + lineHeight = lineHeight, + color = fontColor, + lineBreak = LineBreak.Paragraph + ) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/app_bar/ReaderTopBar.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/app_bar/ReaderTopBar.kt index df8a8f2b..ff2d99b8 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/app_bar/ReaderTopBar.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/app_bar/ReaderTopBar.kt @@ -8,9 +8,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Settings -import androidx.compose.material.icons.filled.Translate import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar @@ -141,17 +139,6 @@ fun ReaderTopBar( } }, actions = { - CustomIconButton( - icon = Icons.Default.Translate, - contentDescription = R.string.translator_content_desc, - disableOnClick = false, - enabled = !state.value.lockMenu, - color = if (state.value.book.enableTranslator) { - MaterialTheme.colorScheme.primary - } else LocalContentColor.current - ) { - onEvent(ReaderEvent.OnShowHideTranslatorBottomSheet()) - } CustomIconButton( icon = Icons.Default.Settings, contentDescription = R.string.open_reader_settings_content_desc, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/download_language_dialog/ReaderDownloadLanguageDialog.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/download_language_dialog/ReaderDownloadLanguageDialog.kt deleted file mode 100644 index 7ee3d1f3..00000000 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/download_language_dialog/ReaderDownloadLanguageDialog.kt +++ /dev/null @@ -1,116 +0,0 @@ -package ua.acclorite.book_story.presentation.screens.reader.components.download_language_dialog - -import android.widget.Toast -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Download -import androidx.compose.runtime.Composable -import androidx.compose.runtime.State -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import ua.acclorite.book_story.R -import ua.acclorite.book_story.presentation.components.customItems -import ua.acclorite.book_story.presentation.components.custom_dialog.CustomDialogWithLazyColumn -import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent -import ua.acclorite.book_story.presentation.screens.reader.data.ReaderState - -/** - * Reader Download Language Dialog. Downloads multiple language models at the same time. - * - * @param state [ReaderState]. - * @param onEvent [ReaderEvent] callback. - */ -@Composable -fun ReaderDownloadLanguageDialog( - state: State, - onEvent: (ReaderEvent) -> Unit -) { - val context = LocalContext.current - var isOnClickEnabled by remember { mutableStateOf(true) } - val isDownloading by remember { - derivedStateOf { - state.value.languagesToTranslate.any { it.isDownloading } - } - } - val isDownloaded by remember { - derivedStateOf { - state.value.languagesToTranslate.filter { it.isSelected }.none { !it.isDownloaded } - } - } - val languages = remember(state.value.languagesToTranslate, isDownloading) { - if (isDownloading) { - state.value.languagesToTranslate.sortedByDescending { it.isDownloading } - } else { - state.value.languagesToTranslate - } - } - - CustomDialogWithLazyColumn( - title = stringResource(id = R.string.download_languages), - imageVectorIcon = Icons.Default.Download, - description = stringResource( - id = R.string.download_languages_description - ), - actionText = stringResource( - if (!isDownloaded) R.string.download - else R.string.continue_download - ), - disableOnClick = false, - isActionEnabled = isOnClickEnabled, - onDismiss = { onEvent(ReaderEvent.OnDismissDownloadLanguageDialog) }, - onAction = { - isOnClickEnabled = false - onEvent( - ReaderEvent.OnDownloadLanguages( - onSuccess = { - Toast.makeText( - context, - context.getString(R.string.languages_downloaded), - Toast.LENGTH_LONG - ).show() - - onEvent( - ReaderEvent.OnTranslateText( - it.keys, - error = { error -> - Toast.makeText( - context, - error.asString(context), - Toast.LENGTH_LONG - ).show() - } - ) - ) - }, - error = { - Toast.makeText( - context, - it.asString(context), - Toast.LENGTH_LONG - ).show() - } - ) - ) - }, - withDivider = true, - items = { - customItems( - languages, key = { it.languageCode } - ) { - ReaderDownloadLanguageDialogItem( - language = it, - isEnabled = !isDownloading, - onClick = { - onEvent( - ReaderEvent.OnSelectLanguage(it) - ) - } - ) - } - } - ) -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/download_language_dialog/ReaderDownloadLanguageDialogItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/download_language_dialog/ReaderDownloadLanguageDialogItem.kt deleted file mode 100644 index 86f4237e..00000000 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/download_language_dialog/ReaderDownloadLanguageDialogItem.kt +++ /dev/null @@ -1,177 +0,0 @@ -package ua.acclorite.book_story.presentation.screens.reader.components.download_language_dialog - -import android.widget.Toast -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyItemScope -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.DownloadDone -import androidx.compose.material.icons.filled.Error -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import ua.acclorite.book_story.R -import ua.acclorite.book_story.domain.model.SelectableLanguage -import ua.acclorite.book_story.presentation.components.CustomCheckbox -import ua.acclorite.book_story.presentation.ui.DefaultTransition - -/** - * Reader Download Language Dialog Item. - * - * @param language [SelectableLanguage] item. - * @param isEnabled Whether OnClick is enabled or not. - * @param onClick OnClick callback. - */ -@Composable -fun LazyItemScope.ReaderDownloadLanguageDialogItem( - language: SelectableLanguage, - isEnabled: Boolean = true, - onClick: () -> Unit -) { - val context = LocalContext.current - - if ( - isEnabled || language.isDownloading || language.isDownloaded || language.isError - ) { - Row( - modifier = Modifier - .animateItem( - fadeInSpec = null, - fadeOutSpec = null - ) - .fillMaxWidth() - .clickable( - enabled = !language.isDownloading && - !language.isDownloaded && - language.canUnselect && - isEnabled || language.isError - ) { - if (!language.isError) { - onClick() - } else if (language.errorMessage != null) { - Toast - .makeText( - context, - language.errorMessage.asString(context), - Toast.LENGTH_LONG - ) - .show() - } - } - .padding(vertical = 12.dp, horizontal = 24.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Column(Modifier.weight(1f)) { - Text( - text = language.displayLanguage, - color = MaterialTheme.colorScheme.onSurface, - style = MaterialTheme.typography.bodyLarge, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - if (!language.occurrences.isNullOrEmpty()) { - Text( - text = stringResource( - id = R.string.translator_occurrences, - language.occurrences.size - ), - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } - } - - - Box(contentAlignment = Alignment.CenterEnd) { - DefaultTransition( - language.isDownloading && !language.isDownloaded && !language.isError - ) { - Row { - Spacer(modifier = Modifier.width(24.dp)) - CircularProgressIndicator( - strokeWidth = 2.5.dp, - modifier = Modifier - .size(22.dp), - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - Spacer(modifier = Modifier.width(4.dp)) - } - } - - - DefaultTransition( - language.isDownloaded && !language.isDownloading && !language.isError - ) { - Row { - Spacer(modifier = Modifier.width(24.dp)) - Icon( - imageVector = Icons.Default.DownloadDone, - tint = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier - .clip(CircleShape) - .background( - MaterialTheme.colorScheme.outlineVariant, - shape = CircleShape - ) - .padding(6.dp) - .size(17.dp), - contentDescription = stringResource(id = R.string.downloaded_content_desc) - ) - } - } - - DefaultTransition( - !language.isDownloaded && !language.isDownloading && isEnabled && !language.isError - ) { - Row { - Spacer(modifier = Modifier.width(24.dp)) - CustomCheckbox( - selected = language.isSelected, - size = 20.dp, - containerColor = - if (language.canUnselect) MaterialTheme.colorScheme.surfaceContainerHigh - else MaterialTheme.colorScheme.surfaceContainerHigh - ) - } - } - - DefaultTransition( - !language.isDownloaded && !language.isDownloading && language.isError - ) { - Row { - Spacer(modifier = Modifier.width(24.dp)) - Icon( - imageVector = Icons.Default.Error, - tint = MaterialTheme.colorScheme.error, - modifier = Modifier - .size(27.dp), - contentDescription = stringResource(id = R.string.error_content_desc) - ) - Spacer(modifier = Modifier.width(1.5.dp)) - } - } - } - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/text/ReaderTextParagraph.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/text/ReaderTextParagraph.kt deleted file mode 100644 index 482a9086..00000000 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/text/ReaderTextParagraph.kt +++ /dev/null @@ -1,220 +0,0 @@ -package ua.acclorite.book_story.presentation.screens.reader.components.text - -import android.content.Context -import android.widget.Toast -import androidx.activity.ComponentActivity -import androidx.compose.animation.core.tween -import androidx.compose.animation.expandVertically -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.shrinkVertically -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.clickable -import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyItemScope -import androidx.compose.foundation.text.BasicText -import androidx.compose.foundation.text.appendInlineContent -import androidx.compose.foundation.text.selection.DisableSelection -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.State -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.font.FontStyle -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.LineBreak -import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.TextUnit -import ua.acclorite.book_story.R -import ua.acclorite.book_story.domain.model.FontWithName -import ua.acclorite.book_story.domain.model.LineWithTranslation -import ua.acclorite.book_story.domain.util.ID -import ua.acclorite.book_story.presentation.components.CustomAnimatedVisibility -import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent -import ua.acclorite.book_story.presentation.screens.reader.data.ReaderState -import java.util.Locale - -/** - * Reader Text Paragraph item. - * - * @param state [ReaderState]. - * @param id Current paragraph [ID]. - * @param line Current line. - * @param context Context. - * @param density [Density] from LocalDensity.current. - * @param fontFamily Line's font family. - * @param fontColor Line's font color. - * @param lineHeight Line's line height. - * @param fontStyle Line's font style. - * @param fontSize Line's font size. - * @param sidePadding Line's side padding. - * @param paragraphIndentation Whether Paragraph Indentation is enabled for this line. - * @param toolbarHidden Whether selection toolbar is hidden. - * @param onEvent [ReaderEvent] callback. - */ -@OptIn(ExperimentalFoundationApi::class) -@Composable -fun LazyItemScope.ReaderTextParagraph( - state: State, - id: ID, - line: LineWithTranslation, - context: Context, - density: Density, - fontFamily: FontWithName, - fontColor: Color, - lineHeight: TextUnit, - fontStyle: FontStyle, - fontSize: TextUnit, - sidePadding: Dp, - paragraphIndentation: Boolean, - toolbarHidden: Boolean, - onEvent: (ReaderEvent) -> Unit -) { - val text = remember( - line.useTranslation, line.translatedLine, line.originalLine - ) { - if (line.useTranslation && line.translatedLine != null) line.translatedLine - else line.originalLine - } - - val translated = remember(line.useTranslation, line.translatedLine) { - line.useTranslation && line.translatedLine != null - } - val showTrailingContent = remember( - line.isTranslationLoading, line.isTranslationFailed - ) { - line.isTranslationLoading || line.isTranslationFailed - } - - val translatingFrom = remember(line.translatingFrom) { - val locale = Locale(line.translatingFrom) - locale.getDisplayName(locale).replaceFirstChar { char -> - char.uppercase() - } - } - val translatingTo = remember(line.translatingTo) { - val locale = Locale(line.translatingTo) - locale.getDisplayName(locale).replaceFirstChar { char -> - char.uppercase() - } - } - - Column( - modifier = Modifier - .animateItem(fadeInSpec = null, fadeOutSpec = null) - .fillMaxWidth() - .padding( - horizontal = sidePadding - ), - verticalArrangement = Arrangement.Center - ) { - CustomAnimatedVisibility( - modifier = Modifier.animateItem( - placementSpec = tween(10), - fadeInSpec = null, - fadeOutSpec = null - ), - visible = translated, - enter = fadeIn(tween(250)) + expandVertically(tween(250)), - exit = fadeOut(tween(250)) + shrinkVertically(tween(250)), - ) { - DisableSelection { - Text( - text = stringResource( - id = R.string.translator_show_original, - translatingFrom, - translatingTo - ), - color = fontColor, - fontStyle = FontStyle.Italic, - fontWeight = FontWeight.Light, - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.clickable(interactionSource = null, indication = null) { - onEvent(ReaderEvent.OnUndoTranslation(id)) - } - ) - } - } - - BasicText( - text = buildAnnotatedString { - if (paragraphIndentation) { - append(" ") - } - - append(text) - - if (showTrailingContent) { - append(" ") - appendInlineContent("translator") - } - }, - modifier = Modifier - .then( - if ( - state.value.book.enableTranslator && - state.value.book.doubleClickTranslation && - toolbarHidden - ) { - Modifier.combinedClickable( - interactionSource = null, - indication = null, - onDoubleClick = { - if (line.isTranslationLoading) { - return@combinedClickable - } - - if (translated) { - onEvent(ReaderEvent.OnUndoTranslation(id)) - } else { - onEvent( - ReaderEvent.OnTranslateText(setOf(id)) { - Toast - .makeText( - context, - it.asString(context), - Toast.LENGTH_LONG - ) - .show() - } - ) - } - }, - onClick = { - onEvent( - ReaderEvent.OnShowHideMenu(context = context as ComponentActivity) - ) - } - ) - } else { - Modifier - } - ), - inlineContent = readerTextTrailingContent( - line = line, - color = fontColor, - textSize = fontSize, - context = context, - density = density - ), - style = TextStyle( - fontFamily = fontFamily.font, - fontStyle = fontStyle, - fontSize = fontSize, - lineHeight = lineHeight, - color = fontColor, - lineBreak = LineBreak.Paragraph - ) - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/text/ReaderTextTrailingContent.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/text/ReaderTextTrailingContent.kt deleted file mode 100644 index 55c9850e..00000000 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/text/ReaderTextTrailingContent.kt +++ /dev/null @@ -1,97 +0,0 @@ -package ua.acclorite.book_story.presentation.screens.reader.components.text - -import android.content.Context -import android.widget.Toast -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.text.InlineTextContent -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Error -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.Icon -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.Placeholder -import androidx.compose.ui.text.PlaceholderVerticalAlign -import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.TextUnit -import ua.acclorite.book_story.R -import ua.acclorite.book_story.domain.model.LineWithTranslation -import ua.acclorite.book_story.presentation.ui.DefaultTransition - -/** - * [ReaderTextParagraph]'s trailing content. - * - * @param line [ReaderTextParagraph]'s line. - * @param color Color. - * @param textSize Font size. - * @param context Context. - * @param density [Density] from LocalDensity.current. - */ -@Composable -fun readerTextTrailingContent( - line: LineWithTranslation, - color: Color, - textSize: TextUnit, - context: Context, - density: Density -): Map { - val sizeDp = remember(textSize, density) { with(density) { textSize.toDp() } } - return remember(line.isTranslationLoading, line.isTranslationFailed, sizeDp, color, textSize) { - mapOf( - Pair( - "translator", - InlineTextContent( - placeholder = Placeholder( - width = textSize, - height = textSize, - placeholderVerticalAlign = PlaceholderVerticalAlign.Center - ), - ) { - Box( - Modifier.fillMaxSize(), - contentAlignment = Alignment.CenterEnd - ) { - DefaultTransition(line.isTranslationLoading) { - CircularProgressIndicator( - strokeWidth = sizeDp / 12, - modifier = Modifier - .fillMaxSize(0.7f), - color = color - ) - } - - DefaultTransition(line.isTranslationFailed && !line.isTranslationLoading) { - Icon( - imageVector = Icons.Filled.Error, - contentDescription = stringResource(id = R.string.error_content_desc), - tint = color, - modifier = Modifier - .fillMaxSize() - .clickable( - interactionSource = null, - indication = null - ) { - line.errorMessage?.let { - Toast - .makeText( - context, - it.asString(context), - Toast.LENGTH_LONG - ) - .show() - } - } - ) - } - } - } - ) - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/translator_bottom_sheet/ReaderTranslatorBottomSheet.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/translator_bottom_sheet/ReaderTranslatorBottomSheet.kt deleted file mode 100644 index b5280e28..00000000 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/translator_bottom_sheet/ReaderTranslatorBottomSheet.kt +++ /dev/null @@ -1,318 +0,0 @@ -package ua.acclorite.book_story.presentation.screens.reader.components.translator_bottom_sheet - -import android.widget.Toast -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.LocalContentColor -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Switch -import androidx.compose.material3.SwitchDefaults -import androidx.compose.material3.Text -import androidx.compose.material3.contentColorFor -import androidx.compose.runtime.Composable -import androidx.compose.runtime.State -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.StrokeCap -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import ua.acclorite.book_story.R -import ua.acclorite.book_story.presentation.components.CustomBottomSheet -import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent -import ua.acclorite.book_story.presentation.screens.reader.data.ReaderState -import ua.acclorite.book_story.presentation.screens.settings.components.SwitchWithTitle -import java.util.Locale - -/** - * Reader Translator Bottom Sheet. You can set up preferences for an individual book here and apply/cancel translation. - * - * @param state [ReaderState]. - * @param onEvent [ReaderEvent] callback. - */ -@Composable -fun ReaderTranslatorBottomSheet( - state: State, - onEvent: (ReaderEvent) -> Unit -) { - val context = LocalContext.current - val book = remember(state.value) { state.value.book } - - val translateFrom = remember(book.translateFrom) { - if (book.translateFrom == "auto") { - return@remember context.getString(R.string.translator_auto_short) - } - - val locale = Locale(book.translateFrom) - locale.getDisplayName(locale).replaceFirstChar { char -> - char.uppercase() - } - } - val translateTo = remember(book.translateTo) { - val locale = Locale(book.translateTo) - locale.getDisplayName(locale).replaceFirstChar { char -> - char.uppercase() - } - } - - val loading = remember { mutableStateOf(false) } - val textTranslated = remember(state.value.isTranslating, state.value.text) { - loading.value = true - - val hasTranslatedText = state.value.text.any { - it.value.useTranslation && it.value.translatedLine != null - } - val loaded = state.value.text.none { - it.value.isTranslationLoading - } - - loading.value = false - - !state.value.isTranslating && hasTranslatedText && loaded - } - - CustomBottomSheet( - onDismissRequest = { - onEvent(ReaderEvent.OnShowHideTranslatorBottomSheet()) - } - ) { - LazyColumn(Modifier.fillMaxWidth()) { - item { - Row( - Modifier - .fillMaxWidth() - .padding(horizontal = 18.dp) - .clip(RoundedCornerShape(25.dp)) - .background( - if (book.enableTranslator) MaterialTheme.colorScheme.primary - else MaterialTheme.colorScheme.outline, - RoundedCornerShape(25.dp) - ) - .clickable { - if (book.enableTranslator) { - onEvent(ReaderEvent.OnCancelTranslation) - } - - onEvent( - ReaderEvent.OnChangeTranslatorSettings( - enableTranslator = !book.enableTranslator - ) - ) - } - .padding(horizontal = 15.dp, vertical = 16.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text( - text = stringResource(id = R.string.translator_reader_settings), - color = if (book.enableTranslator) MaterialTheme.colorScheme.onPrimary - else MaterialTheme.colorScheme.surface, - fontSize = 17.5.sp - ) - Switch( - checked = book.enableTranslator, - onCheckedChange = null, - colors = SwitchDefaults.colors( - checkedThumbColor = MaterialTheme.colorScheme.primary, - checkedTrackColor = MaterialTheme.colorScheme.secondaryContainer, - uncheckedBorderColor = MaterialTheme.colorScheme.surfaceContainerHighest - ) - ) - } - } - - item { - Spacer(modifier = Modifier.height(12.dp)) - } - - item { - ReaderTranslatorBottomSheetLanguageChooser( - fromLanguage = translateFrom, - toLanguage = translateTo, - onFromClick = { - onEvent( - ReaderEvent.OnShowHideLanguageBottomSheet( - show = true, - translateFrom = true - ) - ) - }, - onToClick = { - onEvent( - ReaderEvent.OnShowHideLanguageBottomSheet( - show = true, - translateFrom = false - ) - ) - }, - enabled = !state.value.isTranslating, - switchEnabled = book.translateFrom != "auto", - onSwitchClick = { - onEvent( - ReaderEvent.OnChangeTranslatorSettings( - translateFrom = book.translateTo, - translateTo = book.translateFrom - ) - ) - } - ) - } - - item { - Spacer(modifier = Modifier.height(6.dp)) - } - - item { - SwitchWithTitle( - selected = book.doubleClickTranslation, - title = stringResource(id = R.string.translator_double_click_to_translate_option), - description = stringResource(id = R.string.translator_double_click_to_translate_option_desc), - verticalPadding = 6.dp, - onClick = { - onEvent( - ReaderEvent.OnChangeTranslatorSettings( - doubleClickTranslation = !book.doubleClickTranslation - ) - ) - } - ) - } - - item { - SwitchWithTitle( - selected = book.translateWhenOpen, - title = stringResource(id = R.string.translator_translate_on_start_option), - description = stringResource(id = R.string.translator_translate_on_start_option_desc), - verticalPadding = 6.dp, - onClick = { - onEvent( - ReaderEvent.OnChangeTranslatorSettings( - translateWhenOpen = !book.translateWhenOpen - ) - ) - } - ) - } - - item { - Spacer(modifier = Modifier.height(48.dp)) - } - - item { - Box( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 18.dp), - contentAlignment = Alignment.CenterEnd - ) { - Button( - modifier = Modifier.fillMaxWidth(1f), - onClick = { - if (loading.value) { - return@Button - } - - if (!state.value.isTranslating) { - if (!textTranslated) { - Toast.makeText( - context, - context.getString(R.string.translator_starting), - Toast.LENGTH_SHORT - ).show() - onEvent( - ReaderEvent.OnTranslateText( - keys = null, /* means translating the whole text */ - error = { - Toast.makeText( - context, - it.asString(context), - Toast.LENGTH_LONG - ).show() - } - ) - ) - } else { - Toast.makeText( - context, - context.getString(R.string.translator_text_canceling), - Toast.LENGTH_SHORT - ).show() - onEvent( - ReaderEvent.OnUndoTranslation( - null /* means canceling the whole text translation */ - ) - ) - } - } else { - Toast.makeText( - context, - context.getString(R.string.translator_canceling), - Toast.LENGTH_SHORT - ).show() - - loading.value = true - onEvent( - ReaderEvent.OnCancelTranslation - ) - } - }, - colors = ButtonDefaults.buttonColors( - containerColor = if (state.value.isTranslating || textTranslated) { - MaterialTheme.colorScheme.secondary - } else { - MaterialTheme.colorScheme.primary - }, - contentColor = contentColorFor( - if (state.value.isTranslating || textTranslated) { - MaterialTheme.colorScheme.secondary - } else { - MaterialTheme.colorScheme.primary - } - ) - ) - ) { - if (!state.value.isTranslating && !loading.value) { - Text( - text = if (textTranslated) stringResource( - id = R.string.undo_translation - ) else stringResource(id = R.string.translate) - ) - } else { - CircularProgressIndicator( - color = LocalContentColor.current, - strokeCap = StrokeCap.Round, - modifier = Modifier.size(21.dp), - strokeWidth = 2.3.dp - ) - } - } - } - } - - item { - Spacer( - modifier = Modifier.height( - 8.dp + it - ) - ) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/translator_bottom_sheet/ReaderTranslatorBottomSheetLanguageChooser.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/translator_bottom_sheet/ReaderTranslatorBottomSheetLanguageChooser.kt deleted file mode 100644 index 192b2ac4..00000000 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/translator_bottom_sheet/ReaderTranslatorBottomSheetLanguageChooser.kt +++ /dev/null @@ -1,191 +0,0 @@ -package ua.acclorite.book_story.presentation.screens.reader.components.translator_bottom_sheet - -import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.scaleOut -import androidx.compose.animation.slideInHorizontally -import androidx.compose.animation.slideOutHorizontally -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.CompareArrows -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.yield -import ua.acclorite.book_story.R -import ua.acclorite.book_story.presentation.components.CustomAnimatedVisibility -import ua.acclorite.book_story.presentation.components.CustomIconButton - -/** - * Reader Translator BottomSheet Language Chooser. - * - * @param fromLanguage Current translate from language. - * @param toLanguage Current translate to language. - * @param enabled Whether it is enabled. - * @param switchEnabled Whether switch languages button is enabled. - * @param horizontalPadding Horizontal item padding. - * @param onFromClick OnFromClick callback. - * @param onToClick OnToClick callback. - * @param onSwitchClick OnSwitchClick callback. - */ -@Composable -fun ReaderTranslatorBottomSheetLanguageChooser( - fromLanguage: String, - toLanguage: String, - enabled: Boolean = true, - switchEnabled: Boolean = true, - horizontalPadding: Dp = 18.dp, - onFromClick: () -> Unit, - onToClick: () -> Unit, - onSwitchClick: () -> Unit -) { - val switchLanguage = remember { mutableStateOf(false) } - val scope = rememberCoroutineScope() - var job: Job? = null - - Row( - Modifier - .fillMaxWidth() - .padding(horizontal = horizontalPadding), - verticalAlignment = Alignment.CenterVertically - ) { - Box(modifier = Modifier.weight(1f)) { - CustomLanguageSwitchingAnimation( - visible = switchLanguage.value, - rightPlacement = false - ) { - LanguageItem(text = fromLanguage, enabled = enabled) { - onFromClick() - } - } - - CustomLanguageSwitchingAnimation( - visible = !switchLanguage.value, - rightPlacement = false - ) { - LanguageItem(text = fromLanguage, enabled = enabled) { - onFromClick() - } - } - } - - - - Spacer(modifier = Modifier.width(4.dp)) - CustomIconButton( - icon = Icons.AutoMirrored.Filled.CompareArrows, - contentDescription = R.string.translator_language_content_desc, - disableOnClick = false, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.size(24.dp), - enabled = enabled && switchEnabled, - onClick = { - job?.cancel() - job = scope.launch { - switchLanguage.value = !switchLanguage.value - - delay(100) - yield() - - onSwitchClick() - } - } - ) - Spacer(modifier = Modifier.width(4.dp)) - - Box(modifier = Modifier.weight(1f)) { - CustomLanguageSwitchingAnimation( - visible = switchLanguage.value, - rightPlacement = true - ) { - LanguageItem(text = toLanguage, enabled = enabled) { - onToClick() - } - } - - CustomLanguageSwitchingAnimation( - visible = !switchLanguage.value, - rightPlacement = true - ) { - LanguageItem(text = toLanguage, enabled = enabled) { - onToClick() - } - } - } - } -} - -@Composable -private fun CustomLanguageSwitchingAnimation( - visible: Boolean, - rightPlacement: Boolean, - content: @Composable () -> Unit -) { - val exitRightAnimation = remember { - fadeOut(tween(200)) + - slideOutHorizontally(tween(250)) { -it / 2 } + - scaleOut(tween(250), 0.4f) - } - val enterRightAnimation = remember { - slideInHorizontally(tween(350)) { -it / 2 } + fadeIn(tween(200)) - } - - val exitLeftAnimation = remember { - fadeOut(tween(200)) + - slideOutHorizontally(tween(250)) { it / 2 } + - scaleOut(tween(250), 0.4f) - } - val enterLeftAnimation = remember { - slideInHorizontally(tween(350)) { it / 2 } + fadeIn(tween(200)) - } - - CustomAnimatedVisibility( - visible = visible, - enter = if (rightPlacement) enterRightAnimation else enterLeftAnimation, - exit = if (rightPlacement) exitRightAnimation else exitLeftAnimation - ) { - content() - } -} - -@Composable -private fun LanguageItem(text: String, enabled: Boolean, onClick: () -> Unit) { - Box( - modifier = Modifier - .fillMaxWidth() - .clip(MaterialTheme.shapes.large) - .background(MaterialTheme.colorScheme.surfaceContainerHigh, MaterialTheme.shapes.large) - .clickable(enabled = enabled) { - onClick() - } - .padding(horizontal = 4.dp, vertical = 14.dp), - contentAlignment = Alignment.Center - ) { - Text( - text = text, - color = MaterialTheme.colorScheme.onSurface, - style = MaterialTheme.typography.bodyMedium, - maxLines = 1 - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderEvent.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderEvent.kt index e57586b7..9134a288 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderEvent.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderEvent.kt @@ -4,10 +4,6 @@ import androidx.activity.ComponentActivity import androidx.compose.foundation.pager.PagerState import androidx.compose.runtime.Immutable import ua.acclorite.book_story.domain.model.Book -import ua.acclorite.book_story.domain.model.LineWithTranslation -import ua.acclorite.book_story.domain.model.SelectableLanguage -import ua.acclorite.book_story.domain.util.ID -import ua.acclorite.book_story.domain.util.LanguageCode import ua.acclorite.book_story.domain.util.OnNavigate import ua.acclorite.book_story.domain.util.UIText @@ -25,44 +21,6 @@ sealed class ReaderEvent { val context: ComponentActivity ) : ReaderEvent() - data class OnShowHideTranslatorBottomSheet(val show: Boolean? = null) : ReaderEvent() - - data class OnChangeTranslatorSettings( - val enableTranslator: Boolean? = null, - val translateFrom: String? = null, - val translateTo: String? = null, - val doubleClickTranslation: Boolean? = null, - val translateWhenOpen: Boolean? = null - ) : ReaderEvent() - - data class OnShowHideLanguageBottomSheet( - val show: Boolean? = null, - val translateFrom: Boolean? = null - ) : ReaderEvent() - - data class OnTranslateText( - val keys: Set?, - val error: suspend (UIText) -> Unit - ) : ReaderEvent() - - data object OnDismissDownloadLanguageDialog : ReaderEvent() - - data object OnCancelTranslation : ReaderEvent() - - data class OnUndoTranslation(val id: ID?) : ReaderEvent() - - data class OnDownloadLanguages( - val onSuccess: suspend (Map) -> Unit, - val error: suspend (UIText) -> Unit - ) : ReaderEvent() - - data class OnSelectLanguage(val language: SelectableLanguage) : ReaderEvent() - - data class OnUpdateLanguage( - val languageCode: LanguageCode, - val calculation: (SelectableLanguage) -> SelectableLanguage - ) : ReaderEvent() - data class OnGoBack( val context: ComponentActivity, val navigate: () -> Unit, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderState.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderState.kt index 75442a85..d290483f 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderState.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderState.kt @@ -3,8 +3,7 @@ package ua.acclorite.book_story.presentation.screens.reader.data import androidx.compose.foundation.lazy.LazyListState import androidx.compose.runtime.Immutable import ua.acclorite.book_story.domain.model.Book -import ua.acclorite.book_story.domain.model.LineWithTranslation -import ua.acclorite.book_story.domain.model.SelectableLanguage +import ua.acclorite.book_story.domain.model.ReaderLine import ua.acclorite.book_story.domain.util.Constants import ua.acclorite.book_story.domain.util.ID import ua.acclorite.book_story.domain.util.UIText @@ -12,25 +11,17 @@ import ua.acclorite.book_story.domain.util.UIText @Immutable data class ReaderState( val book: Book = Constants.EMPTY_BOOK, - val text: Map = emptyMap(), + val text: Map = emptyMap(), val words: Int = 0, val letters: Int = 0, val listState: LazyListState = LazyListState(), - val languagesToTranslate: List = emptyList(), - val showDownloadLanguageDialog: Boolean = false, - val isTranslating: Boolean = false, - val errorMessage: UIText? = null, val loading: Boolean = true, val showMenu: Boolean = false, val lockMenu: Boolean = false, - val showTranslatorBottomSheet: Boolean = false, - val showLanguageBottomSheet: Boolean = false, - val languageBottomSheetTranslateFrom: Boolean = false, - val showSettingsBottomSheet: Boolean = false, val currentPage: Int = 0, ) \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderViewModel.kt index 5ac93746..99645f84 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderViewModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderViewModel.kt @@ -13,10 +13,7 @@ import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -25,34 +22,18 @@ import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import kotlinx.coroutines.yield import ua.acclorite.book_story.R import ua.acclorite.book_story.domain.model.Book import ua.acclorite.book_story.domain.model.Category -import ua.acclorite.book_story.domain.model.LineWithTranslation -import ua.acclorite.book_story.domain.model.SelectableLanguage -import ua.acclorite.book_story.domain.use_case.DeleteLanguageModel -import ua.acclorite.book_story.domain.use_case.DownloadLanguageModel import ua.acclorite.book_story.domain.use_case.GetBookById import ua.acclorite.book_story.domain.use_case.GetLatestHistory import ua.acclorite.book_story.domain.use_case.GetText -import ua.acclorite.book_story.domain.use_case.IdentifyLanguage -import ua.acclorite.book_story.domain.use_case.IsLanguageModelDownloaded -import ua.acclorite.book_story.domain.use_case.TranslateText import ua.acclorite.book_story.domain.use_case.UpdateBooks -import ua.acclorite.book_story.domain.util.ID -import ua.acclorite.book_story.domain.util.LanguageCode import ua.acclorite.book_story.domain.util.OnNavigate -import ua.acclorite.book_story.domain.util.Resource import ua.acclorite.book_story.domain.util.UIText import ua.acclorite.book_story.presentation.data.Screen -import ua.acclorite.book_story.presentation.data.removeKey -import ua.acclorite.book_story.presentation.data.removeKeys -import ua.acclorite.book_story.presentation.data.update -import ua.acclorite.book_story.presentation.data.updateWithCopy import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent -import java.util.Locale import javax.inject.Inject import kotlin.math.roundToInt @@ -62,12 +43,7 @@ class ReaderViewModel @Inject constructor( private val updateBooks: UpdateBooks, private val getText: GetText, private val getLatestHistory: GetLatestHistory, - private val getBookById: GetBookById, - private val identifyLanguage: IdentifyLanguage, - private val isLanguageModelDownloaded: IsLanguageModelDownloaded, - private val downloadLanguageModel: DownloadLanguageModel, - private val deleteLanguageModel: DeleteLanguageModel, - private val translateText: TranslateText, + private val getBookById: GetBookById ) : ViewModel() { private val _state = MutableStateFlow(ReaderState()) @@ -75,12 +51,6 @@ class ReaderViewModel @Inject constructor( private var eventJob = SupervisorJob() - private var dismissTranslatorJob: Job? = null - private var cancelTranslationJob: Job? = null - private var translatorJob: Job? = null - private var undoTranslationJob: Job? = null - private var downloadLanguageJob: Job? = null - fun onEvent(event: ReaderEvent) { viewModelScope.launch(eventJob + Dispatchers.Main) { when (event) { @@ -102,7 +72,7 @@ class ReaderViewModel @Inject constructor( } val textAsLine = text - .map { it.value.originalLine } + .map { it.value.line } .joinToString( separator = "\n" ) @@ -181,17 +151,6 @@ class ReaderViewModel @Inject constructor( ) } - if ( - _state.value.book.translateWhenOpen && - _state.value.book.enableTranslator - ) { - onEvent( - ReaderEvent.OnTranslateText(null) { - event.onError(it) - } - ) - } - return@collectLatest } } @@ -229,653 +188,6 @@ class ReaderViewModel @Inject constructor( } } - is ReaderEvent.OnShowHideTranslatorBottomSheet -> { - _state.update { - it.copy( - showTranslatorBottomSheet = event.show - ?: !it.showTranslatorBottomSheet - ) - } - } - - is ReaderEvent.OnChangeTranslatorSettings -> { - launch(Dispatchers.IO) { - val book = _state.value.book - val updatedBook = book.copy( - enableTranslator = event.enableTranslator ?: book.enableTranslator, - translateFrom = event.translateFrom ?: book.translateFrom, - translateTo = event.translateTo ?: book.translateTo, - doubleClickTranslation = event.doubleClickTranslation - ?: book.doubleClickTranslation, - translateWhenOpen = event.translateWhenOpen - ?: book.translateWhenOpen - ) - - _state.update { - it.copy( - book = updatedBook - ) - } - updateBooks.execute(listOf(updatedBook)) - } - } - - is ReaderEvent.OnShowHideLanguageBottomSheet -> { - _state.update { - it.copy( - showLanguageBottomSheet = event.show ?: !it.showLanguageBottomSheet, - languageBottomSheetTranslateFrom = event.translateFrom ?: false - ) - } - } - - is ReaderEvent.OnTranslateText -> { - _state.update { - it.copy( - isTranslating = true - ) - } - - launch { - joinJobs() - - translatorJob = launch(Dispatchers.IO) { - onEvent( - ReaderEvent.OnChangeTranslatorSettings( - enableTranslator = true - ) - ) - - val keys = event.keys ?: _state.value.text.keys - var linesToTranslate = if (keys.size >= _state.value.text.size) { - _state.value.text - } else { - _state.value.text.filterKeys { keys.any { key -> key == it } } - } - - val languages = mutableMapOf?>() - - yield() - linesToTranslate = linesToTranslate.updateWithCopy { - it.copy( - isTranslationLoading = true, - isTranslationFailed = false, - errorMessage = null, - useTranslation = false - ) - } - - yield() - onUpdateTextLines( - linesToTranslate, - wholeText = linesToTranslate.size >= _state.value.text.size - ) - - yield() - if (_state.value.book.translateFrom == "auto") { - val identifiedLanguages = - mutableListOf>() - val errors = mutableMapOf() - - for (line in linesToTranslate.toMap()) { - yield() - val result = - identifyLanguage.execute(line.value.originalLine) - - when (result) { - is Resource.Success -> { - identifiedLanguages.add( - result.data!! to line.key - ) - } - - is Resource.Error -> { - errors[line.key] = line.value.copy( - isTranslationFailed = true, - isTranslationLoading = false, - errorMessage = result.message - ) - } - } - } - - yield() - if (errors.isNotEmpty()) { - onUpdateTextLines( - errors, - errors.size >= _state.value.text.size - ) - linesToTranslate = linesToTranslate.removeKeys(errors.keys) - } - - yield() - languages.putAll( - identifiedLanguages - .groupBy { - it.first - }.mapValues { (_, value) -> - value.map { it.second } - } - ) - } else { - languages[_state.value.book.translateFrom] = emptyList() - } - - yield() - if (languages.isEmpty()) { - _state.update { - it.copy( - isTranslating = false - ) - } - - yield() - launch(Dispatchers.Main) { - event.error(UIText.StringResource(R.string.error_languages_not_identified)) - } - return@launch - } - - var shouldShowDialog = false - - if (languages.any { !isLanguageModelDownloaded.execute(it.key) }) { - shouldShowDialog = true - } - - if (!isLanguageModelDownloaded.execute(_state.value.book.translateTo)) { - shouldShowDialog = true - } - - yield() - if (shouldShowDialog) { - val languagesToDownload = mutableListOf() - - val translateToLocale = Locale(_state.value.book.translateTo) - val translateToDisplayLanguage = translateToLocale - .getDisplayLanguage(translateToLocale) - .replaceFirstChar { - it.uppercase() - } - - val translateToCanceled = - _state.value.languagesToTranslate.find { - it.languageCode == _state.value.book.translateTo - }?.isCanceled ?: false - - languagesToDownload.add( - SelectableLanguage( - languageCode = _state.value.book.translateTo, - displayLanguage = translateToDisplayLanguage, - isDownloading = false, - isCanceled = translateToCanceled, - isError = false, - isDownloaded = isLanguageModelDownloaded.execute(_state.value.book.translateTo), - isSelected = true, - canUnselect = false, - occurrences = languages[_state.value.book.translateTo] - ) - ) - languagesToDownload.addAll( - languages - .filterNot { it.key == _state.value.book.translateTo } - .toList() - .sortedByDescending { it.second?.size } - .mapIndexed { index, language -> - val locale = Locale(language.first) - val displayLanguage = locale - .getDisplayLanguage(locale) - .replaceFirstChar { - it.uppercase() - } - - val isCanceled = - _state.value.languagesToTranslate.find { - it.languageCode == language.first - }?.isCanceled ?: false - - SelectableLanguage( - languageCode = language.first, - displayLanguage = displayLanguage, - isDownloading = false, - isCanceled = isCanceled, - isDownloaded = isLanguageModelDownloaded.execute( - language.first - ), - isError = false, - isSelected = index == 0, - canUnselect = index > 0, - occurrences = language.second - ) - } - ) - - yield() - _state.update { - it.copy( - languagesToTranslate = languagesToDownload, - showDownloadLanguageDialog = true - ) - } - return@launch - } - - yield() - for (i in 0.. result.data!! - is Resource.Error -> continue - } - } else { - _state.value.book.translateFrom - } - val targetLanguage = _state.value.book.translateTo - - yield() - if ( - line.translatingFrom == sourceLanguage && - line.translatingTo == targetLanguage && - line.translatedLine != null - ) { - val translatedLine = line.copy( - useTranslation = true, - isTranslationFailed = false, - isTranslationLoading = false, - errorMessage = null - ) - - yield() - _state.update { - it.copy( - text = it.text.update( - closestLine to translatedLine - ) - ) - } - linesToTranslate = linesToTranslate.removeKey(closestLine) - continue - } - - var translation: String? = null - var error: UIText? = null - - yield() - val result = translateText.execute( - sourceLanguage = sourceLanguage, - targetLanguage = targetLanguage, - text = line.originalLine - ) - - when (result) { - is Resource.Success -> { - translation = result.data - } - - is Resource.Error -> { - error = result.message - } - } - - val translatedLine = line.copy( - useTranslation = translation != null, - translatedLine = translation, - translatingFrom = sourceLanguage, - translatingTo = targetLanguage, - isTranslationFailed = translation == null, - isTranslationLoading = false, - errorMessage = error - ) - - yield() - _state.update { - it.copy( - text = it.text.update( - closestLine to translatedLine - ) - ) - } - linesToTranslate = linesToTranslate.removeKey(closestLine) - } - - yield() - _state.update { - it.copy( - isTranslating = false - ) - } - } - } - } - - is ReaderEvent.OnDismissDownloadLanguageDialog -> { - _state.update { - it.copy( - showDownloadLanguageDialog = false, - isTranslating = false - ) - } - - launch { - joinJobs() - - dismissTranslatorJob = launch(Dispatchers.IO) { - val languagesToDownload = _state.value.languagesToTranslate - - yield() - _state.update { - it.copy( - languagesToTranslate = it.languagesToTranslate.map { lang -> - lang.copy( - isCanceled = true, - isDownloading = false, - isError = false - ) - }, - showDownloadLanguageDialog = false - ) - } - - yield() - - downloadLanguageJob?.cancel() - val lines = getLinesFromLanguages(languagesToDownload) - onUpdateTextLines( - lines.updateWithCopy { - it.copy( - isTranslationLoading = false, - useTranslation = false - ) - }, - wholeText = lines.size >= _state.value.text.size - ) - } - } - } - - is ReaderEvent.OnCancelTranslation -> { - launch { - cancelTranslationJob?.join() - undoTranslationJob?.join() - dismissTranslatorJob?.cancel() - translatorJob?.cancel() - - cancelTranslationJob = launch(Dispatchers.IO) { - _state.update { - it.copy( - isTranslating = false, - showDownloadLanguageDialog = false - ) - } - - yield() - onUpdateTextLines( - _state.value.text.updateWithCopy { - if (it.useTranslation) { - return@updateWithCopy it - } - - it.copy( - isTranslationLoading = false, - useTranslation = false - ) - }, - wholeText = true - ) - } - } - } - - is ReaderEvent.OnUndoTranslation -> { - launch { - cancelTranslationJob?.join() - undoTranslationJob?.join() - - cancelTranslationJob = launch(Dispatchers.IO) { - yield() - - if (event.id != null) { - _state.update { - val line = it.text[event.id]?.copy( - useTranslation = false, - isTranslationLoading = false - ) - - if (line == null) { - return@update it - } - - it.copy( - text = it.text.update(event.id to line) - ) - } - } else { - _state.update { - it.copy( - text = it.text.updateWithCopy { line -> - if (!line.useTranslation) { - return@updateWithCopy line - } - - line.copy( - isTranslationLoading = false, - useTranslation = false - ) - } - ) - } - } - } - } - } - - is ReaderEvent.OnDownloadLanguages -> { - launch { - downloadLanguageJob?.cancel() - - downloadLanguageJob = launch(Dispatchers.IO) { - _state.update { - it.copy( - languagesToTranslate = it.languagesToTranslate.map { lang -> - val isDownloaded = isLanguageModelDownloaded.execute( - lang.languageCode - ) - - lang.copy( - isDownloading = !isDownloaded && lang.isSelected, - isCanceled = false, - isDownloaded = isDownloaded - ) - } - ) - } - - val shouldDownload = _state.value.languagesToTranslate.filter { - it.isSelected - }.any { - it.isDownloading - } - - if (!shouldDownload) { - _state.update { - it.copy( - languagesToTranslate = it.languagesToTranslate.filter { lang -> - lang.isDownloaded - }, - showDownloadLanguageDialog = false - ) - } - - onUnselectLines() - launch(Dispatchers.Main) { - event.onSuccess(getLinesFromLanguages(_state.value.languagesToTranslate)) - } - return@launch - } - - val languagesToDownload = _state.value.languagesToTranslate.filter { - it.isSelected - } - val jobs = languagesToDownload.map { lang -> - async { - downloadLanguageModel.execute( - languageCode = lang.languageCode, - onCompleted = { - launch(Dispatchers.IO) { - yield() - - val modelDownloaded = - isLanguageModelDownloaded.execute(lang.languageCode) - if (!modelDownloaded) { - onEvent( - ReaderEvent.OnUpdateLanguage(lang.languageCode) { - it.copy( - isError = true, - isDownloading = false, - isDownloaded = false, - errorMessage = UIText.StringResource( - R.string.error_could_not_download_language - ) - ) - } - ) - return@launch - } - - onEvent( - ReaderEvent.OnUpdateLanguage(lang.languageCode) { - it.copy( - isDownloading = false, - isDownloaded = true - ) - } - ) - - val isCanceled = - _state.value.languagesToTranslate.find { - it.languageCode == lang.languageCode - }?.isCanceled ?: true - - if (isCanceled) { - deleteLanguageModel.execute( - languageCode = lang.languageCode - ) - onEvent( - ReaderEvent.OnUpdateLanguage(lang.languageCode) { - it.copy( - isDownloading = false, - isDownloaded = false, - isCanceled = false - ) - } - ) - return@launch - } - } - }, - onFailure = { error -> - onEvent( - ReaderEvent.OnUpdateLanguage(lang.languageCode) { - it.copy( - isError = true, - isDownloading = false, - isDownloaded = false, - errorMessage = UIText.StringResource( - R.string.error_query, - error.message ?: "" - ) - ) - } - ) - } - ) - } - } - jobs.awaitAll() - - yield() - - _state.update { - it.copy( - languagesToTranslate = it.languagesToTranslate.filter { lang -> - isLanguageModelDownloaded.execute(lang.languageCode) - }.map { lang -> lang.copy(isDownloaded = true) }, - showDownloadLanguageDialog = false - ) - } - - if (languagesToDownload.isEmpty()) { - yield() - launch(Dispatchers.Main) { - event.error( - UIText.StringResource( - R.string.error_could_not_download_languages - ) - ) - } - onEvent(ReaderEvent.OnDismissDownloadLanguageDialog) - return@launch - } - - yield() - - onUnselectLines() - launch(Dispatchers.Main) { - event.onSuccess(getLinesFromLanguages(_state.value.languagesToTranslate)) - } - } - } - } - - is ReaderEvent.OnSelectLanguage -> { - launch(Dispatchers.IO) { - val editedList = _state.value.languagesToTranslate.map { - if (it.languageCode == event.language.languageCode) { - it.copy( - isSelected = !it.isSelected - ) - } else { - it - } - } - - _state.update { - it.copy( - languagesToTranslate = editedList - ) - } - } - } - - is ReaderEvent.OnUpdateLanguage -> { - launch(Dispatchers.IO) { - val editedList = _state.value.languagesToTranslate.map { - if (it.languageCode == event.languageCode) { - event.calculation(it) - } else { - it - } - } - - _state.update { - it.copy( - languagesToTranslate = editedList - ) - } - } - } - is ReaderEvent.OnGoBack -> { launch { _state.update { @@ -1061,174 +373,6 @@ class ReaderViewModel @Inject constructor( } } - private suspend fun joinJobs() { - cancelTranslationJob?.join() - translatorJob?.join() - dismissTranslatorJob?.join() - undoTranslationJob?.join() - } - - private suspend fun findClosestLineToTranslate( - translatingLines: Map - ): ID? { - val lines = translatingLines.toMutableMap() - val visibleItems = _state.value.listState.layoutInfo.visibleItemsInfo - val listStateIndex = (visibleItems.getOrNull(visibleItems.size / 2)?.index ?: 0) - 1 - .coerceAtLeast(0) - - var closestLineIndex: Int? = null - var searchUpperLine = false - var currentLineIndex = listStateIndex - val invalidIndices = mutableListOf() - - while (true) { - yield() - - if (lines.isEmpty()) { - break - } - - currentLineIndex = currentLineIndex.coerceIn(0..lines.maxOf { it.key }) - - if (lines[currentLineIndex] == null) { - invalidIndices.add(currentLineIndex) - - if (searchUpperLine) { - val invalid = invalidIndices.filterNot { - it >= currentLineIndex - } - var minus = 1 - - while (true) { - if (invalid.any { currentLineIndex - minus == it }) { - minus++ - continue - } - break - } - - currentLineIndex -= minus - } else { - val invalid = invalidIndices.filterNot { - it <= currentLineIndex - } - var plus = 1 - - while (true) { - if (invalid.any { currentLineIndex + plus == it }) { - plus++ - continue - } - break - } - - currentLineIndex += plus - } - searchUpperLine = !searchUpperLine - continue - } - - if ( - lines[currentLineIndex]?.useTranslation == true || - lines[currentLineIndex]?.isTranslationFailed == true - ) { - invalidIndices.add(currentLineIndex) - lines.remove(currentLineIndex) - - if (searchUpperLine) { - val invalid = invalidIndices.filterNot { - it >= currentLineIndex - } - var minus = 1 - - while (true) { - if (invalid.any { currentLineIndex - minus == it }) { - minus++ - continue - } - break - } - - currentLineIndex -= minus - } else { - val invalid = invalidIndices.filterNot { - it <= currentLineIndex - } - var plus = 1 - - while (true) { - if (invalid.any { currentLineIndex + plus == it }) { - plus++ - continue - } - break - } - - currentLineIndex += plus - } - searchUpperLine = !searchUpperLine - continue - } - - closestLineIndex = currentLineIndex - break - } - - yield() - return closestLineIndex - } - - private suspend fun onUpdateTextLines( - newLines: Map, - wholeText: Boolean - ) { - val newText = if (wholeText) newLines - else _state.value.text.update(newLines) - - yield() - _state.update { - it.copy( - text = newText - ) - } - } - - private suspend fun onUnselectLines() { - onUpdateTextLines( - _state.value.text.updateWithCopy { - it.copy(isTranslationLoading = false) - }, - wholeText = true - ) - } - - private suspend fun getLinesFromLanguages( - languages: List - ): Map { - val lines = mutableMapOf() - - yield() - for (language in languages) { - yield() - - if (language.occurrences == null) { - continue - } - - if (language.occurrences.isEmpty()) { - lines += _state.value.text - break - } - - lines += language.occurrences.associateWith { - _state.value.text[it]!! - } - } - yield() - - return lines - } - fun init( screen: Screen.Reader, onNavigate: OnNavigate, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/data/SettingsEvent.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/data/SettingsEvent.kt index a32c8e28..c9bfa87f 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/data/SettingsEvent.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/data/SettingsEvent.kt @@ -15,9 +15,4 @@ sealed class SettingsEvent { val notificationsPermissionState: PermissionState, val onChangeCheckForUpdates: (Boolean) -> Unit ) : SettingsEvent() - - data class OnReaderShowHideTranslatorLanguageBottomSheet( - val show: Boolean? = null, - val translateFrom: Boolean? = null - ) : SettingsEvent() } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/data/SettingsState.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/data/SettingsState.kt deleted file mode 100644 index 91c6fc42..00000000 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/data/SettingsState.kt +++ /dev/null @@ -1,9 +0,0 @@ -package ua.acclorite.book_story.presentation.screens.settings.data - -import androidx.compose.runtime.Immutable - -@Immutable -data class SettingsState( - val showReaderTranslatorLanguageBottomSheet: Boolean = false, - val readerTranslatorLanguageBottomSheetTranslateFrom: Boolean = false, -) diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/data/SettingsViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/data/SettingsViewModel.kt index 7c44c99c..58e802dd 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/data/SettingsViewModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/data/SettingsViewModel.kt @@ -11,9 +11,6 @@ import com.google.accompanist.permissions.shouldShowRationale import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.yield import javax.inject.Inject @@ -24,9 +21,6 @@ class SettingsViewModel @Inject constructor( ) : ViewModel() { - private val _state = MutableStateFlow(SettingsState()) - val state = _state.asStateFlow() - private var notificationsPermissionJob: Job? = null fun onEvent(event: SettingsEvent) { @@ -77,17 +71,6 @@ class SettingsViewModel @Inject constructor( } } } - - is SettingsEvent.OnReaderShowHideTranslatorLanguageBottomSheet -> { - _state.update { - it.copy( - showReaderTranslatorLanguageBottomSheet = event.show - ?: !it.showReaderTranslatorLanguageBottomSheet, - readerTranslatorLanguageBottomSheetTranslateFrom = event.translateFrom - ?: false - ) - } - } } } } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/reader/ReaderSettings.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/reader/ReaderSettings.kt index 28a1e068..9faadb65 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/reader/ReaderSettings.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/reader/ReaderSettings.kt @@ -20,7 +20,6 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.unit.dp @@ -32,35 +31,25 @@ import ua.acclorite.book_story.domain.util.OnNavigate import ua.acclorite.book_story.presentation.components.CategoryTitle import ua.acclorite.book_story.presentation.components.GoBackButton import ua.acclorite.book_story.presentation.components.collapsibleUntilExitScrollBehaviorWithLazyListState -import ua.acclorite.book_story.presentation.components.translator_language.TranslatorLanguageBottomSheet import ua.acclorite.book_story.presentation.data.LocalNavigator import ua.acclorite.book_story.presentation.data.MainEvent import ua.acclorite.book_story.presentation.data.MainState import ua.acclorite.book_story.presentation.data.MainViewModel -import ua.acclorite.book_story.presentation.screens.reader.components.translator_bottom_sheet.ReaderTranslatorBottomSheetLanguageChooser import ua.acclorite.book_story.presentation.screens.settings.components.ChipsWithTitle import ua.acclorite.book_story.presentation.screens.settings.components.SliderWithTitle import ua.acclorite.book_story.presentation.screens.settings.components.SwitchWithTitle -import ua.acclorite.book_story.presentation.screens.settings.data.SettingsEvent -import ua.acclorite.book_story.presentation.screens.settings.data.SettingsState -import ua.acclorite.book_story.presentation.screens.settings.data.SettingsViewModel -import java.util.Locale @Composable fun ReaderSettingsRoot() { val navigator = LocalNavigator.current val mainViewModel: MainViewModel = hiltViewModel() - val settingsViewModel: SettingsViewModel = hiltViewModel() val state = mainViewModel.state.collectAsState() - val settingsState = settingsViewModel.state.collectAsState() ReaderSettings( state = state, - settingsState = settingsState, onNavigate = { navigator.it() }, - onMainEvent = mainViewModel::onEvent, - onEvent = settingsViewModel::onEvent + onMainEvent = mainViewModel::onEvent ) } @@ -68,70 +57,16 @@ fun ReaderSettingsRoot() { @Composable private fun ReaderSettings( state: State, - settingsState: State, onNavigate: OnNavigate, - onMainEvent: (MainEvent) -> Unit, - onEvent: (SettingsEvent) -> Unit + onMainEvent: (MainEvent) -> Unit ) { val scrollState = TopAppBarDefaults.collapsibleUntilExitScrollBehaviorWithLazyListState() - val context = LocalContext.current val fontFamily = remember(state.value.fontFamily) { Constants.FONTS.find { it.id == state.value.fontFamily } ?: Constants.FONTS[0] } - val translateFrom = remember(state.value.translateFrom) { - if (state.value.translateFrom!! == "auto") { - return@remember context.getString(R.string.translator_auto_short) - } - - val locale = Locale(state.value.translateFrom!!) - locale.getDisplayName(locale).replaceFirstChar { char -> - char.uppercase() - } - } - val translateTo = remember(state.value.translateTo) { - val locale = Locale(state.value.translateTo!!) - locale.getDisplayName(locale).replaceFirstChar { char -> - char.uppercase() - } - } - - if (settingsState.value.showReaderTranslatorLanguageBottomSheet) { - TranslatorLanguageBottomSheet( - selectedLanguage = if ( - settingsState.value.readerTranslatorLanguageBottomSheetTranslateFrom - ) { - state.value.translateFrom!! - } else state.value.translateTo!!, - - unselectedLanguage = if ( - !settingsState.value.readerTranslatorLanguageBottomSheetTranslateFrom - ) { - state.value.translateFrom!! - } else state.value.translateTo!!, - - translateFromSelecting = settingsState.value.readerTranslatorLanguageBottomSheetTranslateFrom, - onSelect = { from, to -> - onMainEvent(MainEvent.OnChangeTranslateFrom(from)) - onMainEvent(MainEvent.OnChangeTranslateTo(to)) - - onEvent( - SettingsEvent.OnReaderShowHideTranslatorLanguageBottomSheet( - show = false - ) - ) - }, - onDismiss = { - onEvent( - SettingsEvent.OnReaderShowHideTranslatorLanguageBottomSheet( - show = false - ) - ) - } - ) - } Scaffold( Modifier @@ -164,91 +99,6 @@ private fun ReaderSettings( item { Spacer(modifier = Modifier.height(16.dp)) } - item { - CategoryTitle( - title = stringResource(id = R.string.translator_reader_settings), - color = MaterialTheme.colorScheme.primary - ) - } - item { - Spacer(modifier = Modifier.height(8.dp)) - } - - item { - SwitchWithTitle( - selected = state.value.enableTranslator!!, - title = stringResource(id = R.string.translator_enable_default_option), - description = stringResource(id = R.string.translator_enable_default_option_desc), - onClick = { - onMainEvent( - MainEvent.OnChangeEnableTranslator( - !state.value.enableTranslator!! - ) - ) - } - ) - } - - item { - Spacer(modifier = Modifier.height(8.dp)) - - CategoryTitle( - title = stringResource(id = R.string.translator_language_option) - ) - - Spacer(modifier = Modifier.height(8.dp)) - - ReaderTranslatorBottomSheetLanguageChooser( - fromLanguage = translateFrom, - toLanguage = translateTo, - onFromClick = { - onEvent( - SettingsEvent.OnReaderShowHideTranslatorLanguageBottomSheet( - show = true, - translateFrom = true - ) - ) - }, - onToClick = { - onEvent( - SettingsEvent.OnReaderShowHideTranslatorLanguageBottomSheet( - show = true, - translateFrom = false - ) - ) - }, - switchEnabled = state.value.translateFrom != "auto", - onSwitchClick = { - val from = state.value.translateFrom!! - val to = state.value.translateTo!! - - onMainEvent(MainEvent.OnChangeTranslateFrom(to)) - onMainEvent(MainEvent.OnChangeTranslateTo(from)) - } - ) - - Spacer(modifier = Modifier.height(8.dp)) - } - - - item { - SwitchWithTitle( - selected = state.value.doubleClickTranslation!!, - title = stringResource(id = R.string.translator_double_click_to_translate_option), - description = stringResource(id = R.string.translator_double_click_to_translate_option_desc), - onClick = { - onMainEvent( - MainEvent.OnChangeDoubleClickTranslation( - !state.value.doubleClickTranslation!! - ) - ) - } - ) - } - - item { - Spacer(modifier = Modifier.height(22.dp)) - } item { CategoryTitle( title = stringResource(id = R.string.font_reader_settings), diff --git a/app/src/main/res/drawable/empty_languages.png b/app/src/main/res/drawable/empty_languages.png deleted file mode 100644 index fadacf56f7bdab3ff78c9ec5b4cce40f90d789af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20005 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
S;Y411O=W*dTT8HepH-Q!Q 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

a7*T)jK zE7xq*{A6O1nRboOIwpHE#K0Qipov{YeO*sph2%TBU6qPB6?#S)M4z%WRqu|+ijpFw ztYn(`kjZ|-I*aJh*L91ilH>QBp04!v_UXh+^}+#~e~v}4*u~=5Lg;`udVSC;x~0e# zcdGfcjy#J%Ow2|Vly!VlA)6OE@8+rsQGCAQl8oEJS#`WdY3ClK9%ah{2hJ>9rp?K z6AVrvN-zc3CO5Q*|Gu@StvDW#wNzsKlNa=MYb@9cPv*fVEo&mwqg$n(C7 zB+2}qxk999$WvohRN7*NhFwhb$c)RqtXDo=t+xoomnc!{BzHgtqj<^*X%eS5%#A&r zvtml|*>T{ne5NEoCK8eAu}AlQzy+zB67yU_7+SkTy)Aohj6j9?^R5xB9}45!3&KMK z4NtRnA?vK_+(xlYein}il)?mF>-cCoVajk9lcgt&8PTUoJm z9I~CI%afBJ<6Ri{N5<)l3($^@%55|=u7UuQgmONll{>HAB$JexPNeLdQ&U(vwg=|P R;eo{)L?L^Ts$6MT{tI`1b5H;P diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 2f752898..70fee74e 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -36,8 +36,6 @@ Перемістити книгу? Видалити історію читання? Підтвердити оновлення? - Завантажити %1$s? - Завантажити мови? @@ -79,17 +77,6 @@ Натиснувши на кнопку «Завантажити», ви перейдете в ваш браузер та завантажете останнє оновлення автоматично. - - Вам потрібно завантажити цю мову перед використанням. - Після завантаження, вона буде доступна офлайн та не потребуватиме інтернету. - Кожна мова має розмір близько ~30мб, для завантаження рекомендується використовувати Wi-Fi. - - - Вам потрібно завантажити ці мови перед використанням. - Кожна мова має розмір близько ~30мб, для завантаження рекомендується використовувати Wi-Fi. - Ви можете вибрати мови, які ви хочете завантажити, - всі не вибрані мови не будуть перекладені. - @@ -116,12 +103,6 @@ Щось пішло не так. Спробуйте пізніше. - Мова не була ідентифікована. - Не вдалося ідентифікувати жодної мови. - Неможливо вибрати ту саму мову двічі. - Не можна вибрати цю мову. - Не вдалося завантажити мову. - Не вдалося завантажити мови. Не вдалося скинути обкладинку. Закритий код @@ -136,9 +117,6 @@ Нехай ваша остання прочитана книга відкриє перед вами сторінки нової великої історії вашого читацького шляху - - Не існує мови з такою назвою, або вона не підтримується - %1$s обрано @@ -173,14 +151,11 @@ Ні Завантажити Готово - Відмінити - Далі файли книги історію - мови Невідомий @@ -194,8 +169,6 @@ Видалити обкладинку Видаліть нинішню обкладинку книги Автор - Опис - Текст Читаю @@ -237,13 +210,8 @@ Перевіряти на оновлення Перевіряти на новішу версію застосунку при запуску Бокові відступи - Мова перекладача - Увімкнути перекладач - Увімкнути перекладач при додаванні книги за замовчуванням Переклад подвійним кліком Двічі клацніть на абзац, щоб перекласти його - Перекладати при відкритті книги - Автоматично перекладати весь текст при відкритті книги Червоний @@ -291,11 +259,6 @@ Скопійовано Історія була успішно видалена. Остання версія застосунка вже встановлена. - Мова була успішно завантажена та вибрана. - Всі необхідні мови завантажені. Переклад почнеться незабаром. - Переклад відміняється. Будь ласка, зачекайте. - Перекладений текст відміняється. Будь ласка, зачекайте. - Переклад скоро почнеться. Будь ласка, зачекайте. Обкладинка була успішно скинута. @@ -325,17 +288,6 @@ %1$s букв(а), %2$s слів. - - Визначити мову - Визначити - Перекласти %1$s - з - на - Всі мови - Недавні мови - %1$s абзац(ів) - Показати оригінал (%1$s на %2$s) - Версія застосунка Book\'s Story v%1$s @@ -577,8 +529,5 @@ Скинути початковий посібник Вийти з режиму редагування Відкрити в браузері - Перекладач - Поміняти мови місцями - Завантажено \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 24880cb2..f2a609da 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -55,8 +55,6 @@ Move book? Delete reading history? Confirm update? - Download %1$s? - Download languages? @@ -98,17 +96,6 @@ By clicking the «Download» button you will be transferred to your browser and download the latest update automatically. - - You need to download this language before using it. - After downloading, it will be available offline and will not require internet. - Each language is about ~30mb in size, it is recommended to use Wi-Fi to download it. - - - You need to download these languages before. - Each language is about ~30mb in size, it is recommended to use Wi-Fi to download it. - You can select which languages you want to download, - all unselected languages will not be translated. - @@ -135,12 +122,6 @@ Something went wrong. Try again later. - Could not identify language. - Could not identify any languages. - Cannot select same language twice. - Cannot select this language. - Could not download language. - Could not download languages. Could not reset cover image. Closed source @@ -154,9 +135,6 @@ Let your last book you read open the pages of a new great story of your reading journey - - There is no such language with this name, or it is not supported - %1$s selected @@ -191,14 +169,11 @@ No Download Done - Undo - Continue files books history - languages Unknown @@ -212,8 +187,6 @@ Delete cover image Delete current cover image of the book Author - Description - Text Reading @@ -255,13 +228,8 @@ Check for updates Check for newer app version on startup Side padding - Translator language - Enable translator - Enable translator when adding a book by default Double click translation Double click on paragraph to translate it - Translate when open a book - Automatically translate the whole text when open a book Red @@ -309,13 +277,6 @@ Copied History was successfully deleted. The latest version of the app is already installed. - Language was successfully downloaded and selected. - - All required languages are downloaded. Translation will start soon. - - Translation is canceling. Please wait. - Translated text is canceling. Please wait. - Translation will start soon. Please wait. Cover image was successfully reset. @@ -345,17 +306,6 @@ %1$s characters, %2$s words. - - Detect language - Detect - Translate %1$s - from - to - All languages - Recent languages - %1$s paragraph(s) - Show original (%1$s to %2$s) - App version Book\'s Story v%1$s @@ -598,8 +548,5 @@ Reset start guide Exit editing mode Open in web - Translator - Change language places - Downloaded \ No newline at end of file