feat: wire incoming call navigation and accept button

- Add ObserveIncomingCalls composable in AppNavigation that watches
  callManager.state and navigates to ActiveCall screen when an
  IncomingCall is detected
- Wire the accept button in CallScreen to call
  callController.acceptIncomingCall(sdpOffer), which sets the remote
  SDP, creates a WebRTC answer, and sends it back gift-wrapped
- Pass CallController to CallScreen for accept functionality

https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS
This commit is contained in:
Claude
2026-04-01 23:47:35 +00:00
parent 0c43e11d46
commit 43776c5bdc
2 changed files with 24 additions and 1 deletions
@@ -55,6 +55,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.vitorpamplona.amethyst.commons.call.CallManager
import com.vitorpamplona.amethyst.commons.call.CallState
import com.vitorpamplona.amethyst.service.call.CallController
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@@ -66,6 +67,7 @@ import kotlinx.coroutines.launch
@Composable
fun CallScreen(
callManager: CallManager,
callController: CallController?,
accountViewModel: AccountViewModel,
onCallEnded: () -> Unit,
) {
@@ -91,7 +93,7 @@ fun CallScreen(
callerPubKey = state.callerPubKey,
callType = state.callType,
accountViewModel = accountViewModel,
onAccept = { /* handled by caller */ },
onAccept = { callController?.acceptIncomingCall(state.sdpOffer) },
onReject = { scope.launch { callManager.rejectCall() } },
)
}
@@ -28,6 +28,7 @@ import androidx.compose.animation.fadeOut
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -39,6 +40,7 @@ import androidx.core.util.Consumer
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.call.CallState
import com.vitorpamplona.amethyst.service.crashreports.DisplayCrashMessages
import com.vitorpamplona.amethyst.service.relayClient.notifyCommand.compose.DisplayNotifyMessages
import com.vitorpamplona.amethyst.ui.actions.NewUserMetadataScreen
@@ -48,6 +50,7 @@ import com.vitorpamplona.amethyst.ui.call.CallScreen
import com.vitorpamplona.amethyst.ui.components.getActivity
import com.vitorpamplona.amethyst.ui.components.toasts.DisplayErrorMessages
import com.vitorpamplona.amethyst.ui.navigation.composableFromEnd
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.navs.Nav
import com.vitorpamplona.amethyst.ui.navigation.navs.rememberNav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
@@ -162,6 +165,23 @@ fun AppNavigation(
DisplayNotifyMessages(accountViewModel, nav)
DisplayCrashMessages(accountViewModel, nav)
DisplayBroadcastProgress(accountViewModel)
ObserveIncomingCalls(accountViewModel, nav)
}
@Composable
private fun ObserveIncomingCalls(
accountViewModel: AccountViewModel,
nav: INav,
) {
val callState by accountViewModel.callManager.state.collectAsState()
LaunchedEffect(callState) {
val state = callState
if (state is CallState.IncomingCall) {
nav.nav(Route.ActiveCall(state.callId, state.callerPubKey))
}
}
}
@Composable
@@ -263,6 +283,7 @@ fun BuildNavigation(
composableFromEndArgs<Route.ActiveCall> {
CallScreen(
callManager = accountViewModel.callManager,
callController = accountViewModel.callController,
accountViewModel = accountViewModel,
onCallEnded = { nav.popBack() },
)