Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import org.monogram.domain.repository.ConnectionStatus
import org.monogram.presentation.core.util.AppPreferences

interface ChatListComponent {
val state: StateFlow<State>
val uiState: StateFlow<UiState>
val foldersState: StateFlow<FoldersState>
val chatsState: StateFlow<ChatsState>
Expand Down Expand Up @@ -58,6 +59,42 @@ interface ChatListComponent {

fun updateScrollPosition(folderId: Int, index: Int, offset: Int)

data class State(
val chatsByFolder: Map<Int, List<ChatModel>> = emptyMap(),
val folders: List<FolderModel> = emptyList(),
val selectedFolderId: Int = -1,
val currentUser: UserModel? = null,
val isLoadingByFolder: Map<Int, Boolean> = emptyMap(),
val selectedChatIds: Set<Long> = emptySet(),
val activeChatId: Long? = null,
val isSearchActive: Boolean = false,
val searchQuery: String = "",
val searchResults: List<ChatModel> = emptyList(),
val globalSearchResults: List<ChatModel> = emptyList(),
val messageSearchResults: List<MessageModel> = emptyList(),
val recentUsers: List<ChatModel> = emptyList(),
val recentOthers: List<ChatModel> = emptyList(),
val connectionStatus: ConnectionStatus = ConnectionStatus.Connected,
val isArchivePinned: Boolean = true,
val isArchiveAlwaysVisible: Boolean = false,
val isForwarding: Boolean = false,
val canLoadMoreMessages: Boolean = false,
val instantViewUrl: String? = null,
val isProxyEnabled: Boolean = false,
val attachMenuBots: List<AttachMenuBotModel> = emptyList(),
val botWebAppUrl: String? = null,
val botWebAppName: String? = null,
val webAppUrl: String? = null,
val webAppBotId: Long? = null,
val webAppBotName: String? = null,
val webViewUrl: String? = null,
val updateState: UpdateState = UpdateState.Idle,
val scrollPositions: Map<Int, Pair<Int, Int>> = emptyMap()
) {
val chats: List<ChatModel> get() = chatsByFolder[selectedFolderId].orEmpty()
val isLoading: Boolean get() = isLoadingByFolder[selectedFolderId] ?: false
}

@Immutable
data class UiState(
val currentUser: UserModel? = null,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package org.monogram.presentation.features.chats

import com.arkivanov.mvikotlin.core.store.Store

interface ChatListStore : Store<ChatListStore.Intent, ChatListComponent.State, ChatListStore.Label> {

sealed class Intent {
data class ChatClicked(val id: Long) : Intent()
data class ProfileClicked(val id: Long) : Intent()
data class MessageClicked(val chatId: Long, val messageId: Long) : Intent()
object SettingsClicked : Intent()
data class FolderClicked(val id: Int) : Intent()
data class LoadMore(val folderId: Int? = null) : Intent()
object LoadMoreMessages : Intent()
data class ChatLongClicked(val id: Long) : Intent()
object ClearSelection : Intent()
object RetryConnection : Intent()
object SearchToggle : Intent()
data class SearchQueryChange(val query: String) : Intent()
data class SetEmojiStatus(val customEmojiId: Long, val statusPath: String?) : Intent()
object ClearSearchHistory : Intent()
data class RemoveSearchHistoryItem(val chatId: Long) : Intent()
data class MuteSelected(val mute: Boolean) : Intent()
data class ArchiveSelected(val archive: Boolean) : Intent()
object PinSelected : Intent()
object ToggleReadSelected : Intent()
object DeleteSelected : Intent()
object ArchivePinToggle : Intent()
object ConfirmForwarding : Intent()
object NewChatClicked : Intent()
object ProxySettingsClicked : Intent()
object EditFoldersClicked : Intent()
data class DeleteFolder(val folderId: Int) : Intent()
data class EditFolder(val folderId: Int) : Intent()
data class OpenInstantView(val url: String) : Intent()
object DismissInstantView : Intent()
data class OpenWebApp(val url: String, val botUserId: Long, val botName: String) : Intent()
object DismissWebApp : Intent()
data class OpenWebView(val url: String) : Intent()
object DismissWebView : Intent()
object UpdateClicked : Intent()
data class UpdateScrollPosition(val folderId: Int, val index: Int, val offset: Int) : Intent()
data class UpdateState(val state: ChatListComponent.State) : Intent()
}

sealed class Label {
data class OpenChat(val chatId: Long, val messageId: Long?) : Label()
data class OpenProfile(val id: Long) : Label()
object OpenSettings : Label()
object OpenProxySettings : Label()
object OpenNewChat : Label()
data class ConfirmForward(val selectedChatIds: Set<Long>) : Label()
data class EditFolders(val folderId: Int? = null) : Label()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package org.monogram.presentation.features.chats

import com.arkivanov.mvikotlin.core.store.Reducer
import com.arkivanov.mvikotlin.core.store.Store
import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.arkivanov.mvikotlin.extensions.coroutines.CoroutineExecutor
import org.monogram.presentation.features.chats.ChatListStore.Intent
import org.monogram.presentation.features.chats.ChatListStore.Label
import org.monogram.presentation.features.chats.chatList.DefaultChatListComponent

class ChatListStoreFactory(
private val storeFactory: StoreFactory,
private val component: DefaultChatListComponent
) {

fun create(): ChatListStore =
object : ChatListStore, Store<Intent, ChatListComponent.State, Label> by storeFactory.create(
name = "ChatListStore",
initialState = component._state.value,
executorFactory = ::ExecutorImpl,
reducer = ReducerImpl
) {}

private inner class ExecutorImpl : CoroutineExecutor<Intent, Nothing, ChatListComponent.State, Message, Label>() {
override fun executeIntent(intent: Intent) {
when (intent) {
is Intent.ChatClicked -> component.handleChatClicked(intent.id)?.let(::publish)
is Intent.ProfileClicked -> publish(Label.OpenProfile(intent.id))
is Intent.MessageClicked -> component.handleMessageClicked(intent.chatId, intent.messageId)?.let(::publish)
Intent.SettingsClicked -> publish(Label.OpenSettings)
is Intent.FolderClicked -> component.handleFolderClicked(intent.id)
is Intent.LoadMore -> component.handleLoadMore(intent.folderId)
Intent.LoadMoreMessages -> component.handleLoadMoreMessages()
is Intent.ChatLongClicked -> component.handleChatLongClicked(intent.id)
Intent.ClearSelection -> component.handleClearSelection()
Intent.RetryConnection -> component.handleRetryConnection()
Intent.SearchToggle -> component.handleSearchToggle()
is Intent.SearchQueryChange -> component.handleSearchQueryChange(intent.query)
is Intent.SetEmojiStatus -> component.handleSetEmojiStatus(intent.customEmojiId, intent.statusPath)
Intent.ClearSearchHistory -> component.handleClearSearchHistory()
is Intent.RemoveSearchHistoryItem -> component.handleRemoveSearchHistoryItem(intent.chatId)
is Intent.MuteSelected -> component.handleMuteSelected(intent.mute)
is Intent.ArchiveSelected -> component.handleArchiveSelected(intent.archive)
Intent.PinSelected -> component.handlePinSelected()
Intent.ToggleReadSelected -> component.handleToggleReadSelected()
Intent.DeleteSelected -> component.handleDeleteSelected()
Intent.ArchivePinToggle -> component.handleArchivePinToggle()
Intent.ConfirmForwarding -> component.handleConfirmForwarding()?.let(::publish)
Intent.NewChatClicked -> publish(Label.OpenNewChat)
Intent.ProxySettingsClicked -> publish(Label.OpenProxySettings)
Intent.EditFoldersClicked -> publish(Label.EditFolders())
is Intent.DeleteFolder -> component.handleDeleteFolder(intent.folderId)
is Intent.EditFolder -> component.handleEditFolder(intent.folderId)?.let(::publish)
is Intent.OpenInstantView -> component.handleOpenInstantView(intent.url)
Intent.DismissInstantView -> component.handleDismissInstantView()
is Intent.OpenWebApp -> component.handleOpenWebApp(intent.url, intent.botUserId, intent.botName)
Intent.DismissWebApp -> component.handleDismissWebApp()
is Intent.OpenWebView -> component.handleOpenWebView(intent.url)
Intent.DismissWebView -> component.handleDismissWebView()
Intent.UpdateClicked -> component.handleUpdateClicked()?.let(::publish)
is Intent.UpdateScrollPosition -> component.handleUpdateScrollPosition(
intent.folderId,
intent.index,
intent.offset
)

is Intent.UpdateState -> dispatch(Message.UpdateState(intent.state))
}
}
}

private object ReducerImpl : Reducer<ChatListComponent.State, Message> {
override fun ChatListComponent.State.reduce(msg: Message): ChatListComponent.State =
when (msg) {
is Message.UpdateState -> msg.state
}
}

sealed class Message {
data class UpdateState(val state: ChatListComponent.State) : Message()
}
}
Loading