@@ -6,67 +6,94 @@ import com.getcode.manager.BottomBarManager
66import com.getcode.manager.TopBarManager
77import com.getcode.model.ID
88import com.getcode.model.Kin
9+ import com.getcode.model.chat.MinimalMember
910import com.getcode.navigation.RoomInfoArgs
10- import xyz.flipchat.app.R
11- import xyz.flipchat.app.features.login.register.onResult
11+ import com.getcode.solana.keys.PublicKey
1212import com.getcode.util.resources.ResourceHelper
1313import com.getcode.view.BaseViewModel2
14+ import com.getcode.view.LoadingSuccessState
1415import dagger.hilt.android.lifecycle.HiltViewModel
1516import kotlinx.coroutines.flow.filterIsInstance
16- import kotlinx.coroutines.flow.flatMapLatest
17+ import kotlinx.coroutines.flow.filterNotNull
1718import kotlinx.coroutines.flow.launchIn
1819import kotlinx.coroutines.flow.map
1920import kotlinx.coroutines.flow.mapNotNull
2021import kotlinx.coroutines.flow.onEach
21- import xyz.flipchat.app.beta.Lab
22- import xyz.flipchat.app.beta.Labs
23- import xyz.flipchat.chat.RoomController
22+ import xyz.flipchat.app.R
2423import xyz.flipchat.app.data.RoomInfo
24+ import xyz.flipchat.app.features.login.register.onResult
2525import xyz.flipchat.app.util.IntentUtils
26+ import xyz.flipchat.chat.RoomController
27+ import xyz.flipchat.controllers.ChatsController
2628import xyz.flipchat.services.extensions.titleOrFallback
29+ import xyz.flipchat.services.internal.data.mapper.nullIfEmpty
2730import xyz.flipchat.services.user.UserManager
2831import javax.inject.Inject
2932
33+ sealed interface MemberType {
34+ data object Speaker : MemberType
35+ data object Listener : MemberType
36+ }
37+
3038@HiltViewModel
3139class ChatInfoViewModel @Inject constructor(
3240 private val roomController : RoomController ,
41+ private val chatsController : ChatsController ,
3342 private val resources : ResourceHelper ,
3443 private val userManager : UserManager ,
35- labs : Labs ,
3644) : BaseViewModel2<ChatInfoViewModel.State, ChatInfoViewModel.Event>(
3745 initialState = State (),
3846 updateStateForEvent = updateStateForEvent
3947) {
4048
4149 data class State (
4250 val isHost : Boolean = false ,
51+ val isMember : Boolean = false ,
52+ val paymentDestination : PublicKey ? = null ,
4353 val roomNameChangesEnabled : Boolean = false ,
54+ val isOpen : Boolean = false ,
4455 val roomInfo : RoomInfo = RoomInfo (),
45- val requestBeingSent : Boolean = false ,
56+ val joining : LoadingSuccessState = LoadingSuccessState (),
57+ val leaving : LoadingSuccessState = LoadingSuccessState (),
58+ val members : Map <MemberType , List <MinimalMember >> = emptyMap()
4659 )
4760
4861 sealed interface Event {
62+ // region state updates
4963 data class OnRoomNameChangesEnabled (val enabled : Boolean ) : Event
5064 data class OnHostStatusChanged (val isHost : Boolean ) : Event
65+ data class OnRoomOpenStateChanged (val isOpen : Boolean ) : Event
66+ data class OnDestinationChanged (val destination : PublicKey ) : Event
5167 data class OnInfoChanged (val args : RoomInfoArgs ) : Event
52- data class OnRequestInFlight (val sending : Boolean ) : Event
53- data class OnMembersUpdated (val count : Int ) : Event
68+ data class OnMembersUpdated (val members : List <MinimalMember >) : Event
69+ // endregion state updates
70+
71+ // region action/reaction
5472 data class OnChangeCover (val roomId : ID ) : Event
5573 data class OnCoverChanged (val cover : Kin ) : Event
74+
5675 data class OnChangeName (val id : ID , val title : String ) : Event
5776 data class OnNameChanged (val name : String ) : Event
77+
5878 data object OnShareRoomClicked : Event
5979 data class ShareRoom (val intent : Intent ) : Event
80+
81+ data object OnListenToClicked : Event
82+ data class OnJoiningStateChanged (val joining : Boolean , val joined : Boolean = false ) : Event
83+ data class OnBecameMember (val roomId : ID ) : Event
84+
85+ data object CloseTemporarily : Event
86+ data object Reopen : Event
87+
6088 data object LeaveRoom : Event
89+ data class OnLeavingStateChanged (val leaving : Boolean , val left : Boolean = false ) : Event
6190 data object OnLeaveRoomConfirmed : Event
91+ // endregion action/reaction
92+
6293 data object OnLeftRoom : Event
6394 }
6495
6596 init {
66- labs.observe(Lab .RoomNameChanges )
67- .onEach { dispatchEvent(Event .OnRoomNameChangesEnabled (it)) }
68- .launchIn(viewModelScope)
69-
7097 eventFlow
7198 .filterIsInstance<Event .OnInfoChanged >()
7299 .map { it.args.ownerId }
@@ -77,14 +104,52 @@ class ChatInfoViewModel @Inject constructor(
77104
78105 eventFlow
79106 .filterIsInstance<Event .OnInfoChanged >()
80- .mapNotNull { it.args.roomId }
81- .flatMapLatest { roomController.observeConversation(it) }
82- .mapNotNull { it }
83- .map { Triple (it.conversation, it.members.count(), it.conversation.coverCharge) }
84- .onEach { (conversation, members, cover) ->
85- dispatchEvent(Event .OnNameChanged (conversation.titleOrFallback(resources)))
86- dispatchEvent(Event .OnMembersUpdated (members))
87- dispatchEvent(Event .OnCoverChanged (cover))
107+ .mapNotNull { it.args }
108+ .onEach { args ->
109+ val exists = roomController.getConversation(args.roomId.orEmpty()) != null
110+ if (! exists) {
111+ chatsController.lookupRoom(args.roomNumber)
112+ .onSuccess { (room, members) ->
113+ dispatchEvent(Event .OnRoomOpenStateChanged (room.isOpen))
114+ dispatchEvent(Event .OnNameChanged (room.titleOrFallback(resources)))
115+ dispatchEvent(
116+ Event .OnMembersUpdated (
117+ members.map { m ->
118+ MinimalMember (
119+ id = m.id,
120+ displayName = m.identity?.displayName.nullIfEmpty(),
121+ profileImageUrl = m.identity?.imageUrl.nullIfEmpty(),
122+ isHost = m.isModerator,
123+ isSelf = userManager.isSelf(m.id),
124+ )
125+ }
126+ )
127+ )
128+ dispatchEvent(Event .OnCoverChanged (room.messagingFee))
129+ }
130+ } else {
131+ roomController.observeConversation(args.roomId.orEmpty())
132+ .filterNotNull()
133+ .map { Triple (it.conversation, it.members, it.conversation.coverCharge) }
134+ .onEach { (conversation, members, cover) ->
135+ dispatchEvent(Event .OnRoomOpenStateChanged (conversation.isOpen))
136+ dispatchEvent(Event .OnNameChanged (conversation.titleOrFallback(resources)))
137+ dispatchEvent(
138+ Event .OnMembersUpdated (
139+ members.map { m ->
140+ MinimalMember (
141+ id = m.id,
142+ displayName = m.memberName.nullIfEmpty(),
143+ profileImageUrl = m.imageUri,
144+ isHost = m.isHost,
145+ isSelf = userManager.isSelf(m.id),
146+ )
147+ }
148+ )
149+ )
150+ dispatchEvent(Event .OnCoverChanged (cover))
151+ }.launchIn(viewModelScope)
152+ }
88153 }.launchIn(viewModelScope)
89154
90155 eventFlow
@@ -109,6 +174,28 @@ class ChatInfoViewModel @Inject constructor(
109174 )
110175 }.launchIn(viewModelScope)
111176
177+ eventFlow
178+ .filterIsInstance<Event .OnListenToClicked >()
179+ .map { stateFlow.value.roomInfo }
180+ .onEach { roomInfo ->
181+ dispatchEvent(Event .OnJoiningStateChanged (true ))
182+ chatsController.joinRoomAsSpectator(roomInfo.id.orEmpty())
183+ .onFailure {
184+ dispatchEvent(Event .OnJoiningStateChanged (false ))
185+ TopBarManager .showMessage(
186+ TopBarManager .TopBarMessage (
187+ resources.getString(R .string.error_title_failedToFollowRoom),
188+ resources.getString(
189+ R .string.error_description_failedToFollowRoom,
190+ stateFlow.value.roomInfo.title
191+ )
192+ )
193+ )
194+ }.onSuccess {
195+ dispatchEvent(Event .OnBecameMember (it.room.id))
196+ }
197+ }.launchIn(viewModelScope)
198+
112199 eventFlow
113200 .filterIsInstance<Event .OnLeaveRoomConfirmed >()
114201 .map { stateFlow.value.roomInfo.id }
@@ -122,11 +209,11 @@ class ChatInfoViewModel @Inject constructor(
122209 )
123210 return @mapNotNull null
124211 }
125- dispatchEvent(Event .OnRequestInFlight (true ))
212+ dispatchEvent(Event .OnLeavingStateChanged (true ))
126213 roomController.leaveRoom(it)
127214 }.onResult(
128215 onError = {
129- dispatchEvent(Event .OnRequestInFlight (false ))
216+ dispatchEvent(Event .OnLeavingStateChanged (false ))
130217 TopBarManager .showMessage(
131218 TopBarManager .TopBarMessage (
132219 title = resources.getString(R .string.error_title_failedToLeaveRoom),
@@ -135,7 +222,7 @@ class ChatInfoViewModel @Inject constructor(
135222 )
136223 },
137224 onSuccess = {
138- dispatchEvent(Event .OnRequestInFlight ( false ))
225+ dispatchEvent(Event .OnLeavingStateChanged (leaving = false , left = true ))
139226 dispatchEvent(Event .OnLeftRoom )
140227 }
141228 ).launchIn(viewModelScope)
@@ -172,9 +259,12 @@ class ChatInfoViewModel @Inject constructor(
172259 is Event .OnChangeName ,
173260 is Event .OnShareRoomClicked ,
174261 is Event .ShareRoom ,
262+ is Event .OnListenToClicked ,
263+ is Event .OnBecameMember ,
264+ is Event .CloseTemporarily ,
265+ is Event .Reopen ,
175266 Event .OnLeftRoom -> { state -> state }
176267
177- is Event .OnRequestInFlight -> { state -> state.copy(requestBeingSent = event.sending) }
178268 is Event .OnHostStatusChanged -> { state -> state.copy(isHost = event.isHost) }
179269 is Event .OnCoverChanged -> { state ->
180270 state.copy(
@@ -193,14 +283,43 @@ class ChatInfoViewModel @Inject constructor(
193283 }
194284
195285 is Event .OnMembersUpdated -> { state ->
286+ val groupedMembers = event.members
287+ .groupBy { it.isHost }
288+ .mapKeys {
289+ if (it.key) {
290+ MemberType .Speaker
291+ } else {
292+ MemberType .Listener
293+ }
294+ }
295+
196296 state.copy(
197297 roomInfo = state.roomInfo.copy(
198- memberCount = event.count,
199- )
298+ memberCount = event.members.count(),
299+ ),
300+ isMember = event.members.any { it.isSelf },
301+ members = groupedMembers,
200302 )
201303 }
202304
203305 is Event .OnRoomNameChangesEnabled -> { state -> state.copy(roomNameChangesEnabled = event.enabled) }
306+ is Event .OnDestinationChanged -> { state -> state.copy(paymentDestination = event.destination) }
307+ is Event .OnJoiningStateChanged -> { state ->
308+ state.copy(
309+ joining = state.joining.copy(
310+ loading = event.joining,
311+ success = event.joined
312+ )
313+ )
314+ }
315+
316+ is Event .OnLeavingStateChanged -> { state ->
317+ state.copy(
318+ leaving = state.joining.copy(loading = event.leaving, success = event.left)
319+ )
320+ }
321+
322+ is Event .OnRoomOpenStateChanged -> { state -> state.copy(isOpen = event.isOpen) }
204323 }
205324 }
206325 }
0 commit comments