feat(jam-session): add connection state management and update UI feedback for session status

This commit is contained in:
Kingkor Roy Tirtho 2026-09-04 15:46:56 +06:00
parent 0eb07ef9b6
commit 60e38f4868
3 changed files with 56 additions and 23 deletions

View File

@ -74,6 +74,9 @@ class JamSessionService(
private val _localParticipantId = MutableStateFlow<String?>(null)
val localParticipantId: StateFlow<String?> = _localParticipantId.asStateFlow()
private val _isConnected = MutableStateFlow(false)
val isConnected: StateFlow<Boolean> = _isConnected.asStateFlow()
private val _incomingMessages = MutableSharedFlow<JamMessage>(extraBufferCapacity = 64)
val incomingMessages = _incomingMessages.asSharedFlow()
@ -174,7 +177,7 @@ class JamSessionService(
return true
}
suspend fun joinSession(offerSdp: String): String {
suspend fun joinSession(offerSdp: String, hostName: String? = null): String {
log.i { "Joining jam session" }
val participantName = resolveParticipantName(defaultPrefix = "Guest")
@ -186,6 +189,13 @@ class JamSessionService(
hostConnection = pc
_role.value = JamRole.Guest
_localParticipantId.value = "guest"
_participants.value = listOf(
JamParticipant(
id = "host",
displayName = hostName?.ifBlank { null } ?: "Host",
isHost = true,
)
)
_isActive.value = true
// The data channel arrives in-band from the host's offer via on_data_channel;
@ -226,6 +236,7 @@ class JamSessionService(
_role.value = null
_participants.value = emptyList()
_isActive.value = false
_isConnected.value = false
_localParticipantId.value = null
}
@ -303,6 +314,7 @@ class JamSessionService(
override fun onDataChannelOpen(label: String) {
log.i { "[$guestId] Data channel '$label' open" }
_isConnected.value = true
}
override fun onDataChannelMessage(label: String, data: String) {
@ -329,6 +341,7 @@ class JamSessionService(
override fun onDataChannelOpen(label: String) {
log.i { "Data channel '$label' open" }
_isConnected.value = true
}
override fun onDataChannelMessage(label: String, data: String) {

View File

@ -295,23 +295,35 @@ private fun GuestSessionView(
ParticipantsSection(state.participants)
val answerLink = state.answerLink
if (answerLink == null) {
Text(
text = "Connecting to the session...",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
} else {
Text(
text = "Almost there! Send your answer back to the host:",
style = MaterialTheme.typography.titleSmall,
)
ShareableLinkBox(
label = "Answer link",
link = answerLink,
onCopy = { clipboard.setText(AnnotatedString(answerLink)) },
onShare = { onShare(answerLink) },
)
when {
state.isConnected -> {
Text(
text = "Connected to the session",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.primary,
)
}
answerLink == null -> {
Text(
text = "Connecting to the session...",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
else -> {
Text(
text = "Almost there! Send your answer back to the host:",
style = MaterialTheme.typography.titleSmall,
)
ShareableLinkBox(
label = "Answer link",
link = answerLink,
onCopy = { clipboard.setText(AnnotatedString(answerLink)) },
onShare = { onShare(answerLink) },
)
}
}
LeaveButton(onLeave)

View File

@ -37,6 +37,7 @@ import kotlinx.coroutines.launch
data class JamUiState(
val isActive: Boolean = false,
val isConnected: Boolean = false,
val role: JamRole? = null,
val participants: List<JamParticipant> = emptyList(),
/** Host: deep link containing this session's SDP offer, ready to share. */
@ -69,6 +70,7 @@ class JamViewModel(
_uiState.update {
it.copy(
isActive = active,
isConnected = jamSession.isConnected.value,
role = jamSession.role.value,
participants = jamSession.participants.value,
inviteLink = if (!active) null else it.inviteLink,
@ -84,6 +86,11 @@ class JamViewModel(
_uiState.update { it.copy(participants = participants) }
}
}
viewModelScope.launch {
jamSession.isConnected.collect { connected ->
_uiState.update { it.copy(isConnected = connected) }
}
}
viewModelScope.launch {
deepLinks.pendingLink.collect { link ->
handleDeepLink(link)
@ -119,16 +126,17 @@ class JamViewModel(
fun joinWithIncomingInvite() {
val sdp = _uiState.value.incomingOfferSdp ?: return
join(sdp)
join(sdp, _uiState.value.incomingHostName)
}
fun joinWithPasted(input: String) {
val sdp = JamInviteCodec.extractSdp(input)
val parsed = JamInviteCodec.parse(input)
val sdp = parsed?.sdp ?: JamInviteCodec.extractSdp(input)
if (sdp == null) {
_uiState.update { it.copy(error = "That doesn't look like a valid jam invite.") }
return
}
join(sdp)
join(sdp, (parsed as? JamInviteLink.HostInvite)?.peerName)
}
/**
@ -171,10 +179,10 @@ class JamViewModel(
_uiState.update { it.copy(incomingOfferSdp = null, incomingHostName = null) }
}
private fun join(offerSdp: String) {
private fun join(offerSdp: String, hostName: String? = null) {
viewModelScope.launch {
runCatching {
val answer = jamSession.joinSession(offerSdp)
val answer = jamSession.joinSession(offerSdp, hostName)
JamInviteCodec.buildGuestAnswer(localName(), answer)
}.onSuccess { link ->
_uiState.update {