diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/jam/JamSessionService.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/jam/JamSessionService.kt index bfcde591..b23b25f6 100644 --- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/jam/JamSessionService.kt +++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/jam/JamSessionService.kt @@ -74,6 +74,9 @@ class JamSessionService( private val _localParticipantId = MutableStateFlow(null) val localParticipantId: StateFlow = _localParticipantId.asStateFlow() + private val _isConnected = MutableStateFlow(false) + val isConnected: StateFlow = _isConnected.asStateFlow() + private val _incomingMessages = MutableSharedFlow(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) { diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/jam/JamScreen.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/jam/JamScreen.kt index bb3cc055..84b0e737 100644 --- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/jam/JamScreen.kt +++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/jam/JamScreen.kt @@ -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) diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/jam/JamViewModel.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/jam/JamViewModel.kt index 2033c322..0b831839 100644 --- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/jam/JamViewModel.kt +++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/jam/JamViewModel.kt @@ -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 = 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 {