Fixed: Multiple issues with JSON LanguageHistory(fixed with moving it in Room Database) + Room version migration 2 → 3.

This commit is contained in:
acclorite 2024-06-28 19:24:45 +03:00
parent ce7fef8dc8
commit 562c06b0c4
19 changed files with 373 additions and 89 deletions

View file

@ -0,0 +1,187 @@
{
"formatVersion": 1,
"database": {
"version": 3,
"identityHash": "8b6e8d2712148ad1c165aa984ffcf35b",
"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, `enableTranslator` INTEGER NOT NULL DEFAULT false, `translateFrom` TEXT NOT NULL DEFAULT 'auto', `translateTo` TEXT NOT NULL DEFAULT 'en', `doubleClickTranslation` INTEGER NOT NULL DEFAULT true, `translateWhenOpen` INTEGER NOT NULL DEFAULT false)",
"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
},
{
"fieldPath": "enableTranslator",
"columnName": "enableTranslator",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "translateFrom",
"columnName": "translateFrom",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'auto'"
},
{
"fieldPath": "translateTo",
"columnName": "translateTo",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'en'"
},
{
"fieldPath": "doubleClickTranslation",
"columnName": "doubleClickTranslation",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "true"
},
{
"fieldPath": "translateWhenOpen",
"columnName": "translateWhenOpen",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
}
],
"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": []
},
{
"tableName": "LanguageHistoryEntity",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`languageCode` TEXT NOT NULL, `order` INTEGER NOT NULL, PRIMARY KEY(`languageCode`))",
"fields": [
{
"fieldPath": "languageCode",
"columnName": "languageCode",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "order",
"columnName": "order",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"languageCode"
]
},
"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, '8b6e8d2712148ad1c165aa984ffcf35b')"
]
}
}

View file

@ -16,6 +16,7 @@ import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import ua.acclorite.book_story.data.local.room.BookDao
import ua.acclorite.book_story.data.local.room.BookDatabase
import ua.acclorite.book_story.data.local.room.DatabaseHelper
import ua.acclorite.book_story.data.remote.GithubAPI
import javax.inject.Singleton
@ -43,7 +44,13 @@ object AppModule {
app,
BookDatabase::class.java,
"book_db"
).allowMainThreadQueries().build().dao
)
.addMigrations(
DatabaseHelper.MIGRATION_2_3, // creates LanguageHistoryEntity table(if does not exist)
)
.allowMainThreadQueries()
.build()
.dao
}
@Provides

View file

@ -12,6 +12,8 @@ import ua.acclorite.book_story.data.mapper.book.BookMapper
import ua.acclorite.book_story.data.mapper.book.BookMapperImpl
import ua.acclorite.book_story.data.mapper.history.HistoryMapper
import ua.acclorite.book_story.data.mapper.history.HistoryMapperImpl
import ua.acclorite.book_story.data.mapper.language_history.LanguageHistoryMapper
import ua.acclorite.book_story.data.mapper.language_history.LanguageHistoryMapperImpl
import ua.acclorite.book_story.data.parser.FileParser
import ua.acclorite.book_story.data.parser.FileParserImpl
import ua.acclorite.book_story.data.parser.TextParser
@ -47,6 +49,12 @@ abstract class RepositoryModule {
historyMapperImpl: HistoryMapperImpl
): HistoryMapper
@Binds
@Singleton
abstract fun bindLanguageHistoryMapper(
languageHistoryMapperImpl: LanguageHistoryMapperImpl
): LanguageHistoryMapper
@Binds
@Singleton
abstract fun bindFileParser(

View file

@ -0,0 +1,10 @@
package ua.acclorite.book_story.data.local.dto
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity
data class LanguageHistoryEntity(
@PrimaryKey(false) val languageCode: String,
val order: Int
)

View file

@ -6,8 +6,10 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import androidx.room.Upsert
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
@Dao
interface BookDao {
@ -25,9 +27,7 @@ interface BookDao {
)
suspend fun searchBooks(query: String): List<BookEntity>
@Query(
"SELECT * FROM bookentity WHERE id=:id"
)
@Query("SELECT * FROM bookentity WHERE id=:id")
suspend fun findBookById(id: Int): BookEntity
@Query("SELECT * FROM bookentity WHERE id IN (:ids)")
@ -59,4 +59,11 @@ interface BookDao {
@Delete
suspend fun deleteHistory(history: List<HistoryEntity>)
@Upsert
suspend fun updateLanguageHistory(history: List<LanguageHistoryEntity>)
@Query("SELECT * FROM languagehistoryentity")
suspend fun getLanguageHistory(): List<LanguageHistoryEntity>
}

View file

@ -3,20 +3,40 @@ package ua.acclorite.book_story.data.local.room
import androidx.room.AutoMigration
import androidx.room.Database
import androidx.room.RoomDatabase
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
HistoryEntity::class,
LanguageHistoryEntity::class
],
version = 2,
version = 3,
autoMigrations = [
AutoMigration(1, 2)
AutoMigration(1, 2),
AutoMigration(2, 3),
],
exportSchema = true
)
abstract class BookDatabase : RoomDatabase() {
abstract val dao: BookDao
}
object DatabaseHelper {
val MIGRATION_2_3 = object : Migration(2, 3) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL(
"CREATE TABLE IF NOT EXISTS `LanguageHistoryEntity` (" +
"`languageCode` TEXT NOT NULL," +
" `order` INTEGER NOT NULL," +
" PRIMARY KEY(`languageCode`)" +
")"
)
}
}
}

View file

@ -0,0 +1,10 @@
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
}

View file

@ -0,0 +1,22 @@
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
)
}
}

View file

@ -29,12 +29,14 @@ 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
@ -67,6 +69,7 @@ class BookRepositoryImpl @Inject constructor(
private val bookMapper: BookMapper,
private val historyMapper: HistoryMapper,
private val languageHistoryMapper: LanguageHistoryMapper,
private val fileParser: FileParser,
private val textParser: TextParser
@ -816,6 +819,20 @@ class BookRepositoryImpl @Inject constructor(
}
}
}
override suspend fun getLanguageHistory(): List<LanguageHistory> {
return database.getLanguageHistory().map {
languageHistoryMapper.toLanguageHistory(it)
}
}
override suspend fun updateLanguageHistory(history: List<LanguageHistory>) {
database.updateLanguageHistory(
history.map {
languageHistoryMapper.toLanguageHistoryEntity(it)
}
)
}
}

View file

@ -1,14 +1,9 @@
package ua.acclorite.book_story.domain.model
import android.os.Parcelable
import androidx.compose.runtime.Immutable
import com.google.errorprone.annotations.Keep
import kotlinx.parcelize.Parcelize
@Parcelize
@Immutable
@Keep
data class LanguageHistory(
val id: Int,
val languageCode: String
) : Parcelable
val languageCode: String,
val order: Int
)

View file

@ -6,6 +6,7 @@ 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
@ -105,4 +106,9 @@ interface BookRepository {
targetLanguage: LanguageCode,
text: String
): Resource<String>
suspend fun getLanguageHistory(): List<LanguageHistory>
suspend fun updateLanguageHistory(history: List<LanguageHistory>)
}

View file

@ -0,0 +1,12 @@
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<LanguageHistory> {
return repository.getLanguageHistory()
}
}

View file

@ -0,0 +1,12 @@
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<LanguageHistory>) {
repository.updateLanguageHistory(history)
}
}

View file

@ -32,7 +32,6 @@ 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.domain.model.LanguageHistory
import ua.acclorite.book_story.presentation.components.CategoryTitle
import ua.acclorite.book_story.presentation.components.CustomAnimatedVisibility
import ua.acclorite.book_story.presentation.components.CustomBottomSheet
@ -45,8 +44,6 @@ import ua.acclorite.book_story.presentation.components.translator_language.compo
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.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainViewModel
import ua.acclorite.book_story.presentation.ui.DefaultTransition
import ua.acclorite.book_story.presentation.ui.Transitions
@ -69,11 +66,8 @@ fun TranslatorLanguageBottomSheet(
onSelect: (String, String) -> Unit
) {
val translatorLanguageViewModel: TranslatorLanguageViewModel = hiltViewModel()
val mainViewModel: MainViewModel = hiltViewModel()
val state = translatorLanguageViewModel.state.collectAsState()
val mainState = mainViewModel.state.collectAsState()
val loading = remember { mutableStateOf(true) }
LaunchedEffect(Unit) {
@ -81,7 +75,6 @@ fun TranslatorLanguageBottomSheet(
selectedLanguage = selectedLanguage,
unselectedLanguage = unselectedLanguage,
translateFromSelecting = translateFromSelecting,
history = mainState.value.translatorLanguageHistory!!,
loaded = {
loading.value = false
}
@ -96,24 +89,9 @@ fun TranslatorLanguageBottomSheet(
val languageToChoose = if (fromSelecting) from else to
if (languageToChoose != "auto") {
val currentHistory = mainState.value.translatorLanguageHistory!!.toMutableList()
val langToAdd = state.value.languageToSelect
val newHistory = mutableListOf<LanguageHistory>()
val indexOfItem = currentHistory.indexOfFirst { it.languageCode == langToAdd }
if (indexOfItem != -1) {
currentHistory.removeAt(indexOfItem)
}
currentHistory.add(0, LanguageHistory(0, langToAdd))
currentHistory.sortedBy { it.id }.forEachIndexed { index, languageHistory ->
newHistory.add(LanguageHistory(index, languageHistory.languageCode))
}
mainViewModel.onEvent(
MainEvent.OnChangeTranslatorLanguageHistory(
newHistory
translatorLanguageViewModel.onEvent(
TranslatorLanguageEvent.OnUpdateLanguageHistory(
languageToChoose
)
)
}
@ -157,7 +135,7 @@ private fun TranslatorLanguageBottomSheetContent(
.fillMaxHeight(),
dragHandle = {},
shape = RoundedCornerShape(0),
scrimColor = Color.Transparent,
scrimColor = MaterialTheme.colorScheme.surface,
onDismissRequest = {
onDismiss()
},

View file

@ -3,6 +3,7 @@ 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
@ -36,4 +37,8 @@ sealed class TranslatorLanguageEvent {
val languageCode: String,
val block: (Language) -> Language
) : TranslatorLanguageEvent()
data class OnUpdateLanguageHistory(
val languageToChoose: LanguageCode
) : TranslatorLanguageEvent()
}

View file

@ -17,7 +17,9 @@ 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
@ -26,7 +28,9 @@ import javax.inject.Inject
class TranslatorLanguageViewModel @Inject constructor(
private val isLanguageModelDownloaded: IsLanguageModelDownloaded,
private val downloadLanguageModel: DownloadLanguageModel,
private val deleteLanguageModel: DeleteLanguageModel
private val deleteLanguageModel: DeleteLanguageModel,
private val getLanguageHistory: GetLanguageHistory,
private val updateLanguageHistory: UpdateLanguageHistory,
) : ViewModel() {
private val _state = MutableStateFlow(TranslatorLanguageState())
@ -313,6 +317,33 @@ class TranslatorLanguageViewModel @Inject constructor(
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)
}
}
}
}
@ -320,7 +351,6 @@ class TranslatorLanguageViewModel @Inject constructor(
selectedLanguage: String,
unselectedLanguage: String,
translateFromSelecting: Boolean,
history: List<LanguageHistory>,
loaded: () -> Unit
) {
viewModelScope.launch(Dispatchers.IO) {
@ -329,6 +359,7 @@ class TranslatorLanguageViewModel @Inject constructor(
TranslatorLanguageState()
}
val history = getLanguageHistory.execute()
val unfilteredLanguages = TranslateLanguage.getAllLanguages()
.filter {
if (it == "ru") {
@ -353,7 +384,7 @@ class TranslatorLanguageViewModel @Inject constructor(
val historyOrder = history.find {
it.languageCode == languageCode
}?.id
}?.order
Language(
languageCode = languageCode,

View file

@ -1,7 +1,5 @@
package ua.acclorite.book_story.presentation.data
import ua.acclorite.book_story.domain.model.LanguageHistory
sealed class MainEvent {
data class OnChangeLanguage(val lang: String) : MainEvent()
data class OnChangeTheme(val theme: String) : MainEvent()
@ -23,5 +21,4 @@ sealed class MainEvent {
data class OnChangeTranslateFrom(val languageCode: String) : MainEvent()
data class OnChangeTranslateTo(val languageCode: String) : MainEvent()
data class OnChangeDoubleClickTranslation(val bool: Boolean) : MainEvent()
data class OnChangeTranslatorLanguageHistory(val history: List<LanguageHistory>) : MainEvent()
}

View file

@ -6,12 +6,7 @@ import androidx.annotation.Keep
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
import com.google.mlkit.nl.translate.TranslateLanguage
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import kotlinx.parcelize.Parcelize
import ua.acclorite.book_story.domain.model.LanguageHistory
import ua.acclorite.book_story.domain.util.Constants
import ua.acclorite.book_story.domain.util.DataStoreConstants
import ua.acclorite.book_story.presentation.ui.DarkTheme
@ -52,21 +47,9 @@ data class MainState(
val enableTranslator: Boolean? = null,
val translateFrom: String? = null,
val translateTo: String? = null,
val doubleClickTranslation: Boolean? = null,
val translatorLanguageHistory: List<LanguageHistory>? = null,
val doubleClickTranslation: Boolean? = null
) : Parcelable {
companion object {
val translatorHistoryMoshi: JsonAdapter<List<LanguageHistory>> = Moshi
.Builder()
.add(KotlinJsonAdapterFactory())
.build()
.adapter(
Types.newParameterizedType(
List::class.java,
LanguageHistory::class.java
)
)
/**
* Initializes [MainState] by given [Map].
*/
@ -144,12 +127,6 @@ data class MainState(
data[DOUBLE_CLICK_TRANSLATION.name] as? Boolean
?: true
val translatorLanguageHistory: List<LanguageHistory> =
(data[TRANSLATOR_LANGUAGE_HISTORY.name] as? String)?.let {
translatorHistoryMoshi.fromJson(it)
} ?: emptyList()
return MainState(
language = language,
theme = theme.toTheme(),
@ -170,8 +147,7 @@ data class MainState(
enableTranslator = enableTranslator,
translateFrom = translateFrom,
translateTo = translateTo,
doubleClickTranslation = doubleClickTranslation,
translatorLanguageHistory = translatorLanguageHistory
doubleClickTranslation = doubleClickTranslation
)
}
}

View file

@ -271,22 +271,6 @@ class MainViewModel @Inject constructor(
}
}
}
is MainEvent.OnChangeTranslatorLanguageHistory -> {
viewModelScope.launch(Dispatchers.IO) {
val historyJson = MainState.translatorHistoryMoshi.toJson(event.history)
setDatastore.execute(
DataStoreConstants.TRANSLATOR_LANGUAGE_HISTORY,
historyJson
)
updateStateWithSavedHandle {
it.copy(
translatorLanguageHistory = event.history
)
}
}
}
}
}