Skip to content

Commit 8482749

Browse files
committed
feat(fc): combine join confirmation and room info and update UI
Signed-off-by: Brandon McAnsh <[email protected]>
1 parent 9bbc493 commit 8482749

File tree

27 files changed

+890
-656
lines changed

27 files changed

+890
-656
lines changed

flipchatApp/src/main/kotlin/xyz/flipchat/app/data/RoomInfo.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ data class RoomInfo(
99
val id: ID? = null,
1010
val number: Long = 0,
1111
val title: String = "",
12+
val imageUrl: String? = null,
1213
val memberCount: Int = 0,
1314
val hostId: ID? = null,
1415
val hostName: String? = null,

flipchatApp/src/main/kotlin/xyz/flipchat/app/features/chat/conversation/ConversationMessages.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import com.getcode.ui.components.text.markup.Markup
3737
import com.getcode.ui.utils.animateScrollToItemWithFullVisibility
3838
import com.getcode.ui.utils.keyboardAsState
3939
import com.getcode.ui.utils.scrollToItemWithFullVisibility
40+
import com.getcode.ui.utils.verticalScrollStateGradient
4041
import kotlinx.coroutines.delay
4142
import kotlinx.coroutines.launch
4243
import xyz.flipchat.app.R
@@ -63,7 +64,9 @@ internal fun ConversationMessages(
6364
modifier = modifier,
6465
) {
6566
MessageList(
66-
modifier = Modifier.fillMaxSize(),
67+
modifier = Modifier
68+
.fillMaxSize()
69+
.verticalScrollStateGradient(lazyListState, color = CodeTheme.colors.background),
6770
messages = messages,
6871
listState = lazyListState,
6972
handleMessagePointers = { (current, previous, next) ->

flipchatApp/src/main/kotlin/xyz/flipchat/app/features/chat/conversation/ConversationScreen.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ data class ConversationScreen(
149149
.onEach {
150150
navigator.push(
151151
ScreenRegistry.get(
152-
NavScreenProvider.Room.Lookup.Confirm(args = it, returnToSender = true)
152+
NavScreenProvider.Room.Info(args = it, returnToSender = true)
153153
)
154154
)
155155
}.launchIn(this)

flipchatApp/src/main/kotlin/xyz/flipchat/app/features/chat/info/ChatInfoViewModel.kt

Lines changed: 147 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,67 +6,94 @@ import com.getcode.manager.BottomBarManager
66
import com.getcode.manager.TopBarManager
77
import com.getcode.model.ID
88
import com.getcode.model.Kin
9+
import com.getcode.model.chat.MinimalMember
910
import 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
1212
import com.getcode.util.resources.ResourceHelper
1313
import com.getcode.view.BaseViewModel2
14+
import com.getcode.view.LoadingSuccessState
1415
import dagger.hilt.android.lifecycle.HiltViewModel
1516
import kotlinx.coroutines.flow.filterIsInstance
16-
import kotlinx.coroutines.flow.flatMapLatest
17+
import kotlinx.coroutines.flow.filterNotNull
1718
import kotlinx.coroutines.flow.launchIn
1819
import kotlinx.coroutines.flow.map
1920
import kotlinx.coroutines.flow.mapNotNull
2021
import 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
2423
import xyz.flipchat.app.data.RoomInfo
24+
import xyz.flipchat.app.features.login.register.onResult
2525
import xyz.flipchat.app.util.IntentUtils
26+
import xyz.flipchat.chat.RoomController
27+
import xyz.flipchat.controllers.ChatsController
2628
import xyz.flipchat.services.extensions.titleOrFallback
29+
import xyz.flipchat.services.internal.data.mapper.nullIfEmpty
2730
import xyz.flipchat.services.user.UserManager
2831
import javax.inject.Inject
2932

33+
sealed interface MemberType {
34+
data object Speaker : MemberType
35+
data object Listener : MemberType
36+
}
37+
3038
@HiltViewModel
3139
class 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

Comments
 (0)