feat: continue voice impl
This commit is contained in:
parent
623ace3998
commit
10efd73965
|
|
@ -258,9 +258,9 @@ dependencies {
|
||||||
implementation(libs.jetbrains.markdown)
|
implementation(libs.jetbrains.markdown)
|
||||||
implementation(libs.highlights)
|
implementation(libs.highlights)
|
||||||
|
|
||||||
// implementation(libs.livekit.android)
|
implementation(libs.livekit.android)
|
||||||
// implementation(libs.livekit.android.camerax)
|
implementation(libs.livekit.android.camerax)
|
||||||
// implementation(libs.livekit.android.compose)
|
implementation(libs.livekit.android.compose)
|
||||||
|
|
||||||
implementation(platform(libs.firebase.bom))
|
implementation(platform(libs.firebase.bom))
|
||||||
implementation(libs.firebase.messaging)
|
implementation(libs.firebase.messaging)
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,7 @@ import chat.stoat.api.settings.LoadedSettings
|
||||||
import chat.stoat.api.settings.SyncedSettings
|
import chat.stoat.api.settings.SyncedSettings
|
||||||
import chat.stoat.composables.generic.HealthAlert
|
import chat.stoat.composables.generic.HealthAlert
|
||||||
import chat.stoat.composables.voice.VoicePermissionSwitch
|
import chat.stoat.composables.voice.VoicePermissionSwitch
|
||||||
|
import chat.stoat.composables.voice.VoiceSheet
|
||||||
import chat.stoat.core.model.schemas.HealthNotice
|
import chat.stoat.core.model.schemas.HealthNotice
|
||||||
import chat.stoat.material.EasingTokens
|
import chat.stoat.material.EasingTokens
|
||||||
import chat.stoat.ndk.NativeLibraries
|
import chat.stoat.ndk.NativeLibraries
|
||||||
|
|
@ -791,18 +792,14 @@ fun AppEntrypoint(
|
||||||
showVoiceUI = false
|
showVoiceUI = false
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
showVoiceUI = false
|
|
||||||
voiceChannelId = null
|
|
||||||
}
|
|
||||||
voiceChannelId?.let {
|
voiceChannelId?.let {
|
||||||
/*VoiceSheet(
|
VoiceSheet(
|
||||||
it,
|
it,
|
||||||
onDisconnect = {
|
onDisconnect = {
|
||||||
showVoiceUI = false
|
showVoiceUI = false
|
||||||
voiceChannelId = null
|
voiceChannelId = null
|
||||||
}
|
}
|
||||||
)*/
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ import io.ktor.http.ContentType
|
||||||
import io.ktor.http.contentType
|
import io.ktor.http.contentType
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.SerializationException
|
import kotlinx.serialization.SerializationException
|
||||||
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
|
import kotlinx.serialization.json.put
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class JoinCallResponse(
|
data class JoinCallResponse(
|
||||||
|
|
@ -18,10 +20,19 @@ data class JoinCallResponse(
|
||||||
val url: String,
|
val url: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun joinCall(channelId: String, nodeName: String): JoinCallResponse {
|
suspend fun joinCall(
|
||||||
|
channelId: String,
|
||||||
|
nodeName: String,
|
||||||
|
forceDisconnect: Boolean = true
|
||||||
|
): JoinCallResponse {
|
||||||
val response = StoatHttp.post("/channels/$channelId/join_call".api()) {
|
val response = StoatHttp.post("/channels/$channelId/join_call".api()) {
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
setBody(mapOf("node" to nodeName))
|
setBody(
|
||||||
|
buildJsonObject {
|
||||||
|
put("node", nodeName)
|
||||||
|
put("force_disconnect", forceDisconnect)
|
||||||
|
}
|
||||||
|
)
|
||||||
}.bodyAsText()
|
}.bodyAsText()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,28 @@
|
||||||
package chat.stoat.composables.voice
|
package chat.stoat.composables.voice
|
||||||
|
|
||||||
|
import androidx.compose.animation.animateColorAsState
|
||||||
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
|
import androidx.compose.animation.core.spring
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.ListItem
|
import androidx.compose.material3.ListItem
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import chat.stoat.R
|
import chat.stoat.R
|
||||||
import chat.stoat.api.StoatAPI
|
import chat.stoat.api.StoatAPI
|
||||||
import chat.stoat.core.model.util.UserVoiceState
|
|
||||||
import chat.stoat.composables.chat.displayNameInChannel
|
import chat.stoat.composables.chat.displayNameInChannel
|
||||||
import chat.stoat.composables.generic.UserAvatar
|
import chat.stoat.composables.generic.UserAvatar
|
||||||
|
import chat.stoat.core.model.util.UserVoiceState
|
||||||
import chat.stoat.internals.extensions.TransparentListItemColours
|
import chat.stoat.internals.extensions.TransparentListItemColours
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -23,23 +32,40 @@ fun VoiceParticipant(
|
||||||
speaking: Boolean,
|
speaking: Boolean,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
|
val speakingBorderColour by animateColorAsState(
|
||||||
|
targetValue = if (speaking) MaterialTheme.colorScheme.primary else Color.Transparent,
|
||||||
|
animationSpec = spring()
|
||||||
|
)
|
||||||
|
val speakingBorderWidth by animateDpAsState(
|
||||||
|
targetValue = if (speaking) 2.dp else 0.dp,
|
||||||
|
animationSpec = spring()
|
||||||
|
)
|
||||||
|
|
||||||
val user = StoatAPI.userCache[state.id]
|
val user = StoatAPI.userCache[state.id]
|
||||||
ListItem(
|
ListItem(
|
||||||
|
modifier = modifier,
|
||||||
colors = TransparentListItemColours,
|
colors = TransparentListItemColours,
|
||||||
headlineContent = {
|
headlineContent = {
|
||||||
Text(displayNameInChannel(state.id, channelId))
|
Text(displayNameInChannel(state.id, channelId))
|
||||||
},
|
},
|
||||||
leadingContent = {
|
leadingContent = {
|
||||||
// TODO circle when speaking
|
|
||||||
UserAvatar(
|
UserAvatar(
|
||||||
username = displayNameInChannel(state.id, channelId),
|
username = displayNameInChannel(state.id, channelId),
|
||||||
userId = state.id,
|
userId = state.id,
|
||||||
allowAnimation = speaking,
|
allowAnimation = speaking,
|
||||||
avatar = user?.avatar
|
avatar = user?.avatar,
|
||||||
|
modifier = Modifier.border(
|
||||||
|
speakingBorderWidth,
|
||||||
|
speakingBorderColour,
|
||||||
|
CircleShape
|
||||||
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
trailingContent = {
|
trailingContent = {
|
||||||
Row {
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
) {
|
||||||
if (!state.isPublishing) {
|
if (!state.isPublishing) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.ic_mic_off_24dp),
|
painter = painterResource(R.drawable.ic_mic_off_24dp),
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,15 @@
|
||||||
package chat.stoat.composables.voice
|
package chat.stoat.composables.voice
|
||||||
/*
|
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.animateContentSize
|
import androidx.compose.animation.animateContentSize
|
||||||
|
import androidx.compose.animation.core.LinearOutSlowInEasing
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.expandVertically
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.shrinkVertically
|
||||||
|
import androidx.compose.animation.slideOutVertically
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
|
@ -24,8 +31,11 @@ import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -46,6 +56,9 @@ import io.livekit.android.compose.local.RoomScope
|
||||||
import io.livekit.android.compose.state.rememberTracks
|
import io.livekit.android.compose.state.rememberTracks
|
||||||
import io.livekit.android.compose.ui.VideoTrackView
|
import io.livekit.android.compose.ui.VideoTrackView
|
||||||
import io.livekit.android.room.Room
|
import io.livekit.android.room.Room
|
||||||
|
import io.livekit.android.util.flow
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import logcat.asLog
|
import logcat.asLog
|
||||||
import logcat.logcat
|
import logcat.logcat
|
||||||
|
|
@ -124,6 +137,8 @@ fun VoiceSheet(
|
||||||
viewModel.getVoiceToken()
|
viewModel.getVoiceToken()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
RoomScope(
|
RoomScope(
|
||||||
url = viewModel.voiceLkNode,
|
url = viewModel.voiceLkNode,
|
||||||
token = viewModel.voiceToken,
|
token = viewModel.voiceToken,
|
||||||
|
|
@ -132,10 +147,22 @@ fun VoiceSheet(
|
||||||
connect = true,
|
connect = true,
|
||||||
) {
|
) {
|
||||||
val room = RoomLocal.current
|
val room = RoomLocal.current
|
||||||
val trackRefs = rememberTracks()
|
val roomState by room::state.flow.collectAsState()
|
||||||
|
val isMicOn by room.localParticipant::isMicrophoneEnabled.flow.collectAsState()
|
||||||
|
val isCameraOn by room.localParticipant::isCameraEnabled.flow.collectAsState()
|
||||||
|
val isScreenShared by room.localParticipant::isScreenShareEnabled.flow.collectAsState()
|
||||||
|
val activeSpeakers by room::activeSpeakers.flow.collectAsState()
|
||||||
|
val trackRefs by rememberTracks(passedRoom = room)
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
LazyColumn(modifier = Modifier.animateContentSize()) {
|
LazyColumn(
|
||||||
|
modifier = Modifier.animateContentSize(
|
||||||
|
animationSpec = tween(
|
||||||
|
durationMillis = 300,
|
||||||
|
easing = LinearOutSlowInEasing
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
val voiceStates = StoatAPI.voiceStateCache[viewModel.channelId]
|
val voiceStates = StoatAPI.voiceStateCache[viewModel.channelId]
|
||||||
items(voiceStates?.participants?.size ?: 0) { index ->
|
items(voiceStates?.participants?.size ?: 0) { index ->
|
||||||
val participantState = voiceStates?.participants[index]
|
val participantState = voiceStates?.participants[index]
|
||||||
|
|
@ -143,56 +170,104 @@ fun VoiceSheet(
|
||||||
VoiceParticipant(
|
VoiceParticipant(
|
||||||
state = participantState,
|
state = participantState,
|
||||||
channelId = viewModel.channelId,
|
channelId = viewModel.channelId,
|
||||||
speaking = false
|
speaking = activeSpeakers.any { it.identity?.value == participantState.id }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
items(trackRefs.size) { index ->
|
items(trackRefs.size) { index ->
|
||||||
VideoTrackView(
|
VideoTrackView(
|
||||||
trackReference = trackRefs[index],
|
trackReference = trackRefs[index],
|
||||||
|
room = room,
|
||||||
modifier = Modifier.fillParentMaxHeight(0.5f)
|
modifier = Modifier.fillParentMaxHeight(0.5f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item(key = "status") {
|
item(key = "status") {
|
||||||
AnimatedContent(
|
var showStatus by remember { mutableStateOf(true) }
|
||||||
room.state
|
LaunchedEffect(roomState) {
|
||||||
) { roomState ->
|
if (roomState == Room.State.CONNECTED) {
|
||||||
Row(
|
delay(1000)
|
||||||
horizontalArrangement = Arrangement.spacedBy(
|
showStatus = false
|
||||||
8.dp,
|
} else {
|
||||||
Alignment.CenterHorizontally
|
showStatus = true
|
||||||
),
|
}
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
}
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
AnimatedVisibility(
|
||||||
.padding(vertical = 8.dp)
|
visible = showStatus,
|
||||||
) {
|
enter = fadeIn(
|
||||||
CompositionLocalProvider(
|
animationSpec = tween(
|
||||||
LocalContentColor provides when (roomState) {
|
durationMillis = 300,
|
||||||
Room.State.CONNECTING, Room.State.RECONNECTING -> MaterialTheme.colorScheme.onSurfaceVariant
|
easing = LinearOutSlowInEasing
|
||||||
Room.State.CONNECTED -> MaterialTheme.colorScheme.primary
|
)
|
||||||
Room.State.DISCONNECTED -> MaterialTheme.colorScheme.error
|
) +
|
||||||
}
|
expandVertically(
|
||||||
|
expandFrom = Alignment.Top,
|
||||||
|
animationSpec = tween(
|
||||||
|
durationMillis = 300,
|
||||||
|
easing = LinearOutSlowInEasing
|
||||||
|
)
|
||||||
|
),
|
||||||
|
exit = fadeOut(
|
||||||
|
animationSpec = tween(
|
||||||
|
durationMillis = 300,
|
||||||
|
easing = LinearOutSlowInEasing
|
||||||
|
)
|
||||||
|
) +
|
||||||
|
slideOutVertically(
|
||||||
|
targetOffsetY = { it },
|
||||||
|
animationSpec = tween(
|
||||||
|
durationMillis = 300,
|
||||||
|
easing = LinearOutSlowInEasing
|
||||||
|
)
|
||||||
|
) +
|
||||||
|
shrinkVertically(
|
||||||
|
shrinkTowards = Alignment.Top,
|
||||||
|
animationSpec = tween(
|
||||||
|
durationMillis = 300,
|
||||||
|
easing = LinearOutSlowInEasing
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
AnimatedContent(
|
||||||
|
roomState
|
||||||
|
) { roomState ->
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(
|
||||||
|
8.dp,
|
||||||
|
Alignment.CenterHorizontally
|
||||||
|
),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 8.dp)
|
||||||
) {
|
) {
|
||||||
Icon(
|
CompositionLocalProvider(
|
||||||
painter = painterResource(
|
LocalContentColor provides when (roomState) {
|
||||||
when (roomState) {
|
Room.State.CONNECTING, Room.State.RECONNECTING -> MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
Room.State.CONNECTING -> R.drawable.ic_sprint_24dp
|
Room.State.CONNECTED -> MaterialTheme.colorScheme.primary
|
||||||
Room.State.CONNECTED -> R.drawable.ic_wifi_tethering_24dp
|
Room.State.DISCONNECTED -> MaterialTheme.colorScheme.error
|
||||||
Room.State.DISCONNECTED -> R.drawable.ic_wifi_tethering_error_24dp
|
|
||||||
Room.State.RECONNECTING -> R.drawable.ic_sprint_24dp
|
|
||||||
}
|
|
||||||
),
|
|
||||||
contentDescription = null
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = when (roomState) {
|
|
||||||
Room.State.CONNECTING -> stringResource(R.string.voice_status_connecting)
|
|
||||||
Room.State.CONNECTED -> stringResource(R.string.voice_status_connected)
|
|
||||||
Room.State.DISCONNECTED -> stringResource(R.string.voice_status_disconnected)
|
|
||||||
Room.State.RECONNECTING -> stringResource(R.string.voice_status_reconnecting)
|
|
||||||
}
|
}
|
||||||
)
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(
|
||||||
|
when (roomState) {
|
||||||
|
Room.State.CONNECTING -> R.drawable.ic_sprint_24dp
|
||||||
|
Room.State.CONNECTED -> R.drawable.ic_wifi_tethering_24dp
|
||||||
|
Room.State.DISCONNECTED -> R.drawable.ic_wifi_tethering_error_24dp
|
||||||
|
Room.State.RECONNECTING -> R.drawable.ic_sprint_24dp
|
||||||
|
}
|
||||||
|
),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = when (roomState) {
|
||||||
|
Room.State.CONNECTING -> stringResource(R.string.voice_status_connecting)
|
||||||
|
Room.State.CONNECTED -> stringResource(R.string.voice_status_connected)
|
||||||
|
Room.State.DISCONNECTED -> stringResource(R.string.voice_status_disconnected)
|
||||||
|
Room.State.RECONNECTING -> stringResource(R.string.voice_status_reconnecting)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -226,10 +301,13 @@ fun VoiceSheet(
|
||||||
) {
|
) {
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
room.localParticipant.setMicrophoneEnabled(!isMicOn)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
colors = ButtonDefaults.buttonColors(
|
colors = ButtonDefaults.buttonColors(
|
||||||
containerColor = MaterialTheme.colorScheme.secondaryContainer,
|
containerColor = if (isMicOn) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.secondaryContainer,
|
||||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
|
contentColor = if (isMicOn) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.onSecondaryContainer,
|
||||||
),
|
),
|
||||||
shapes = ButtonDefaults.shapes(),
|
shapes = ButtonDefaults.shapes(),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -237,17 +315,23 @@ fun VoiceSheet(
|
||||||
.height(64.dp)
|
.height(64.dp)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.ic_mic_off_24dp),
|
painter = if (isMicOn) painterResource(R.drawable.ic_mic_24dp) else painterResource(
|
||||||
|
R.drawable.ic_mic_off_24dp
|
||||||
|
),
|
||||||
contentDescription = "TODO change this string to res"
|
contentDescription = "TODO change this string to res"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Spacer(Modifier.width(4.dp))
|
Spacer(Modifier.width(4.dp))
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
room.localParticipant.setCameraEnabled(!isMicOn)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
enabled = false,
|
||||||
colors = ButtonDefaults.buttonColors(
|
colors = ButtonDefaults.buttonColors(
|
||||||
containerColor = MaterialTheme.colorScheme.secondaryContainer,
|
containerColor = if (isCameraOn) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.secondaryContainer,
|
||||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
|
contentColor = if (isCameraOn) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.onSecondaryContainer,
|
||||||
),
|
),
|
||||||
shapes = ButtonDefaults.shapes(),
|
shapes = ButtonDefaults.shapes(),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -255,14 +339,20 @@ fun VoiceSheet(
|
||||||
.height(64.dp)
|
.height(64.dp)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.ic_videocam_off_24dp),
|
painter = if (isCameraOn) painterResource(R.drawable.ic_videocam_24dp) else painterResource(
|
||||||
|
R.drawable.ic_videocam_off_24dp
|
||||||
|
),
|
||||||
contentDescription = "TODO change this string to res"
|
contentDescription = "TODO change this string to res"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Spacer(Modifier.width(4.dp))
|
Spacer(Modifier.width(4.dp))
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
room.localParticipant.setScreenShareEnabled(!isScreenShared)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
enabled = false,
|
||||||
colors = ButtonDefaults.buttonColors(
|
colors = ButtonDefaults.buttonColors(
|
||||||
containerColor = MaterialTheme.colorScheme.secondaryContainer,
|
containerColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
|
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
|
||||||
|
|
@ -280,7 +370,6 @@ fun VoiceSheet(
|
||||||
Spacer(Modifier.width(4.dp))
|
Spacer(Modifier.width(4.dp))
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
room.disconnect()
|
|
||||||
onDisconnect()
|
onDisconnect()
|
||||||
},
|
},
|
||||||
colors = ButtonDefaults.buttonColors(
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
|
@ -300,4 +389,4 @@ fun VoiceSheet(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}*/
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,10 @@ import chat.stoat.api.internals.Roles
|
||||||
@Composable
|
@Composable
|
||||||
fun rememberChannelPermissions(channelId: String, key1: Any = Unit): MutableLongState {
|
fun rememberChannelPermissions(channelId: String, key1: Any = Unit): MutableLongState {
|
||||||
val permissions = rememberSaveable { mutableLongStateOf(0L) }
|
val permissions = rememberSaveable { mutableLongStateOf(0L) }
|
||||||
|
val selfId = StoatAPI.selfId
|
||||||
|
val channel = StoatAPI.channelCache[channelId]
|
||||||
|
|
||||||
LaunchedEffect(channelId, key1) {
|
LaunchedEffect(channelId, key1, selfId, channel) {
|
||||||
if (StoatAPI.selfId == null) return@LaunchedEffect
|
if (StoatAPI.selfId == null) return@LaunchedEffect
|
||||||
if (StoatAPI.userCache[StoatAPI.selfId] == null) return@LaunchedEffect
|
if (StoatAPI.userCache[StoatAPI.selfId] == null) return@LaunchedEffect
|
||||||
if (StoatAPI.channelCache[channelId] == null) return@LaunchedEffect
|
if (StoatAPI.channelCache[channelId] == null) return@LaunchedEffect
|
||||||
|
|
|
||||||
|
|
@ -952,7 +952,7 @@ fun ChannelScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viewModel.channel?.voice != null &&
|
if ((viewModel.channel?.channelType == ChannelType.VoiceChannel || viewModel.channel?.voice != null) &&
|
||||||
channelPermissions has PermissionBit.Connect &&
|
channelPermissions has PermissionBit.Connect &&
|
||||||
Experiments.useVoiceChats2p0.isEnabled
|
Experiments.useVoiceChats2p0.isEnabled
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -41,13 +41,13 @@ import chat.stoat.api.routes.microservices.autumn.uploadToAutumn
|
||||||
import chat.stoat.api.routes.server.fetchMember
|
import chat.stoat.api.routes.server.fetchMember
|
||||||
import chat.stoat.api.routes.user.addUserIfUnknown
|
import chat.stoat.api.routes.user.addUserIfUnknown
|
||||||
import chat.stoat.api.routes.user.fetchUser
|
import chat.stoat.api.routes.user.fetchUser
|
||||||
import chat.stoat.core.model.schemas.Channel
|
|
||||||
import chat.stoat.core.model.schemas.Message
|
|
||||||
import chat.stoat.api.settings.GeoStateProvider
|
import chat.stoat.api.settings.GeoStateProvider
|
||||||
import chat.stoat.callbacks.Action
|
import chat.stoat.callbacks.Action
|
||||||
import chat.stoat.callbacks.ActionChannel
|
import chat.stoat.callbacks.ActionChannel
|
||||||
import chat.stoat.callbacks.UiCallback
|
import chat.stoat.callbacks.UiCallback
|
||||||
import chat.stoat.callbacks.UiCallbacks
|
import chat.stoat.callbacks.UiCallbacks
|
||||||
|
import chat.stoat.core.model.schemas.Channel
|
||||||
|
import chat.stoat.core.model.schemas.Message
|
||||||
import chat.stoat.internals.text.MessageProcessor
|
import chat.stoat.internals.text.MessageProcessor
|
||||||
import chat.stoat.persistence.KVStorage
|
import chat.stoat.persistence.KVStorage
|
||||||
import chat.stoat.screens.chat.ChatRouterDestination
|
import chat.stoat.screens.chat.ChatRouterDestination
|
||||||
|
|
@ -75,7 +75,10 @@ class ChannelScreenViewModel @Inject constructor(
|
||||||
var items = mutableStateListOf<ChannelScreenItem>()
|
var items = mutableStateListOf<ChannelScreenItem>()
|
||||||
var typingUsers = mutableStateListOf<String>()
|
var typingUsers = mutableStateListOf<String>()
|
||||||
|
|
||||||
var channel by mutableStateOf<Channel?>(null)
|
var channelId by mutableStateOf<String?>(null)
|
||||||
|
val channel: Channel?
|
||||||
|
get() = channelId?.let { StoatAPI.channelCache[it] }
|
||||||
|
|
||||||
var activePane by mutableStateOf<ChannelScreenActivePane>(ChannelScreenActivePane.None)
|
var activePane by mutableStateOf<ChannelScreenActivePane>(ChannelScreenActivePane.None)
|
||||||
var keyboardHeight by mutableIntStateOf(0)
|
var keyboardHeight by mutableIntStateOf(0)
|
||||||
|
|
||||||
|
|
@ -116,7 +119,7 @@ class ChannelScreenViewModel @Inject constructor(
|
||||||
fun switchChannel(id: String) {
|
fun switchChannel(id: String) {
|
||||||
// Reset state
|
// Reset state
|
||||||
this.loadMessagesJob?.cancel()
|
this.loadMessagesJob?.cancel()
|
||||||
this.channel = StoatAPI.channelCache[id]
|
this.channelId = id
|
||||||
this.items = mutableStateListOf(ChannelScreenItem.Loading)
|
this.items = mutableStateListOf(ChannelScreenItem.Loading)
|
||||||
this.activePane = ChannelScreenActivePane.None
|
this.activePane = ChannelScreenActivePane.None
|
||||||
this.typingUsers = mutableStateListOf()
|
this.typingUsers = mutableStateListOf()
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ telephoto = "1.0.0-alpha02"
|
||||||
haze = "1.7.2"
|
haze = "1.7.2"
|
||||||
chucker = "4.3.1"
|
chucker = "4.3.1"
|
||||||
androidx-test = "1.6.1"
|
androidx-test = "1.6.1"
|
||||||
livekit = "2.21.0"
|
livekit = "2.24.1"
|
||||||
livekit-compose = "1.4.0"
|
livekit-compose = "2.3.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
android-core-ktx = { module = "androidx.core:core-ktx", version = "1.16.0" }
|
android-core-ktx = { module = "androidx.core:core-ktx", version = "1.16.0" }
|
||||||
|
|
@ -99,7 +99,7 @@ chucker = { module = "com.github.chuckerteam.chucker:library", version.ref = "ch
|
||||||
chucker-noop = { module = "com.github.chuckerteam.chucker:library-no-op", version.ref = "chucker" }
|
chucker-noop = { module = "com.github.chuckerteam.chucker:library-no-op", version.ref = "chucker" }
|
||||||
square-logcat = { module = "com.squareup.logcat:logcat", version = "0.1" }
|
square-logcat = { module = "com.squareup.logcat:logcat", version = "0.1" }
|
||||||
livekit-android = { module = "io.livekit:livekit-android", version.ref = "livekit" }
|
livekit-android = { module = "io.livekit:livekit-android", version.ref = "livekit" }
|
||||||
livekit-android-camerax = { module = "io.livekit:android-camerax", version.ref = "livekit" }
|
livekit-android-camerax = { module = "io.livekit:livekit-android-camerax", version.ref = "livekit" }
|
||||||
livekit-android-compose = { module = "io.livekit:livekit-android-compose-components", version.ref = "livekit-compose" }
|
livekit-android-compose = { module = "io.livekit:livekit-android-compose-components", version.ref = "livekit-compose" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue