porting settings to reducer pattern

- creates a generate page reducer for combining with other reducers
This commit is contained in:
Adam Brown 2022-11-02 19:29:48 +00:00
parent 8a8aa375c0
commit 7a13f530b0
10 changed files with 311 additions and 233 deletions

View File

@ -61,4 +61,4 @@ data class SpiderPage<T>(
)
@JvmInline
value class Route<S>(val value: String)
value class Route<out S>(val value: String)

View File

@ -1,10 +1,8 @@
package app.dapk.st.core.page
import app.dapk.st.design.components.SpiderPage
import app.dapk.state.Action
import app.dapk.state.ReducerFactory
import app.dapk.state.change
import app.dapk.state.createReducer
import app.dapk.state.*
import kotlin.reflect.KClass
fun <P : Any> createPageReducer(
initialPage: SpiderPage<out P>
@ -30,9 +28,9 @@ fun <P : Any> createPageReducer(
)
}
sealed interface PageAction : Action {
data class GoTo<P : Any>(val page: SpiderPage<P>) : PageAction
data class UpdatePage<P : Any>(val pageContent: P) : PageAction
sealed interface PageAction<P> : Action {
data class GoTo<P : Any>(val page: SpiderPage<P>) : PageAction<P>
data class UpdatePage<P : Any>(val pageContent: P) : PageAction<P>
}
data class PageContainer<P>(
@ -43,3 +41,43 @@ fun PageContainer<*>.isDifferentPage(page: SpiderPage<*>): Boolean {
return page::class != this.page::class
}
interface PageReducerScope {
fun <PC : Any> withPageContent(page: KClass<PC>, block: PageDispatchScope<PC>.() -> Unit)
}
interface PageDispatchScope<P> {
fun ReducerScope<*>.pageDispatch(action: PageAction<P>)
fun getPageState(): P?
}
fun <P : Any, S : Any> createPageReducer(
initialPage: SpiderPage<out P>,
factory: PageReducerScope.() -> ReducerFactory<S>,
): ReducerFactory<Combined2<PageContainer<P>, S>> = shareState {
combineReducers(
createPageReducer(initialPage),
factory(object : PageReducerScope {
override fun <PC : Any> withPageContent(page: KClass<PC>, block: PageDispatchScope<PC>.() -> Unit) {
val currentPage = getSharedState().state1.page.state
if (currentPage::class == page) {
val pageDispatchScope = object : PageDispatchScope<PC> {
override fun ReducerScope<*>.pageDispatch(action: PageAction<PC>) {
val currentPageGuard = getSharedState().state1.page.state
if (currentPageGuard::class == page) {
dispatch(action)
}
}
override fun getPageState() = getSharedState().state1.page.state as? PC
}
block(pageDispatchScope)
}
}
})
)
}
inline fun <reified PC : Any> PageReducerScope.withPageContext(crossinline block: PageDispatchScope<PC>.(PC) -> Unit) {
withPageContent(PC::class) { getPageState()?.let { block(it) } }
}

View File

@ -40,7 +40,7 @@ fun interface Reducer<S> {
private fun <S> createScope(coroutineScope: CoroutineScope, store: Store<S>) = object : ReducerScope<S> {
override val coroutineScope = coroutineScope
override suspend fun dispatch(action: Action) = store.dispatch(action)
override fun dispatch(action: Action) = store.dispatch(action)
override fun getState(): S = store.getState()
}
@ -52,7 +52,7 @@ interface Store<S> {
interface ReducerScope<S> {
val coroutineScope: CoroutineScope
suspend fun dispatch(action: Action)
fun dispatch(action: Action)
fun getState(): S
}
@ -85,13 +85,13 @@ fun <S1, S2> combineReducers(r1: ReducerFactory<S1>, r2: ReducerFactory<S2>): Re
override fun create(scope: ReducerScope<Combined2<S1, S2>>): Reducer<Combined2<S1, S2>> {
val r1Scope = object : ReducerScope<S1> {
override val coroutineScope: CoroutineScope = scope.coroutineScope
override suspend fun dispatch(action: Action) = scope.dispatch(action)
override fun dispatch(action: Action) = scope.dispatch(action)
override fun getState() = scope.getState().state1
}
val r2Scope = object : ReducerScope<S2> {
override val coroutineScope: CoroutineScope = scope.coroutineScope
override suspend fun dispatch(action: Action) = scope.dispatch(action)
override fun dispatch(action: Action) = scope.dispatch(action)
override fun getState() = scope.getState().state2
}
@ -174,9 +174,10 @@ fun <A : Action, S> async(klass: KClass<A>, block: suspend ReducerScope<S>.(A) -
fun <A : Action, S> multi(klass: KClass<A>, block: Multi<A, S>.(A) -> (ReducerScope<S>) -> ActionHandler<S>): (ReducerScope<S>) -> ActionHandler<S> {
val multiScope = object : Multi<A, S> {
override fun sideEffect(block: (A, S) -> Unit): (ReducerScope<S>) -> ActionHandler<S> = sideEffect(klass, block)
override fun sideEffect(block: suspend (S) -> Unit): (ReducerScope<S>) -> ActionHandler<S> = sideEffect(klass) { _, state -> block(state) }
override fun change(block: (A, S) -> S): (ReducerScope<S>) -> ActionHandler<S> = change(klass, block)
override fun async(block: suspend ReducerScope<S>.(A) -> Unit): (ReducerScope<S>) -> ActionHandler<S> = async(klass, block)
override fun nothing() = sideEffect { }
}
return {
@ -187,7 +188,8 @@ fun <A : Action, S> multi(klass: KClass<A>, block: Multi<A, S>.(A) -> (ReducerSc
}
interface Multi<A : Action, S> {
fun sideEffect(block: (A, S) -> Unit): (ReducerScope<S>) -> ActionHandler<S>
fun sideEffect(block: suspend (S) -> Unit): (ReducerScope<S>) -> ActionHandler<S>
fun nothing(): (ReducerScope<S>) -> ActionHandler<S>
fun change(block: (A, S) -> S): (ReducerScope<S>) -> ActionHandler<S>
fun async(block: suspend ReducerScope<S>.(A) -> Unit): (ReducerScope<S>) -> ActionHandler<S>
}

View File

@ -30,7 +30,7 @@ internal fun directoryReducer(
}.launchIn(coroutineScope))
}
ComponentLifecycle.OnGone -> sideEffect { _, _ -> jobBag.cancel(KEY_SYNCING_JOB) }
ComponentLifecycle.OnGone -> sideEffect { jobBag.cancel(KEY_SYNCING_JOB) }
}
},

View File

@ -4,20 +4,17 @@ import android.os.Bundle
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import app.dapk.st.core.DapkActivity
import app.dapk.st.core.module
import app.dapk.st.core.resetModules
import app.dapk.st.core.viewModel
import app.dapk.st.core.*
class SettingsActivity : DapkActivity() {
private val settingsViewModel by viewModel { module<SettingsModule>().settingsViewModel() }
private val settingsState by state { module<SettingsModule>().settingsState() }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Surface(Modifier.fillMaxSize()) {
SettingsScreen(settingsViewModel, onSignOut = {
SettingsScreen(settingsState, onSignOut = {
resetModules()
navigator.navigate.toHome()
finish()

View File

@ -8,6 +8,8 @@ import app.dapk.st.domain.application.message.MessageOptionsStore
import app.dapk.st.engine.ChatEngine
import app.dapk.st.push.PushModule
import app.dapk.st.settings.eventlogger.EventLoggerViewModel
import app.dapk.st.settings.state.SettingsState
import app.dapk.st.settings.state.settingsReducer
class SettingsModule(
private val chatEngine: ChatEngine,
@ -22,20 +24,25 @@ class SettingsModule(
private val messageOptionsStore: MessageOptionsStore,
) : ProvidableModule {
internal fun settingsViewModel(): SettingsViewModel {
return SettingsViewModel(
chatEngine,
storeModule.cacheCleaner(),
contentResolver,
UriFilenameResolver(contentResolver, coroutineDispatchers),
SettingsItemFactory(buildMeta, deviceMeta, pushModule.pushTokenRegistrars(), themeStore, loggingStore, messageOptionsStore),
pushModule.pushTokenRegistrars(),
themeStore,
loggingStore,
messageOptionsStore,
)
internal fun settingsState(): SettingsState {
return createStateViewModel {
settingsReducer(
chatEngine,
storeModule.cacheCleaner(),
contentResolver,
UriFilenameResolver(contentResolver, coroutineDispatchers),
SettingsItemFactory(buildMeta, deviceMeta, pushModule.pushTokenRegistrars(), themeStore, loggingStore, messageOptionsStore),
pushModule.pushTokenRegistrars(),
themeStore,
loggingStore,
messageOptionsStore,
it,
JobBag(),
)
}
}
internal fun eventLogViewModel(): EventLoggerViewModel {
return EventLoggerViewModel(storeModule.eventLogStore())
}

View File

@ -41,35 +41,44 @@ import app.dapk.st.core.components.CenteredLoading
import app.dapk.st.core.components.Header
import app.dapk.st.core.extensions.takeAs
import app.dapk.st.core.getActivity
import app.dapk.st.core.page.PageAction
import app.dapk.st.design.components.*
import app.dapk.st.engine.ImportResult
import app.dapk.st.navigator.Navigator
import app.dapk.st.settings.SettingsEvent.*
import app.dapk.st.settings.eventlogger.EventLogActivity
import app.dapk.st.settings.state.ComponentLifecycle
import app.dapk.st.settings.state.RootActions
import app.dapk.st.settings.state.ScreenAction
import app.dapk.st.settings.state.SettingsState
@OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterial3Api::class)
@Composable
internal fun SettingsScreen(viewModel: SettingsViewModel, onSignOut: () -> Unit, navigator: Navigator) {
viewModel.ObserveEvents(onSignOut)
internal fun SettingsScreen(settingsState: SettingsState, onSignOut: () -> Unit, navigator: Navigator) {
settingsState.ObserveEvents(onSignOut)
LaunchedEffect(true) {
viewModel.start()
settingsState.dispatch(ComponentLifecycle.Visible)
}
val onNavigate: (SpiderPage<out Page>?) -> Unit = {
when (it) {
null -> navigator.navigate.upToHome()
else -> viewModel.goTo(it)
else -> settingsState.dispatch(PageAction.GoTo(it))
}
}
Spider(currentPage = viewModel.state.page, onNavigate = onNavigate) {
Spider(currentPage = settingsState.current.state1.page, onNavigate = onNavigate) {
item(Page.Routes.root) {
RootSettings(it, onClick = { viewModel.onClick(it) }, onRetry = { viewModel.start() })
RootSettings(
it,
onClick = { settingsState.dispatch(ScreenAction.OnClick(it)) },
onRetry = { settingsState.dispatch(ComponentLifecycle.Visible) }
)
}
item(Page.Routes.encryption) {
Encryption(viewModel, it)
Encryption(settingsState, it)
}
item(Page.Routes.pushProviders) {
PushProviders(viewModel, it)
PushProviders(settingsState, it)
}
item(Page.Routes.importRoomKeys) {
when (val result = it.importProgress) {
@ -83,7 +92,7 @@ internal fun SettingsScreen(viewModel: SettingsViewModel, onSignOut: () -> Unit,
) {
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) {
it?.let {
viewModel.fileSelected(it)
settingsState.dispatch(RootActions.SelectKeysFile(it))
}
}
val keyboardController = LocalSoftwareKeyboardController.current
@ -100,7 +109,7 @@ internal fun SettingsScreen(viewModel: SettingsViewModel, onSignOut: () -> Unit,
var passwordVisibility by rememberSaveable { mutableStateOf(false) }
val startImportAction = {
keyboardController?.hide()
viewModel.importFromFileKeys(it.selectedFile.uri, passphrase)
settingsState.dispatch(RootActions.ImportKeysFromFile(it.selectedFile.uri, passphrase))
}
TextField(
@ -235,40 +244,40 @@ private fun RootSettings(page: Page.Root, onClick: (SettingItem) -> Unit, onRetr
}
@Composable
private fun Encryption(viewModel: SettingsViewModel, page: Page.Security) {
private fun Encryption(state: SettingsState, page: Page.Security) {
Column {
TextRow("Import room keys", includeDivider = false, onClick = { viewModel.goToImportRoom() })
TextRow("Import room keys", includeDivider = false, onClick = { state.dispatch(ScreenAction.OpenImportRoom) })
}
}
@Composable
private fun PushProviders(viewModel: SettingsViewModel, state: Page.PushProviders) {
private fun PushProviders(state: SettingsState, page: Page.PushProviders) {
LaunchedEffect(true) {
viewModel.fetchPushProviders()
state.dispatch(RootActions.FetchProviders)
}
when (val lce = state.options) {
when (val lce = page.options) {
null -> {}
is Lce.Loading -> CenteredLoading()
is Lce.Content -> {
LazyColumn {
items(lce.value) {
Row(Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically) {
RadioButton(selected = it == state.selection, onClick = { viewModel.selectPushProvider(it) })
RadioButton(selected = it == page.selection, onClick = { state.dispatch(RootActions.SelectPushProvider(it)) })
Text(it.id)
}
}
}
}
is Lce.Error -> GenericError(cause = lce.cause) { viewModel.fetchPushProviders() }
is Lce.Error -> GenericError(cause = lce.cause) { state.dispatch(RootActions.FetchProviders) }
}
}
@Composable
private fun SettingsViewModel.ObserveEvents(onSignOut: () -> Unit) {
private fun SettingsState.ObserveEvents(onSignOut: () -> Unit) {
val context = LocalContext.current
StartObserving {
this@ObserveEvents.events.launch {

View File

@ -1,182 +0,0 @@
package app.dapk.st.settings
import android.content.ContentResolver
import android.net.Uri
import androidx.lifecycle.viewModelScope
import app.dapk.st.core.Lce
import app.dapk.st.core.ThemeStore
import app.dapk.st.design.components.SpiderPage
import app.dapk.st.domain.StoreCleaner
import app.dapk.st.domain.application.eventlog.LoggingStore
import app.dapk.st.domain.application.message.MessageOptionsStore
import app.dapk.st.engine.ChatEngine
import app.dapk.st.engine.ImportResult
import app.dapk.st.push.PushTokenRegistrars
import app.dapk.st.push.Registrar
import app.dapk.st.settings.SettingItem.Id.*
import app.dapk.st.settings.SettingsEvent.*
import app.dapk.st.viewmodel.DapkViewModel
import app.dapk.st.viewmodel.MutableStateFactory
import app.dapk.st.viewmodel.defaultStateFactory
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
private const val PRIVACY_POLICY_URL = "https://ouchadam.github.io/small-talk/privacy/"
internal class SettingsViewModel(
private val chatEngine: ChatEngine,
private val cacheCleaner: StoreCleaner,
private val contentResolver: ContentResolver,
private val uriFilenameResolver: UriFilenameResolver,
private val settingsItemFactory: SettingsItemFactory,
private val pushTokenRegistrars: PushTokenRegistrars,
private val themeStore: ThemeStore,
private val loggingStore: LoggingStore,
private val messageOptionsStore: MessageOptionsStore,
factory: MutableStateFactory<SettingsScreenState> = defaultStateFactory(),
) : DapkViewModel<SettingsScreenState, SettingsEvent>(
initialState = SettingsScreenState(SpiderPage(Page.Routes.root, "Settings", null, Page.Root(Lce.Loading()))),
factory = factory,
) {
fun start() {
viewModelScope.launch {
val root = Page.Root(Lce.Content(settingsItemFactory.root()))
val rootPage = SpiderPage(Page.Routes.root, "Settings", null, root)
updateState { copy(page = rootPage) }
}
}
fun goTo(page: SpiderPage<out Page>) {
updateState { copy(page = page) }
}
fun onClick(item: SettingItem) {
when (item.id) {
SignOut -> viewModelScope.launch {
cacheCleaner.cleanCache(removeCredentials = true)
_events.emit(SignedOut)
}
AccessToken -> viewModelScope.launch {
require(item is SettingItem.AccessToken)
_events.emit(CopyToClipboard("Token copied", item.accessToken))
}
ClearCache -> viewModelScope.launch {
cacheCleaner.cleanCache(removeCredentials = false)
_events.emit(Toast(message = "Cache deleted"))
}
EventLog -> viewModelScope.launch {
_events.emit(OpenEventLog)
}
Encryption -> {
updateState {
copy(page = SpiderPage(Page.Routes.encryption, "Encryption", Page.Routes.root, Page.Security))
}
}
PrivacyPolicy -> viewModelScope.launch {
_events.emit(OpenUrl(PRIVACY_POLICY_URL))
}
PushProvider -> {
updateState {
copy(page = SpiderPage(Page.Routes.pushProviders, "Push providers", Page.Routes.root, Page.PushProviders()))
}
}
Ignored -> {
// do nothing
}
ToggleDynamicTheme -> viewModelScope.launch {
themeStore.storeMaterialYouEnabled(!themeStore.isMaterialYouEnabled())
refreshRoot()
_events.emit(RecreateActivity)
}
ToggleEnableLogs -> viewModelScope.launch {
loggingStore.setEnabled(!loggingStore.isEnabled())
refreshRoot()
}
ToggleSendReadReceipts -> viewModelScope.launch {
messageOptionsStore.setReadReceiptsDisabled(!messageOptionsStore.isReadReceiptsDisabled())
refreshRoot()
}
}
}
private fun refreshRoot() {
start()
}
fun fetchPushProviders() {
updatePageState<Page.PushProviders> { copy(options = Lce.Loading()) }
viewModelScope.launch {
val currentSelection = pushTokenRegistrars.currentSelection()
val options = pushTokenRegistrars.options()
updatePageState<Page.PushProviders> {
copy(
selection = currentSelection,
options = Lce.Content(options)
)
}
}
}
fun selectPushProvider(registrar: Registrar) {
viewModelScope.launch {
pushTokenRegistrars.makeSelection(registrar)
fetchPushProviders()
}
}
fun importFromFileKeys(file: Uri, passphrase: String) {
updatePageState<Page.ImportRoomKey> { copy(importProgress = ImportResult.Update(0)) }
viewModelScope.launch {
with(chatEngine) {
runCatching { contentResolver.openInputStream(file)!! }
.fold(
onSuccess = { fileStream ->
fileStream.importRoomKeys(passphrase)
.onEach {
updatePageState<Page.ImportRoomKey> { copy(importProgress = it) }
}
.launchIn(viewModelScope)
},
onFailure = {
updatePageState<Page.ImportRoomKey> { copy(importProgress = ImportResult.Error(ImportResult.Error.Type.UnableToOpenFile)) }
}
)
}
}
}
fun goToImportRoom() {
goTo(SpiderPage(Page.Routes.importRoomKeys, "Import room keys", Page.Routes.encryption, Page.ImportRoomKey()))
}
fun fileSelected(file: Uri) {
viewModelScope.launch {
val namedFile = NamedUri(
name = uriFilenameResolver.readFilenameFromUri(file),
uri = file
)
updatePageState<Page.ImportRoomKey> { copy(selectedFile = namedFile) }
}
}
@Suppress("UNCHECKED_CAST")
private inline fun <reified S : Page> updatePageState(crossinline block: S.() -> S) {
val page = state.page
val currentState = page.state
require(currentState is S)
updateState { copy(page = (page as SpiderPage<S>).copy(state = block(page.state))) }
}
}

View File

@ -0,0 +1,22 @@
package app.dapk.st.settings.state
import android.net.Uri
import app.dapk.st.push.Registrar
import app.dapk.st.settings.SettingItem
import app.dapk.state.Action
internal sealed interface ScreenAction : Action {
data class OnClick(val item: SettingItem) : ScreenAction
object OpenImportRoom : ScreenAction
}
internal sealed interface RootActions : Action {
object FetchProviders : RootActions
data class SelectPushProvider(val registrar: Registrar) : RootActions
data class ImportKeysFromFile(val file: Uri, val passphrase: String) : RootActions
data class SelectKeysFile(val file: Uri) : RootActions
}
internal sealed interface ComponentLifecycle : Action {
object Visible : ComponentLifecycle
}

View File

@ -0,0 +1,185 @@
package app.dapk.st.settings.state
import android.content.ContentResolver
import app.dapk.st.core.JobBag
import app.dapk.st.core.Lce
import app.dapk.st.core.State
import app.dapk.st.core.ThemeStore
import app.dapk.st.core.page.PageAction
import app.dapk.st.core.page.PageContainer
import app.dapk.st.core.page.createPageReducer
import app.dapk.st.core.page.withPageContext
import app.dapk.st.design.components.SpiderPage
import app.dapk.st.domain.StoreCleaner
import app.dapk.st.domain.application.eventlog.LoggingStore
import app.dapk.st.domain.application.message.MessageOptionsStore
import app.dapk.st.engine.ChatEngine
import app.dapk.st.engine.ImportResult
import app.dapk.st.push.PushTokenRegistrars
import app.dapk.st.settings.*
import app.dapk.st.settings.SettingItem.Id.*
import app.dapk.st.settings.SettingsEvent.*
import app.dapk.state.Combined2
import app.dapk.state.async
import app.dapk.state.createReducer
import app.dapk.state.multi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
private const val PRIVACY_POLICY_URL = "https://ouchadam.github.io/small-talk/privacy/"
internal fun settingsReducer(
chatEngine: ChatEngine,
cacheCleaner: StoreCleaner,
contentResolver: ContentResolver,
uriFilenameResolver: UriFilenameResolver,
settingsItemFactory: SettingsItemFactory,
pushTokenRegistrars: PushTokenRegistrars,
themeStore: ThemeStore,
loggingStore: LoggingStore,
messageOptionsStore: MessageOptionsStore,
eventEmitter: suspend (SettingsEvent) -> Unit,
jobBag: JobBag,
) = createPageReducer(
initialPage = SpiderPage<Page>(Page.Routes.root, "Settings", null, Page.Root(Lce.Loading())),
factory = {
createReducer(
initialState = Unit,
async(ComponentLifecycle.Visible::class) {
jobBag.replace("page", coroutineScope.launch {
val root = Page.Root(Lce.Content(settingsItemFactory.root()))
val rootPage = SpiderPage(Page.Routes.root, "Settings", null, root)
dispatch(PageAction.GoTo(rootPage))
})
},
async(RootActions.FetchProviders::class) {
withPageContext<Page.PushProviders> {
pageDispatch(PageAction.UpdatePage(it.copy(options = Lce.Loading())))
}
val currentSelection = pushTokenRegistrars.currentSelection()
val options = pushTokenRegistrars.options()
withPageContext<Page.PushProviders> {
pageDispatch(
PageAction.UpdatePage(
it.copy(
selection = currentSelection,
options = Lce.Content(options)
)
)
)
}
},
async(RootActions.SelectPushProvider::class) {
pushTokenRegistrars.makeSelection(it.registrar)
dispatch(RootActions.FetchProviders)
},
async(RootActions.ImportKeysFromFile::class) { action ->
withPageContext<Page.ImportRoomKey> {
pageDispatch(PageAction.UpdatePage(it.copy(importProgress = ImportResult.Update(0))))
}
with(chatEngine) {
runCatching { contentResolver.openInputStream(action.file)!! }
.fold(
onSuccess = { fileStream ->
fileStream.importRoomKeys(action.passphrase)
.onEach { progress ->
withPageContext<Page.ImportRoomKey> {
pageDispatch(PageAction.UpdatePage(it.copy(importProgress = progress)))
}
}
.launchIn(coroutineScope)
},
onFailure = {
withPageContext<Page.ImportRoomKey> {
pageDispatch(PageAction.UpdatePage(it.copy(importProgress = ImportResult.Error(ImportResult.Error.Type.UnableToOpenFile))))
}
}
)
}
},
async(RootActions.SelectKeysFile::class) { action ->
val namedFile = NamedUri(
name = uriFilenameResolver.readFilenameFromUri(action.file),
uri = action.file
)
withPageContext<Page.ImportRoomKey> {
pageDispatch(PageAction.UpdatePage(it.copy(selectedFile = namedFile)))
}
},
multi(ScreenAction.OnClick::class) { action ->
val item = action.item
when (item.id) {
SignOut -> sideEffect {
cacheCleaner.cleanCache(removeCredentials = true)
eventEmitter.invoke(SignedOut)
}
AccessToken -> sideEffect {
require(item is SettingItem.AccessToken)
eventEmitter.invoke(CopyToClipboard("Token copied", item.accessToken))
}
ClearCache -> sideEffect {
cacheCleaner.cleanCache(removeCredentials = false)
eventEmitter.invoke(Toast(message = "Cache deleted"))
}
EventLog -> sideEffect {
eventEmitter.invoke(OpenEventLog)
}
Encryption -> async {
dispatch(PageAction.GoTo(SpiderPage(Page.Routes.encryption, "Encryption", Page.Routes.root, Page.Security)))
}
PrivacyPolicy -> sideEffect {
eventEmitter.invoke(OpenUrl(PRIVACY_POLICY_URL))
}
PushProvider -> async {
dispatch(PageAction.GoTo(SpiderPage(Page.Routes.pushProviders, "Push providers", Page.Routes.root, Page.PushProviders())))
}
Ignored -> {
nothing()
}
ToggleDynamicTheme -> async {
themeStore.storeMaterialYouEnabled(!themeStore.isMaterialYouEnabled())
dispatch(ComponentLifecycle.Visible)
eventEmitter.invoke(RecreateActivity)
}
ToggleEnableLogs -> async {
loggingStore.setEnabled(!loggingStore.isEnabled())
dispatch(ComponentLifecycle.Visible)
}
ToggleSendReadReceipts -> async {
messageOptionsStore.setReadReceiptsDisabled(!messageOptionsStore.isReadReceiptsDisabled())
dispatch(ComponentLifecycle.Visible)
}
}
},
async(ScreenAction.OpenImportRoom::class) {
dispatch(PageAction.GoTo(SpiderPage(Page.Routes.importRoomKeys, "Import room keys", Page.Routes.encryption, Page.ImportRoomKey())))
},
)
}
)
internal typealias SettingsState = State<Combined2<PageContainer<Page>, Unit>, SettingsEvent>