mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 13:08:46 +00:00
Ensures chat folder is remembered when we leave page.
This commit is contained in:
committed by
Cody Henthorne
parent
252a4afa79
commit
be035456f7
@@ -32,6 +32,7 @@ import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.viewModels
|
||||
import org.signal.core.ui.compose.BottomSheets
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.signal.core.ui.compose.SignalPreview
|
||||
@@ -40,7 +41,6 @@ import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity
|
||||
import org.thoughtcrime.securesms.components.settings.app.chats.folders.ChatFolderRecord
|
||||
import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.util.viewModel
|
||||
|
||||
/**
|
||||
* Bottom sheet shown when choosing to add a chat to a folder
|
||||
@@ -59,7 +59,11 @@ class AddToFolderBottomSheet private constructor(private val onDismissListener:
|
||||
OTHER(3)
|
||||
}
|
||||
|
||||
private val viewModel by viewModel { ConversationListViewModel(isArchived = false) }
|
||||
private val viewModel: ConversationListViewModel by viewModels(
|
||||
factoryProducer = {
|
||||
ConversationListViewModel.Factory(isArchived = false)
|
||||
}
|
||||
)
|
||||
|
||||
companion object {
|
||||
private const val ARG_FOLDERS = "argument.folders"
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
package org.thoughtcrime.securesms.conversationlist
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.thoughtcrime.securesms.components.settings.app.chats.folders.ChatFolderRecord
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
|
||||
|
||||
/**
|
||||
* Mapping model of folders used in [ChatFolderAdapter]
|
||||
*/
|
||||
@Parcelize
|
||||
data class ChatFolderMappingModel(
|
||||
val chatFolder: ChatFolderRecord,
|
||||
val unreadCount: Int,
|
||||
val isMuted: Boolean,
|
||||
val isSelected: Boolean
|
||||
) : MappingModel<ChatFolderMappingModel> {
|
||||
) : MappingModel<ChatFolderMappingModel>, Parcelable {
|
||||
override fun areItemsTheSame(newItem: ChatFolderMappingModel): Boolean {
|
||||
return chatFolder.id == newItem.chatFolder.id
|
||||
}
|
||||
|
||||
@@ -146,7 +146,6 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.search.MessageResult;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||
import org.thoughtcrime.securesms.util.AppForegroundObserver;
|
||||
import org.thoughtcrime.securesms.util.AppStartup;
|
||||
import org.thoughtcrime.securesms.util.BottomSheetUtil;
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
package org.thoughtcrime.securesms.conversationlist
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.createSavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.CreationExtras
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.BackpressureStrategy
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.kotlin.addTo
|
||||
import io.reactivex.rxjava3.kotlin.combineLatest
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.rx3.asFlowable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.signal.paging.PagedData
|
||||
import org.signal.paging.PagingConfig
|
||||
import org.signal.paging.ProxyPagingController
|
||||
@@ -35,15 +43,24 @@ import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class ConversationListViewModel(
|
||||
private val isArchived: Boolean
|
||||
private val isArchived: Boolean,
|
||||
private val savedStateHandle: SavedStateHandle
|
||||
) : ViewModel() {
|
||||
|
||||
companion object {
|
||||
private const val STATE = "state"
|
||||
|
||||
private var coldStart = true
|
||||
}
|
||||
|
||||
private val disposables: CompositeDisposable = CompositeDisposable()
|
||||
|
||||
private var saveableState: SaveableState
|
||||
get() = savedStateHandle[STATE] ?: SaveableState()
|
||||
set(value) {
|
||||
savedStateHandle[STATE] = value
|
||||
}
|
||||
|
||||
private val store = RxStore(ConversationListState()).addTo(disposables)
|
||||
private val conversationListDataSource: Flowable<ConversationListDataSource>
|
||||
private val pagingConfig = PagingConfig.Builder()
|
||||
@@ -53,18 +70,18 @@ class ConversationListViewModel(
|
||||
|
||||
val conversationsState: Flowable<List<Conversation>> = store.mapDistinctForUi { it.conversations }
|
||||
val selectedState: Flowable<ConversationSet> = store.mapDistinctForUi { it.selectedConversations }
|
||||
val filterRequestState: Flowable<ConversationFilterRequest> = store.mapDistinctForUi { it.filterRequest }
|
||||
val chatFolderState: Flowable<List<ChatFolderMappingModel>> = store.mapDistinctForUi { it.chatFolders }
|
||||
val filterRequestState: Flowable<ConversationFilterRequest> = savedStateHandle.getStateFlow(STATE, SaveableState()).map { it.filterRequest }.asFlowable()
|
||||
val chatFolderState: Flowable<List<ChatFolderMappingModel>> = savedStateHandle.getStateFlow(STATE, SaveableState()).map { it.chatFolders }.asFlowable()
|
||||
val hasNoConversations: Flowable<Boolean>
|
||||
|
||||
val controller = ProxyPagingController<Long>()
|
||||
|
||||
val folders: List<ChatFolderMappingModel>
|
||||
get() = store.state.chatFolders
|
||||
get() = saveableState.chatFolders
|
||||
val currentFolder: ChatFolderRecord
|
||||
get() = store.state.currentFolder
|
||||
get() = saveableState.currentFolder
|
||||
val conversationFilterRequest: ConversationFilterRequest
|
||||
get() = store.state.filterRequest
|
||||
get() = saveableState.filterRequest
|
||||
val pinnedCount: Int
|
||||
get() = store.state.pinnedCount
|
||||
val webSocketState: Observable<WebSocketConnectionState>
|
||||
@@ -75,8 +92,9 @@ class ConversationListViewModel(
|
||||
get() = store.state.internalSelection
|
||||
|
||||
init {
|
||||
conversationListDataSource = store
|
||||
.stateFlowable
|
||||
val saveableStateFlowable = savedStateHandle.getStateFlow(STATE, SaveableState()).asFlowable()
|
||||
|
||||
conversationListDataSource = saveableStateFlowable
|
||||
.subscribeOn(Schedulers.io())
|
||||
.filter { it.currentFolder.id != -1L }
|
||||
.map { it.filterRequest to it.currentFolder }
|
||||
@@ -128,7 +146,8 @@ class ConversationListViewModel(
|
||||
hasNoConversations = store
|
||||
.stateFlowable
|
||||
.subscribeOn(Schedulers.io())
|
||||
.map { it.filterRequest to it.conversations }
|
||||
.combineLatest(saveableStateFlowable.map { it.filterRequest })
|
||||
.map { (state, filterRequest) -> filterRequest to state.conversations }
|
||||
.distinctUntilChanged()
|
||||
.map { (filterRequest, conversations) ->
|
||||
if (conversations.isNotEmpty()) {
|
||||
@@ -187,9 +206,12 @@ class ConversationListViewModel(
|
||||
}
|
||||
|
||||
fun setFiltered(isFiltered: Boolean, conversationFilterSource: ConversationFilterSource) {
|
||||
store.update {
|
||||
it.copy(filterRequest = ConversationFilterRequest(if (isFiltered) ConversationFilter.UNREAD else ConversationFilter.OFF, conversationFilterSource))
|
||||
}
|
||||
saveableState = saveableState.copy(
|
||||
filterRequest = ConversationFilterRequest(
|
||||
filter = if (isFiltered) ConversationFilter.UNREAD else ConversationFilter.OFF,
|
||||
source = conversationFilterSource
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun loadCurrentFolders() {
|
||||
@@ -211,12 +233,10 @@ class ConversationListViewModel(
|
||||
)
|
||||
}
|
||||
|
||||
store.update {
|
||||
it.copy(
|
||||
currentFolder = folders.find { folder -> folder.id == selectedFolderId } ?: ChatFolderRecord(),
|
||||
chatFolders = chatFolders
|
||||
)
|
||||
}
|
||||
saveableState = saveableState.copy(
|
||||
currentFolder = folders.find { folder -> folder.id == selectedFolderId } ?: ChatFolderRecord(),
|
||||
chatFolders = chatFolders
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,14 +248,12 @@ class ConversationListViewModel(
|
||||
}
|
||||
|
||||
fun select(chatFolder: ChatFolderRecord) {
|
||||
store.update {
|
||||
it.copy(
|
||||
currentFolder = chatFolder,
|
||||
chatFolders = folders.map { model ->
|
||||
model.copy(isSelected = chatFolder.id == model.chatFolder.id)
|
||||
}
|
||||
)
|
||||
}
|
||||
saveableState = saveableState.copy(
|
||||
currentFolder = chatFolder,
|
||||
chatFolders = folders.map { model ->
|
||||
model.copy(isSelected = chatFolder.id == model.chatFolder.id)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun onUpdateMute(chatFolder: ChatFolderRecord, until: Long) {
|
||||
@@ -282,19 +300,30 @@ class ConversationListViewModel(
|
||||
StorageSyncHelper.scheduleSyncForDataChange()
|
||||
}
|
||||
|
||||
private data class ConversationListState(
|
||||
/**
|
||||
* Easily persistable state to ensure proper restoration upon VM recreation.
|
||||
*/
|
||||
@Parcelize
|
||||
private data class SaveableState(
|
||||
val chatFolders: List<ChatFolderMappingModel> = emptyList(),
|
||||
val currentFolder: ChatFolderRecord = ChatFolderRecord(),
|
||||
val filterRequest: ConversationFilterRequest = ConversationFilterRequest(ConversationFilter.OFF, ConversationFilterSource.DRAG)
|
||||
) : Parcelable
|
||||
|
||||
private data class ConversationListState(
|
||||
val conversations: List<Conversation> = emptyList(),
|
||||
val selectedConversations: ConversationSet = ConversationSet(),
|
||||
val internalSelection: Set<Conversation> = emptySet(),
|
||||
val filterRequest: ConversationFilterRequest = ConversationFilterRequest(ConversationFilter.OFF, ConversationFilterSource.DRAG),
|
||||
val pinnedCount: Int = 0
|
||||
)
|
||||
|
||||
class Factory(private val isArchived: Boolean) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return modelClass.cast(ConversationListViewModel(isArchived))!!
|
||||
class Factory(
|
||||
private val isArchived: Boolean
|
||||
) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
|
||||
val savedStateHandle = extras.createSavedStateHandle()
|
||||
|
||||
return modelClass.cast(ConversationListViewModel(isArchived, savedStateHandle))!!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package org.thoughtcrime.securesms.conversationlist.chatfilter
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.thoughtcrime.securesms.conversationlist.model.ConversationFilter
|
||||
|
||||
@Parcelize
|
||||
data class ConversationFilterRequest(
|
||||
val filter: ConversationFilter,
|
||||
val source: ConversationFilterSource
|
||||
)
|
||||
) : Parcelable
|
||||
|
||||
Reference in New Issue
Block a user