@@ -19,9 +19,6 @@ package com.wire.android.feature.cells.ui.tags
1919
2020import androidx.compose.foundation.text.input.TextFieldState
2121import androidx.compose.foundation.text.input.clearText
22- import androidx.compose.runtime.getValue
23- import androidx.compose.runtime.mutableStateOf
24- import androidx.compose.runtime.setValue
2522import androidx.compose.runtime.snapshotFlow
2623import androidx.lifecycle.SavedStateHandle
2724import androidx.lifecycle.viewModelScope
@@ -30,11 +27,11 @@ import com.wire.android.ui.common.ActionsViewModel
3027import com.wire.kalium.cells.domain.usecase.GetAllTagsUseCase
3128import com.wire.kalium.cells.domain.usecase.RemoveNodeTagsUseCase
3229import com.wire.kalium.cells.domain.usecase.UpdateNodeTagsUseCase
33- import com.wire.kalium.common.functional.getOrElse
3430import com.wire.kalium.common.functional.onFailure
3531import com.wire.kalium.common.functional.onSuccess
3632import dagger.hilt.android.lifecycle.HiltViewModel
3733import kotlinx.coroutines.flow.MutableStateFlow
34+ import kotlinx.coroutines.flow.asStateFlow
3835import kotlinx.coroutines.flow.collectLatest
3936import kotlinx.coroutines.flow.debounce
4037import kotlinx.coroutines.flow.update
@@ -50,98 +47,86 @@ class AddRemoveTagsViewModel @Inject constructor(
5047) : ActionsViewModel<AddRemoveTagsViewModelAction>() {
5148
5249 private val navArgs: AddRemoveTagsNavArgs = savedStateHandle.navArgs()
50+ private val initialTags: Set <String > = navArgs.tags.toSet()
51+ private val disallowedChars = setOf (" ," , " ;" , " /" , " \\ " , " \" " , " \' " , " <" , " >" )
5352
54- val isLoading = MutableStateFlow (false )
53+ private val _state = MutableStateFlow (TagsViewState (addedTags = initialTags))
54+ val state = _state .asStateFlow()
5555
5656 val tagsTextState = TextFieldState ()
5757
58- val initialTags: Set <String > = navArgs.tags.toSet()
59-
60- val disallowedChars = listOf (" ," , " ;" , " /" , " \\ " , " \" " , " \' " , " <" , " >" )
61-
62- var allTags: Set <String > = emptySet()
63- private set
64-
65- val addedTags: MutableStateFlow <Set <String >> = MutableStateFlow (navArgs.tags.toSet())
66-
67- var suggestedTags by mutableStateOf<Set <String >>(emptySet())
68- private set
69-
7058 init {
7159 viewModelScope.launch {
72- allTags = getAllTagsUseCase().getOrElse { emptySet() }
73- updateSuggestions(" " ) // initial state
74- }
75-
76- viewModelScope.launch {
60+ getAllTagsUseCase().onSuccess { tags ->
61+ _state .update { it.copy(allTags = tags) }
62+ }
7763 snapshotFlow { tagsTextState.text.toString() }
7864 .debounce(TYPING_DEBOUNCE_TIME )
79- .collectLatest {
80- onQueryChanged(it)
81- }
65+ .collectLatest { updateViewState() }
8266 }
8367 }
8468
85- fun onQueryChanged (query : String ) {
86- updateSuggestions(query)
87- }
88-
89- private fun updateSuggestions (query : String ) {
90- suggestedTags = allTags
91- .asSequence()
92- .filter { query.isBlank() || it.contains(query, ignoreCase = true ) }
93- .filter { it !in addedTags.value }
94- .toSet()
69+ fun isValidTag (): Boolean = with (tagsTextState) {
70+ disallowedChars.none { it in text } && text.length in ALLOWED_LENGTH
9571 }
9672
97- fun isValidTag (): Boolean = disallowedChars.none {
98- it in tagsTextState.text
99- } && tagsTextState.text.length in ALLOWED_LENGTH
100-
10173 fun addTag (tag : String ) {
102- tag.trim().let { newTag ->
103- if (newTag.isNotBlank() && newTag !in addedTags.value) {
104- addedTags.update { it + newTag }
105- updateSuggestions(" " )
106- tagsTextState.clearText()
107- }
74+ val addedTags = state.value.addedTags
75+ val newTag = tag.trim()
76+ if (newTag.isNotBlank() && newTag !in addedTags) {
77+ updateViewState(addedTags + tag)
78+ tagsTextState.clearText()
10879 }
10980 }
11081
11182 fun removeTag (tag : String ) {
112- addedTags.update { it - tag }
113- updateSuggestions(" " )
83+ updateViewState(state.value.addedTags - tag)
11484 }
11585
11686 fun removeLastTag () {
117- addedTags.value.lastOrNull()?.let { lastTag ->
118- removeTag(lastTag)
119- }
87+ state.value.addedTags.lastOrNull()?.let { removeTag(it) }
12088 }
12189
122- fun updateTags () {
123- viewModelScope.launch {
124- isLoading.value = true
125- val result = if (addedTags.value.isEmpty()) {
126- removeNodeTagsUseCase(navArgs.uuid)
127- } else {
128- updateNodeTagsUseCase(navArgs.uuid, addedTags.value)
129- }
90+ fun updateTags () = viewModelScope.launch {
91+ _state .update { it.copy(isLoading = true ) }
13092
131- result
132- .onSuccess { sendAction( AddRemoveTagsViewModelAction . Success ) }
133- .onFailure { sendAction( AddRemoveTagsViewModelAction . Failure ) }
134- . also { isLoading .value = false }
93+ if (state.value.addedTags.isEmpty()) {
94+ removeNodeTagsUseCase(navArgs.uuid)
95+ } else {
96+ updateNodeTagsUseCase(navArgs.uuid, state .value.addedTags)
13597 }
98+ .onSuccess { sendAction(AddRemoveTagsViewModelAction .Success ) }
99+ .onFailure { sendAction(AddRemoveTagsViewModelAction .Failure ) }
100+ .also { _state .update { it.copy(isLoading = false ) } }
136101 }
137102
138- companion object {
139- val ALLOWED_LENGTH = 1 .. 30
103+ fun updateViewState (addedTags : Set <String > = state.value.addedTags) {
104+ _state .update { current ->
105+ current.copy(
106+ addedTags = addedTags,
107+ suggestedTags = current.allTags
108+ .filter { it !in addedTags }
109+ .filter { it.contains(tagsTextState.text.toString(), ignoreCase = true ) }
110+ .toSet(),
111+ tagsUpdated = addedTags != initialTags
112+ )
113+ }
114+ }
140115
116+ private companion object {
117+ val ALLOWED_LENGTH = 1 .. 30
141118 const val TYPING_DEBOUNCE_TIME = 200L
142119 }
143120}
144121
122+ data class TagsViewState (
123+ val isLoading : Boolean = false ,
124+ val tagsUpdated : Boolean = false ,
125+ val addedTags : Set <String > = emptySet(),
126+ val suggestedTags : Set <String > = emptySet(),
127+ val allTags : Set <String > = emptySet(),
128+ )
129+
145130sealed interface AddRemoveTagsViewModelAction {
146131 data object Success : AddRemoveTagsViewModelAction
147132 data object Failure : AddRemoveTagsViewModelAction
0 commit comments