fix: Combine both old and new screen states #582
This commit is contained in:
parent
a9fdb3a6b6
commit
fe77229db0
|
@ -73,6 +73,27 @@ fun JsonObject.toMap(): Map<String, Any?> = this
|
||||||
element.extractedContent
|
element.extractedContent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Any?.toSchema(): JsonElement {
|
||||||
|
return when (this) {
|
||||||
|
null -> JsonNull
|
||||||
|
is String -> JsonPrimitive("string")
|
||||||
|
is Number -> JsonPrimitive("number")
|
||||||
|
is Boolean -> JsonPrimitive("boolean")
|
||||||
|
is Map<*, *> -> {
|
||||||
|
val content = map { (k, v) -> k.toString() to v.toSchema() }
|
||||||
|
JsonObject(content.toMap())
|
||||||
|
}
|
||||||
|
|
||||||
|
is List<*> -> {
|
||||||
|
val content = map { it.toSchema() }
|
||||||
|
JsonArray(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
is JsonElement -> JsonPrimitive(this::class.qualifiedName)
|
||||||
|
else -> JsonPrimitive("unknown:" + this::class.qualifiedName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun Any?.toJson(): JsonElement = when (this) {
|
fun Any?.toJson(): JsonElement = when (this) {
|
||||||
null -> JsonNull
|
null -> JsonNull
|
||||||
is String -> JsonPrimitive(this)
|
is String -> JsonPrimitive(this)
|
||||||
|
|
|
@ -2,9 +2,14 @@ package com.artemchep.keyguard.common.usecase.impl
|
||||||
|
|
||||||
import com.artemchep.keyguard.common.io.IO
|
import com.artemchep.keyguard.common.io.IO
|
||||||
import com.artemchep.keyguard.common.service.state.StateRepository
|
import com.artemchep.keyguard.common.service.state.StateRepository
|
||||||
|
import com.artemchep.keyguard.common.service.state.impl.toSchema
|
||||||
import com.artemchep.keyguard.common.usecase.PutScreenState
|
import com.artemchep.keyguard.common.usecase.PutScreenState
|
||||||
|
import com.artemchep.keyguard.feature.crashlytics.crashlyticsTap
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import org.kodein.di.DirectDI
|
import org.kodein.di.DirectDI
|
||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
class PutScreenStateImpl(
|
class PutScreenStateImpl(
|
||||||
private val stateRepository: StateRepository,
|
private val stateRepository: StateRepository,
|
||||||
|
@ -13,6 +18,21 @@ class PutScreenStateImpl(
|
||||||
stateRepository = directDI.instance(),
|
stateRepository = directDI.instance(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private class FailedToSaveScreenStateException(
|
||||||
|
message: String,
|
||||||
|
e: Throwable,
|
||||||
|
) : IOException(message, e)
|
||||||
|
|
||||||
override fun invoke(key: String, state: Map<String, Any?>): IO<Unit> = stateRepository
|
override fun invoke(key: String, state: Map<String, Any?>): IO<Unit> = stateRepository
|
||||||
.put(key, state)
|
.put(key, state)
|
||||||
|
// We have tried to save something that is not
|
||||||
|
// serializable. Notify the developer so he has
|
||||||
|
// a chance to fix it.
|
||||||
|
.crashlyticsTap { e ->
|
||||||
|
val schema = Json.encodeToString(state.toSchema())
|
||||||
|
FailedToSaveScreenStateException(
|
||||||
|
message = schema,
|
||||||
|
e = e,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
package com.artemchep.keyguard.feature.navigation.state
|
package com.artemchep.keyguard.feature.navigation.state
|
||||||
|
|
||||||
import arrow.core.Either
|
import arrow.core.Either
|
||||||
|
import arrow.core.getOrElse
|
||||||
import com.artemchep.keyguard.common.io.attempt
|
import com.artemchep.keyguard.common.io.attempt
|
||||||
import com.artemchep.keyguard.common.io.bind
|
import com.artemchep.keyguard.common.io.bind
|
||||||
import com.artemchep.keyguard.common.io.toIO
|
import com.artemchep.keyguard.common.io.toIO
|
||||||
|
import com.artemchep.keyguard.common.service.state.impl.toSchema
|
||||||
import com.artemchep.keyguard.common.usecase.GetScreenState
|
import com.artemchep.keyguard.common.usecase.GetScreenState
|
||||||
import com.artemchep.keyguard.common.usecase.PutScreenState
|
import com.artemchep.keyguard.common.usecase.PutScreenState
|
||||||
import com.artemchep.keyguard.common.util.flow.combineToList
|
import com.artemchep.keyguard.common.util.flow.combineToList
|
||||||
|
import com.artemchep.keyguard.feature.crashlytics.crashlyticsAttempt
|
||||||
import com.artemchep.keyguard.feature.crashlytics.crashlyticsTap
|
import com.artemchep.keyguard.feature.crashlytics.crashlyticsTap
|
||||||
import kotlinx.collections.immutable.persistentMapOf
|
import kotlinx.collections.immutable.persistentMapOf
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
@ -18,6 +21,9 @@ import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
class DiskHandleImpl private constructor(
|
class DiskHandleImpl private constructor(
|
||||||
private val scope: CoroutineScope,
|
private val scope: CoroutineScope,
|
||||||
|
@ -50,6 +56,11 @@ class DiskHandleImpl private constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class FailedToSerializeScreenStateException(
|
||||||
|
message: String,
|
||||||
|
e: Throwable,
|
||||||
|
) : IOException(message, e)
|
||||||
|
|
||||||
private val registrySink = MutableStateFlow(persistentMapOf<String, Flow<Any?>>())
|
private val registrySink = MutableStateFlow(persistentMapOf<String, Flow<Any?>>())
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -61,12 +72,32 @@ class DiskHandleImpl private constructor(
|
||||||
// key of the variable.
|
// key of the variable.
|
||||||
value
|
value
|
||||||
.map { f -> key to f }
|
.map { f -> key to f }
|
||||||
|
.crashlyticsAttempt { e ->
|
||||||
|
val schema = Json.encodeToString(state.toSchema())
|
||||||
|
FailedToSerializeScreenStateException(
|
||||||
|
message = schema,
|
||||||
|
e = e,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.combineToList()
|
.combineToList()
|
||||||
}
|
}
|
||||||
.debounce(SAVE_DEBOUNCE_MS) // no need to save all of the events
|
.debounce(SAVE_DEBOUNCE_MS) // no need to save all of the events
|
||||||
.onEach { entries ->
|
.onEach { entries ->
|
||||||
val state = entries.toMap()
|
// Start by using the restored state. This is needed because
|
||||||
|
// of this flow:
|
||||||
|
// 1. A user has all of options configured.
|
||||||
|
// 2. A user opens a screen for a super brief moment, that
|
||||||
|
// either loads a different set of options or loads only
|
||||||
|
// a set of all options.
|
||||||
|
// 3. Previous state gets overwritten.
|
||||||
|
val state = restoredState.toMutableMap()
|
||||||
|
entries.forEach { result ->
|
||||||
|
val entry = result.getOrElse {
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
state[entry.first] = entry.second
|
||||||
|
}
|
||||||
val result = tryWrite(state)
|
val result = tryWrite(state)
|
||||||
if (result is Either.Left) {
|
if (result is Either.Left) {
|
||||||
val e = result.value
|
val e = result.value
|
||||||
|
|
Loading…
Reference in New Issue